1.事件委托是什么
又叫事件代理,原理就是直接利用了事件冒泡的机制来实现,也就是说把子元素的事件绑定到了父元素的身上,如果子元素阻止了事件冒泡,那么委托也就不成立了。
阻止事件冒泡:event.stopPropagation()
addEventListener('click',函数名,true/false) 默认是false(事件冒泡),true(事件捕获)
事件冒泡的好处:提高性能,减少事件的绑定,也就是减少了内存的占用。
2.基本数据类型和引用数据类型的区别
基本数据类型:String、Number、Boolean、undefined、null
基本数据类型保存在栈内存中,保存的就是一个具体的值
引用数据类型:Object、Function、Array
保存在堆内存当中,声明一个引用类型的变量,它保存的是引用数据的地址
假如声明两个引用类型同时指向了一个地址的时候,修改其中一个那么另外一个也会改变
var obj = {name:'张三',age:18 } var obj1 = obj; obj1.name = '李四'; console.log(obj); console.log(obj1);------------------ {name:'李四',age:18} {name:'李四',age:18}
3.说一下原型链
原型就是一个普通对象,它是为构造函数的实例共享属性和方法;所有实例中引用的原型都是同一个对象
使用prototype可以把方法挂在原型上,内存只保存一份
function Person(){this.say = function(){console.log('唱歌');} } Person.prototype.look = function(){console.log('红楼梦'); } var p1 = new Person(); var p2 = new Person(); p1.say(); p2.say();// 调用几次,内存中就保存几次 p1.look(); p2.look();// 内存中只保存一次
__proto__可以理解为指针,实例对象中的属性,指向了构造函数的原型(prototype)
p1.__proto__ === Person.prototype ---- true
一个实例对象在调用属性和方法的时候,会依次从实例本身、构造函数原型、原型的原型上去查找
4.new操作符具体做了什么
1.先创建一个空对象
2.把空对象和构造函数通过原型链进行链接
3.把构造函数的this绑定到新的空对象身上
4.根据构造函数返回的类型判断,如果是值类型,则返回对象,如果是引用类型,就要返回这个引用类型
function newFun(Fun,...args){// 1.先创建一个空对象let newObj = {};// 2.把空对象和构造函数通过原型链链接newObj.__proto__ = Fun.prototype;// 3.把构造函数的this绑定到新的空对象身上const result = Fun.apply(newObj,args);// 4.根据构造函数返回的类型判断,如果是值类型,则返回对象,如果是引用类型,就要返回这个引用类型return result instanceof Object ? result : newObj; }function Person(name){this.name = name; } Person.prototype.say = function(){consoel.log('123456'); } const p1 = newFun(Person,'张三'); p1.say(); // 123456 console.log(p1); // Person {name:'张三'}
5.JS是如何实现继承的?
1.原型链继承
让一个构造函数的原型是另一个类型的实例,那么这个构造函数new出来的实例就具有该实例的属性。
function Parent(){this.isShow = true;this.info = {name:'abc',age:18}; } Parent.prototype.getInfo = function(){console.log(this.info);console.log(this.isShow); } function Child(){} Child.prototype = new Parent();let child1 = new Child(); child1.info.gender = '男'; child1.getInfo(); //{name:'abc',age:18,gender:'男'} truelet child2 = new Child(); child2.isShow = false; conosle.log(child2.info.gender); // 男 child2.getInfo(); //{name:'abc',age:18,gender:'男'} false
优点:写法方便简单,容易理解
缺点:对象实例共享所有继承的属性和方法。无法向父类构造函数传参
2.借用构造函数继承
在子类型构造函数的内部调用父类型构造函数;使用apply() 或 call() 方法将父对象的构造函数绑定在子对象上。
function Parent(gender){this.info = {name:'yyy',age:18,gender:gender} }function Child(gender){Parent.call(this,gender); }let child1 = new Child('男'); child1.info.nickname = 'zzzz'; console.log(child1.info); // {name:'yyy',age:18,gender:'男',nickname:'zzzz'}let child2 = new Child('女'); console.log(child2.info); // {name:'yyy',age:18,gender:'女'}
优点:解决了原型链实现继承的不能传参的问题和父类的原型共享的问题
缺点:方法都在构造函数中定义,因此无法实现函数复用
在父类的原型中定义的方法,对于子类型而言是不可见的,结果所有类型都是
只能使用构造函数模式
3.组合式继承
将 原型链 和 借用构造函数 的组合到一起。使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每一个实例都有自己的属性。
function Parent(gender){console.log('执行次数');this.info = {name:'yyy',age:18,gender:gender} }Parent.prototype.getInfo = function(){ // 使用原型链继承原型上的属性和方法console.log(this.info.name,this.info.age); }function Child(gender){Parent.call(this,gender); // 使用构造函数法传递参数 }Child.prototype = new Parent();let child1 = new Child('男'); child1.info.nickname = 'zzzz'; child1.getInfo(); // 'yyy' 18 console.log(child1.info); // {name:'yyy',age:18,gender:'男',nickname:'zzzz'} let child2 = new Child('女'); console.log(child2.info); // {name:'yyy',age:18,gender:'女'}
优点:解决了原型链继承和借用构造函数继承造成的影响
缺点:无论在什么情况下,都会调用两次父类构造函数,一次是在创建子类原型的时候,另一次是在子类构造函数内部
4.ES6的class类继承
Class通过extends关键字实现继承,其实质就是先创造出父类的this对象,然后用子类的构造函数修改this
子类的构造方法中必须调用super方法,且只有在调用了super()之后才能使用this,因为子类的this对象是继承父类的this对象,然后对其进行加工,而super方法表示的是父类的构造函数,用来新建父类的this对象
class Animal{constructor(kind){this.kind = kind;}getKind(){return this.kind;} }// 继承Animal class Cat extends Animal{constructor(name){// 子类的构造方法中必须先调用super方法super('cat');this.name = name;}getCatInfo(){console.log(this.name + ':' + super.getKind())} } const cat1 = new Cat('buding'); cats.getCatInfo(); // buding:cat
优点:语法简单易懂,操作更方便
缺点:并不是所有的浏览器都是支持class关键字