2020前端面试题总结
一、CSS
1.css的重绘与回流
重绘:指当前节点更改外观,不会影响布局。浏览器不需要重新计算元素的集合属性,跳过回流,直接为元素绘制成更改后的样式。
回流:DOM结构的修改导致几何尺寸发生改变时,发生回流。
发生回流就是浏览器将已渲染好的受到影响的部分失效,重新构造这部分,完成reflow 后,浏览器会重新绘制这部分内容。
但有时一个元素的回流可能会导致了其所有子元素以及DOM中紧随其后的节点、祖先节点元素的随后的回流,所以一个节点的回流会引起页面某个部分甚至整个页面的回流
常见的集合尺寸发生改变的属性有:height,width,margin,padding,left,top,border.或者DOM节点发生增减。
常见题目:display:none;发生回流。
visibility:hidden;未发生回流,发生重绘。
回流必然会导致重绘,但重绘未必会发生回流。
所以在页面交互的过程中如果频繁的发生重绘与回流,会消耗很高的性能。因为回流比重绘消耗性能多的多,所以我们能用重绘就尽量重绘,不要回流。
减少重绘与回流的方法:主要就是减少DOM操作。
1.直接改变className,如果动态改变样式,则使用cssText(减少设置多项内联样式)
2.让要操作的元素进行“离线处理”,处理完后一起更新
当使用DocumentFragment进行缓存操作,引发一次回流和重绘
使用display:none 技术,只引发两次回流和重绘
使用cloneNode(true or false)和replaceChild技术,引发一次回流和重绘
3.不要经常访问会引起浏览器flush队列(宏任务、微任务)的属性,如果你确实要访问,利用缓存。
4.让元素脱离动画流,减少render 树的规模
5.牺牲平滑度换取速度
6.避免使用table布局
7.IE中避免使用javascript表达式
8.用CSS3新增的属性translate 代替top等方向值。
9.避免频繁使用style 而是 用class代替。
https://www.cnblogs.com/chenlei987/p/11447484.html
二、JS
1.let、var、const的区别:
在ES5中,声明变量只有var和function两种形式。但是因为var声明的变量会有一定的缺点(内层变量可能覆盖外层变量的问题以及用来计数的循环变量泄露为全局变量,下面会有详细的介绍),ES6提出了使用let和const声明变量,下面就来看看他们之间的区别。
a.什么时候提出的?
var是ES5提出的,let和const是ES6提出的
b.是否存在变量提升?
var声明的变量存在变量提升(将变量提升到当前作用域的顶部)。即变量可以在声明之前调用,值为undefined。
let和const不存在变量提升。即它们所声明的变量一定要在声明后使用,否则报ReferenceError错。
console.log(f) //undefined
var f = 1 ;
console.log(g) //ReferenceError: g is not defined
let g = 2;
console.log(h) //ReferenceError: g is not defined
const h = 2;
c.是否存在暂时性死区?
let和const存在暂时性死区。即只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
//以上代码if后面{}形成了块级作用域,由于使用let声明了tmp,则这个变量就绑定了块区域,在声明之前使用,会报错。
在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
d.是否允许重复声明变量?
var允许重复声明变量。let和const在同一作用域不允许重复声明变量。
var f = 4;
var f = 5;
console.log(5) //5
let g = 6;
let g = 7;
console.log(7) //SyntaxError: Identifier 'g' has already been declared
const h = 8;
const h = 9;
console.log(h) //SyntaxError: Identifier 'g' has already been declared
e.是否存在块级作用域?
var不存在块级作用域。let和const存在块级作用域。
到底什么叫块级作用域呢,
ES5中作用域有:全局作用域、函数作用域。没有块作用域的概念。因此也有一系列的问题。
//1,内层变量可能覆盖外层变量的问题
var a = 2;
function fun(){
console.log(a) //undefined
if(false){
var a = 3;//变量提升带来的,尽管存在块级作用域,但是var声明的变量会跨越这个域。
}
}
fun()
//2,用来计数的循环变量泄露为全局变量。
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5 i循环结束后,泄露成了全局变量
f.ECMAScript 6(简称ES6)中新增了块级作用域。块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域。
//1,解决内层变量可能覆盖外层变量的问题
var b = 2;
function fun1(){
console.log(b) //2 访问的外层变量
if(false){
let b = 3;//不存在变量提升,变量存在于块级作用域之中。
}
}
fun1()
//2,解决用来计数的循环变量泄露为全局变量。
var s1 = 'hello';
for (let j = 0; j < s1.length; j++) {
console.log(s1[j]); //j存在于块级作用域之中,和其绑定
}
console.log(j); // 报错 j is not defined
g.是否能修改声明的变量?
var和let可以。
const声明一个只读的常量。一旦声明,常量的值就不能改变。const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
const f = 10;
// f= 11;
// console.log(f) //报错 不能进行重复声明
const obj = {
name: '小明',
age: 18
}
obj.age = 20
console.log(obj) //{ name: '小明', age: 20 }
//const声明常量,不允许对变量重新赋值。对于引用类型的值而言,只要在栈内存保存的地址值不变即可。
总结:
var 没有块级作用域,可以实现变量提升;
let 有块级作用域,不支持变量提升,不能重复申明,暂存性死区。不能通过window.变量访问。
const 有块级作用域,不支持变量提升,不允许重复声明,暂存性死区。声明一个变量一旦声明就不能改变,改变报错。
2.箭头函数:
a.箭头函数:出现的作用除了让函数的书写变得很简洁,可读性很好外;最大的优点是解决了this执行环境所造成的一些问题。比如:解决了匿名函数this指向的问题(匿名函数的执行环境具有全局性),包括setTimeout和setInterval中使用this所造成的问题。
b.我们常见的window属性和方法有alter,document,parseInt,setTimeout,setInterval,localtion等等,这些在默认的情况下是省略了window前缀的。(window.alter = alter)。
c.在“use strict”严格模式下,没有直接的挂载者(或称调用者)的函数中this是指向window,这是约定俗成的。在“use strict”严格模式下,没有直接的挂载者的话,this默认为undefined。以下都是在非严格模式下讨论。
箭头函数的使用,this指向
箭头函数和普通函数的区别是什么?
普通函数this:
this总是代表它的直接调用者。
在默认情况下,没找到直接调用者,this指的是window。
在严格模式下,没有直接调用者的函数中的this是undefined。
使用call,apply,bind绑定,this指的是绑定的对象。
箭头函数this:
搞清箭头函数在函数和对象中定义的不同;
在使用=>定义函数的时候,this的指向是 定义时所在的对象,而不是使用时所在的对象;
不能够用作构造函数,这就是说,不能够使用new命令,否则就会抛出一个错误;
不能够使用 arguments 对象;
不能使用 yield 命令;
箭头函数的this指向:指向定义时所在的上下文,继承的是父执行上下文里面的this,切忌是父执行上下文
注意:简单对象{ }(非函数)是没有执行上下文的!
3.原型、原型链
JS原型原型链
构造函数创建对象:
function Person() {
}
var person = new Person();
person.name = 'Kevin';
console.log(person.name) // Kevin
Person 就是一个构造函数,我们使用 new 创建了一个实例对象 person
prototype
每个函数都有一个 prototype 属性
每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。
function Person() {
}
// 虽然写在注释里,但是你要注意:
// prototype是函数才会有的属性
Person.prototype.name = 'Kevin';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // Kevin
console.log(person2.name) // Kevin
proto
每一个JavaScript对象(除了 null )都具有的一个属性,叫proto,这个属性会指向该对象的原型
function Person() {
}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
constructor
每个原型都有一个 constructor 属性指向关联的构造函数 实例原型指向构造函数
function Person() {
}
console.log(Person === Person.prototype.constructor); // true
function Person() {
}
var person = new Person();
console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
实例与原型
function Person() {
}
Person.prototype.name = 'Kevin';
var person = new Person();
person.name = 'Daisy';
console.log(person.name) // Daisy
delete person.name;
console.log(person.name) // Kevin
在这个例子中,我们给实例对象 person 添加了 name 属性,当我们打印 person.name 的时候,结果自然为 Daisy。
但是当我们删除了 person 的 name 属性时,读取 person.name,从 person 对象中找不到 name 属性就会从 person 的原型也就是 person.proto ,也就是 Person.prototype中查找,幸运的是我们找到了 name 属性,结果为 Kevin。
原型与原型
var obj = new Object();
obj.name = 'Kevin'
console.log(obj.name) // Kevin
原型链
console.log(Object.prototype.proto === null) // true
JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。
4.js实现一个 new(通俗易懂)
JS实现一个new
function _new(){
let obj = new Object();
let Con = [].shift.call(arguments);
obj.__proto__ = Con.prototype;
let result = Con.apply(obj,arguments);
return typeof result === 'object' ? result : obj
}
5、继承
原理是:复制父类的属性和方法来重写子类的原型对象
原型继承
构造函数继承
组合继承
寄生继承
寄生组合继承
class
等等
// 寄生组合继承方法
function Father(...arr) {
this.some = '父类属性';
this.params = arr;
}
Father.prototype.someFn = function() {
console.log(1);
}
Father.prototype.someValue = '2';
function Son() {
Father.call(this, 'xxxx');
this.text = '2222';
}
Son.protptype = Object.create(Father.prototype);
Son.prototype.constructor = Son;
6.object.create()做了什么?
Object._create = function(obj){
function F(){
}; // 创建了一个新的构造函数F
F.prototype = obj; // 然后将构造函数F的原型指向了参数对象obj
return new F(); // 返回构造函数F的实例对象,从而实现了该实例继承obj的属性。
}
7.闭包
闭包就是有权访问一个函数内部变量的函数,也就是常说的函数内部嵌套函数,内部函数访问外部函数变量,从而导致垃圾回收机制没有将当前变量回收掉。这样的操作,有可能会带来内存泄漏。好处就是可以设计私有的方法和变量。
闭包详解
a、概念
闭包函数:声明在一个函数中的函数,叫做闭包函数。
闭包:内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。
b、特点
让外部访问函数内部变量成为可能;
局部变量会常驻在内存中;
可以避免使用全局变量,防止全局变量污染;
会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
c、闭包的创建:
闭包就是可以创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰。闭包会发生内存泄漏,每次外部函数执行的时 候,外部函数的引用地址不同,都会重新创建一个新的地址。但凡是当前活动对象中有被内部子集引用的数据,那么这个时候,这个数据不删除,保留一根指针给内部活动对象。
闭包内存泄漏为: key = value,key 被删除了 value 常驻内存中; 局部变量闭包升级版(中间引用的变量) => 自由变量;
d、闭包的应用场景
结论:闭包找到的是同一地址中父级函数中对应变量最终的值
function funA(){
var a = 10; // funA的活动对象之中;
return function(){
//匿名函数的活动对象;
alert(a);
}
}
var b = funA();
b(); //10
function outerFn(){
var i = 0;
function innerFn(){
i++;
console.log(i);
}
return innerFn;
}
var inner = outerFn(); //每次外部函数执行的时候,都会开辟一块内存空间,外部函数的地址不同,都会重新创建一个新的地址
inner();
inner();
inner();
var inner2 = outerFn();
inner2();
inner2();
inner2(); //1 2 3 1 2 3
var i = 0;
function outerFn(){
function innnerFn(){
i++;
console.log(i);
}
return innnerFn;
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();
inner2();
inner1();
inner2(); //1 2 3 4
function fn(){
var a = 3;
return function(){
return ++a;
}
}
alert(fn()()); //4
alert(fn()()); //4
function outerFn(){
var i = 0;
function innnerFn(){
i++;
console.log(i);
}
return innnerFn;
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();
inner2();
inner1();
inner2(); //1 1 2 2
(function() {
var m = 0;
function getM() {
return m; }
function seta(val) {
m = val; }
window.g = getM;
window.f = seta;
})();
f(100);
console.info(g()); //100 闭包找到的是同一地址中父级函数中对应变量最终的值
function a() {
var i = 0;
function b() {
alert(++i); }
return b;
}
var c = a();
c(); //1
c(); //2
function f() {
var count = 0;
return function() {
count++;
console.info(count);
}
}
var t1 = f();
t1(); //1
t1(); //2
t1(); //3
var add = function(x) {
var sum = 1;
var tmp = function(x) {
sum = sum + x;
return tmp;
}
tmp.toString = function() {
return sum;
}
return tmp;
}
alert(add(1)(2)(3)); //6
var lis = document.getElementsByTagName("li");
for(var i=0;i<lis.length;i++){
(function(i){
lis[i].onclick = function(){
console.log(i);
};
})(i); //事件处理函数中闭包的写法
}
function m1(){
var x = 1;
return function(){
console.log(++x);
}
}
m1()(); //2
m1()(); //2
m1()(); //2
var m2 = m1();
m2(); //2
m2(); //3
m2(); //4
var fn=(function(){
var i=10;
function fn(){
console.log(++i);
}
return fn;
})()
fn(); //11
fn(); //12
function love1(){
var num = 223;
var me1 = function() {
console.log(num);
}
num++;
return me1;
}
var loveme1 = love1();
loveme1(); //输出224
function fun(n,o) {
console.log(o);
return {
fun:function(m) {
return fun(m,n);
}
};
}
var a = fun(0); //undefined
a.fun(1); //0
a.fun(2); //0
a.fun(3); //0
var b = fun(0).fun(1).fun(2).fun(3); //undefined 0 1 2
var c = fun(0).fun(1);
c.fun(2);
c.fun(3); //undefined 0 1 1
function fn(){
var arr = [];
for(var i = 0;i < 5;i ++){
arr[i] = function(){
return i;
}
}
return arr;
}
var list = fn();
for(var i = 0,len = list.length;i < len ; i ++){
console.log(list[i]());
} //5 5 5 5 5
function fn(){
var arr = [];
for(var i = 0;i < 5;i ++){
arr[i] = (function(i){
return function (){
return i;
};
})(i);
}
return arr;
}
var list = fn();
for(var i = 0,len = list.length;i < len ; i ++){
console.log(list[i]());
} //0 1 2 3 4
8.垃圾回收机制(闭包的延伸)
js拥有特殊的垃圾回收机制,当一个变量在内存中失去引用,js会通过特殊的算法将其回收,并释放内存。
分为以下两个阶段:
标记阶段:垃圾回收器,从根对象开始遍历,访问到的每一个对象都会被标示为可到达对象。
清除阶段:垃圾回收器在对内存当中进行线性遍历,如果发现该对象没有被标记为可到达对象,那么就会被垃圾回收机制回收。
这里面牵扯到了引用计数法,每次引用都被会‘➕1’ 如果标记清零,那么就会被回收掉。
9.数据的深拷贝(只针对于Object类型)浅拷贝
两个对象A、B, A有数据B为空,B复制了A,我们修改A,如果B中的数据跟着变化了,那就是浅拷贝,如果没有变化,那就是深拷贝。说明B另开辟了一块内存。
基本数据类型:number,string,boolean,null,undefined,symbol等;
引用数据类型:object({}对象,数组[]),function函数等;
因为拷贝是对数据进行操作,所以我们得了解一下这两类的数据存储方式;
基本数据类型一般存储在栈中;引用数据类型一般存放在堆中;
a.基本类型–属性名,属性值存储在栈内存中,例如var a=1;
当你b=a复制时,栈内存会新开辟一个内存,例如这样:
当我们修改a = 2时,b中的数据没有改变;
我们可以得出,基本数据类型中,复制一个对象,会在栈中开辟一块新内存,改变其中一个对象另一个对象不会受影响;但是这不是深拷贝,深拷贝只针对较为复杂的object数据类型;
b.引用数据类型–属性名存在栈内存中,属性值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值,我们以上面浅拷贝的例子画个图:
当b=a进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值。
而当我们a[0]=1时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,这就是所谓的浅拷贝了。
那,要是在堆内存中也开辟一个新的内存专门为b存放值,就像基本类型那样,岂不就达到深拷贝的效果了
深拷贝的实现:
a.利用递归的方式实现;
b.利用json.stringfy(),json.parse()实现
该方法忽略掉undefined、忽略Symbol、忽略function。只适合简单深拷贝
c.简单的深拷贝 Object.assign方法来实现
扩展运算符 …obj
递归的方式实现
function deepClone(obj) {
let _self = this;
let objClone = Array.isArray(obj)? []: {
};
if(obj && typeof obj=="object"){
for (let k in obj){
if (obj.hasOwnProperty(k)) {
if(obj[k] && typeof obj[k]=="object"){
objClone[k] = _self.deepClone(obj[k]);
}else {
objClone[k] = obj[k];
}
}
}
}
return objClone;
}
2、利用json.stringify()和json.parse();
function deepClone(obj){
let _obj = JSON.stringify(obj),
objClone = JSON.parse(_obj);
return objClone
}
let a=[0,1,[2,3],4],
b=deepClone(a);
a[0]=1;
a[2][0]=1;
console.log(a,b);
讲了这么多,我们来说下用途,我们在开发中,如果有很多数据要处理,贸然的对数据进行增删改的话,可能会存在污染源数据问题,导致数据无法恢复;
深拷贝就帮助我们解决了这个隐患,我们就可以安心的去对数据进行相应的操作;
注:有些方法也可以实现拷贝,例如:slice()、assign()等方法,这两个方式如果拷贝的层级只有一层为深拷贝,如果拷贝的层级是多层,那就是浅拷贝;
10.函数的节流和防抖
应用场景
a.函数防抖的应用场景(连续触发的事件)
连续的事件,只需触发一次回调的场景有:
搜索框搜索输入。只需用户最后一次输入完,再发送请求
手机号、邮箱验证输入检测
窗口大小Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
b.函数节流的应用场景(间隔一段时间触发的事件)
间隔一段时间执行一次回调的场景有:
滚动加载,加载更多或滚到底部监听
谷歌搜索框,搜索联想功能
高频点击提交,表单重复提交
函数防抖与节流的比较
相同点:
都可以通过使用 setTimeout 实现。
目的都是,降低回调执行频率。节省计算资源。
不同点:
函数防抖,在一段连续操作结束后,处理回调,利用clearTimeout 和 setTimeout实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能。
函数防抖关注一定时间连续触发的事件只在最后执行一次,而函数节流侧重于一段时间内只执行一次。
防抖函数:将多次触发变成最后一次触发;
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
let timer只能在setTimeout的父级作用域中,这样才是同一个timer,并且为了方便防抖函数的调用和回调函数fn的传参问题,我们应该用闭包来解决这些问题。
function debounce(fn,delay) {
let timer;//维护一个timer
return function () {
let args = arguments;
if (timer){
clearTimeout(timer);
}
timer = setTimeout(()=>{
fn.apply(this, args);//相当于this.fn(args);
}, delay)
}
}
function count() {
let num = 0;
return function(){
console.log(this)
console.log(++num)
}
}
window.onmousemove = debounce(count(), 1000)
不断触发事件, 只执行最后一次的事件;
节流函数:将多次执行变成每隔一个时间节点去执行的函数
每隔一段时间,只执行一次函数。
定时器解决函数节流
function throttle(fn,delay) {
let timer;
return function () {
let args = arguments;
if(timer){
return;
}
timer = setTimeout(()=>{
fn.apply(this, args);//相当于this.fn(args);
timer = null;
},delay)
}
}
window.onmousemove = throttle(count(), 2000)
function count() {
let num = 0;
return function(){
console.log(this)
console.log(++num)
}
}
利用时间戳解决函数节流
function throttle(fn, delay) {
let previous = 0;
// 使用闭包返回一个函数并且用到闭包函数外面的变量previous
return function() {
let _this = this;
let args = arguments;
let now = new Date();
if(now - previous > delay) {
fn.apply(_this, args);
previous = now;
}
}
}
// test
function testThrottle(e, content) {
console.log(e, content);
}
let testThrottleFn = throttle(testThrottle, 1000); // 节流函数
document.onmousemove = function (e) {
testThrottleFn(e, 'throttle'); // 给节流函数传参
}
11.call、apply区别
相同点:都是重定向this指针的方法。
不同点:call和apply的第二个参数不相同,call是若干个参数的列表。apply是一个数组
核心:
将函数设为对象的属性
执行&删除这个函数
指定 this到函数并传入给定参数执行函数
如果不传入参数,默认指向为 window
call:
Function.prototype.call2 = function (context = window) {
context.fn = this;//给context 赋值方法
let args = [...arguments].slice(1);
let result = context.fn(...args);//调用方法
delete context.fn;
return result;
}
apply:
Function.prototype.apply2 = function (context = window) {
context.fn = this;
let result;
if(arguments[1]){
result = context.fn(args)
}else {
result = context.fn()
}
delete context.fn;
return result;
}
12.bind:
支持函数柯里化,可以先传一部分参数,调用的时候再传;
返回指向某个对象的函数;
bind函数实现详解
var obj = {
z: 1
};
var obj1 = {
z: 2
};
function fn(x, y) {
console.log(x + y + this.z);
};
// call与apply
fn.call(obj, 2, 3); //6
fn.apply(obj, [2, 3]); //6
var bound = fn.bind(obj, 2);
bound(3); //6
//尝试修改bind返回函数的this
bound.call(obj1, 3); //6
Function.prototype.bind2 = function (context = window) {
let self = this;
let args = [...arguments].slice(1);
return function F(){
if(self instanceof F){
return new self(...args,...arguments);
}
return self.apply(context,args.concat([...arguments]));
}
}
13.事件冒泡事件捕获
捕获:就是从根元素开始向目标元素递进的一个关系;从上而下
冒泡:是从目标元素开始向根元素冒泡的过程;想象一下水里的泡泡从下而上。
事件冒泡
<div class="classv">
我是祖宗
<div class="actva">我是老爸
<div class="foo">我是孩子</div>
</div>
</div>
<script type="text/javascript">
var a = document.querySelector('.classv').addEventListener('click', function() {
console.log('我是祖宗')
}, false)
var b = document.querySelector('.actva').addEventListener('click', function() {
console.log('我是老爸')
}, false)
var c = document.querySelector('.foo').addEventListener('click', function() {
console.log('我是孩子')
}, false)
</script>
输出结果:
我是孩子—我是老爸—我是祖宗
事件捕获
<div class="classv">
我是祖宗
<div class="actva">我是老大
<div class="foo">我是老幺</div>
</div>
</div>
<script type="text/javascript">
var a = document.querySelector('.classv').addEventListener('click', function() {
console.log('我是祖宗')
}, true)
var b = document.querySelector('.actva').addEventListener('click', function() {
console.log('我是老大')
}, true)
var c = document.querySelector('.foo').addEventListener('click', function() {
console.log('我是老幺')
}, true)
</script>
输出结果:
我是祖宗—我是老爸—我是孩子
总结:
(1).addEventListener的第三个参数决定了是事件捕获还是事件冒泡
true:表示注册的事件在捕获阶段触发
false:表示注册的事件在冒泡阶段触发-----默认值
(2).事件冒泡:
当一个元素的事件触发了,同样的事件会在该元素的所有祖辈元素中一次触发,事件冒泡(从里往外).
(3).事件捕获:
同事件冒泡相反,从外往里
误解:元素并不是只有注册了事件,才会有事件冒泡和事件捕获
(4).事件流
三个阶段; 1.事件捕获 2.事件目标[事件源] 3.事件冒泡
注意点: 一个元素的事件,不会再两个阶段都触发
再实际使用中,有时候我们会需要阻止事件冒泡,可以使用e.stopPropagation();
附阻止浏览器默认行为的方法:
函数内return false ; e.preventDefault();----e是事件对象event
addEventListener()必须用removeEventListener()解除
事件冒泡和事件捕获
14.instanceof原理
instanceOf用来判断右边的prototype是否在左边的原型链上,告诉我们左边是否是右边的实例。
//左边是否是右边的实例
//左边:实例,右边:构造函数
function instanceof1(left,right) {
let prototype = right.prototype; //构造函数(类)的原型
let _left = left.__proto__; //实例化的原型对象
while(true){
if(_left == null){
return false;
}
if(prototype == _left){
return true;
}
_left = _left.__proto__;
}
}
15.type of 的值有哪些?
(1).String
(2).Number
(3).Boolean
(4).Object
像常见的数组,对象或者是正则,日期等等都是object;
typeof null // object
(5).Function
(6).undefined
typeof undefined // undefined
(7)symbol
typeof Symbol() // 'symbol'