三、继承
1、原型链继承
// 父类: 公共属性和方法 function Person() { this.name = "why" this.age = 18 this.friends = [] } Person.prototype.eating = function () { console.log(this.name + " eating~") } // 子类: 特有属性和方法 function Student() { this.sno = 111 //学号 } // 在需要继承的对象上创建父类实例 Student.prototype=new Person() Student.prototype.study=function(){ console.log('学生学习'); } const s=new Student() console.log(s.name,s.age); s.eating() s.study() // 优点:实现了继承 // 弊端: // 1、打印对象, 继承的属性是看不到的 // 2、存在引用值共享问题,就是当a中某个属性是引用数据类型的时候,b实例如果修改了这个属性的内容则其他的b实例中这个属性也会一起改变(正常来说应该是互不干扰的,原始数据类型属性就是互不干扰的) // 3、实现类的过程中都没有传递参数
2、构造函数继承
// 父类: 公共属性和方法 function Person(name,age) { //this Person的实例对象 this.name = name //Student实例对象.name=name this.age = age this.friends = [] } Person.prototype.eating = function () { console.log(this.name + " eating~") } // 子类: 特有属性和方法 function Student(name,age,sno) { // this Student的实例对象 Person.call(this,name,age)//改变Person构造函数中的this指向 this.sno = sno //学号 } Student.prototype = new Person() Student.prototype.study = function () { console.log('学生学习'); } var stu1 = new Student("lilei",18,112) var stu2 = new Student("马六",18,112) console.log(stu1); stu1.friends.push('kabe') console.log(stu2); // 弊端: // 1、使用这种方法stu实例没有办法拿到Person函数原型上的属性和方法。 // 2、组合继承最大的问题就是无论在什么情况下,都会调用两次父类构造函数。一次在创建子类原型的时候;另一次在子类构造函数内部(也就是每次创建子类实例的时候);
3、寄生组合式
// 父类: 公共属性和方法 function Person(name,age) { //this Person的实例对象 this.name = name //Student实例对象.name=name this.age = age this.friends = [] } Person.prototype.eating = function () { console.log(this.name + " eating~") } // console.log(Object.keys(Person.prototype)); // 子类: 特有属性和方法 function Student(name,age,sno) { // this Student的实例对象 Person.call(this,name,age)//改变Person构造函数中的this指向 this.sno = sno //学号 } function Coder(name,age,lang){ Person.call(this,name,age) this.lang=lang } //为每一个子类创建一个原型对象 让原型对象的隐式原型属性指向父类原型对象 // Student.prototype=Object.create(Person.prototype) // Student.prototype.constructor=Student Student.prototype=Object.defineProperty(Object.create(Person.prototype),'constructor',{ value:Student }) Student.prototype.study=function(){ console.log('学生学习'); } console.log(Object.keys(Student.prototype)); const stu=new Student('zs',18,1198) console.log(stu); //创建了一个Coder的原型对象 Coder.prototype=Object.create(Person.prototype) const c=new Coder('zsc',19,'java') console.log(c); // 弊端:通过Object.create改变Student原型的指向后,Student原型上原有的属性和方法就消失了。
4、es6的extends类继承
class Person{ constructor(n,a){ this.name=n this.age=a } eating(){ console.log('吃饭'); } } class Student extends Person{ constructor(name,age,sno){ super(name,age) this.sno=sno } study(){ console.log('学习'); } } const s=new Student('zs',18,1111) console.log(s);
四、this指向
1、函数的this指向
函数中的this始终指向函数的调用者
//全局作用域中的this指向 window console.log(this); //函数中的this指向 函数的调用者 console.log(window); window.test() test() function test(){ console.log(this); } const obj={ name:'obj', fn:function(){ console.log(this); } } obj.fn()//obj //箭头函数中的this指向 函数定义时的作用域中的this指向 const obj1={ name:'obj1', fn:()=>{ console.log(this); } } obj1.fn()//window //在事件中,`this`指向触发这个事件的对象(dom) document.querySelector('#btn').onclick=function(){ console.log(this); }
2、改变函数的this指向 call 、 apply 、 bind
function fn(n,m){ console.log(n,m,this); } var obj={ name:'zsc' } fn()// window //需求: fn中的this指向 obj obj.fn() // 1.实现方案: 将函数放置在obj中 obj.fn=fn obj.fn() // 2.利用函数对象方法 call apply bind // 注意: 使用call 和 apply方法时 是在调用该函数 fn.call(obj,1,2) fn.apply(obj,[1,2]) var newfn=fn.bind(obj) //在调用bind方法时 函数并没有被调用 newfn(1,2)
call apply bind的区别【重点】:
call 和 apply 在调用时 函数会执行 ,而 bind 在调用时会返回一个新的函数 需要进行手动调用
call 和apply 再使用基本类似 但是在传参的时候 call 能够从第2个参数开始接收一个参数序列 a,b,c… apply方法在传参时需要将第二个参数设置为数组
3、绑定this指向的方式
默认绑定:全局环境中,this默认绑定到window
隐式绑定:一般地,被直接对象所包含的函数调用时,也称为方法调用,this隐式绑定到该直接对象
隐式丢失:隐式丢失是指被隐式绑定的函数丢失绑定对象,从而默认绑定到window。
显式绑定:通过call()、apply()、bind()方法把对象绑定到this上,叫做显式绑定
new绑定:如果函数或者方法调用之前带有关键字new,它就构成构造函数调用。对于this绑定来说,称为new绑定
构造函数通常不使用return关键字,它们通常初始化新对象,当构造函数的函数体执行完毕时,它会显式返回。在这种情况下,构造函数调用表达式的计算结果就是这个新对象的值
如果构造函数使用return语句但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对象作为调用结果
如果构造函数显式地使用return语句返回一个对象,那么调用表达式的值就是这个对象
五、new关键字具体做了什么
p1. 在内存中创建一个新的对象(空对象);
p2. 这个对象内部的[[Prototype]]属性会被赋值为该构造函数的prototype属性;
p3. 构造函数内部的this,会指向创建出来的新对象;
p4. 执行函数的内部代码(函数体代码);
p5. 如果构造函数没有返回非空对象,则返回创建出来的新对象;
function Student(){ 1. const obj={} // 创建一个空对象 2. obj.[[Prototype]]=Student.prototype // 把当前构造函数得原型赋给创建出来对象得隐式原型 3. this=obj // this,会指向创建出来的新对象; 以上代码全部有js解析器执行 程序员看不到摸不着 //4.接下来就是执行程序猿所写代码了 this.name=name this.age=age this.study=function(){ console.log(`我是${this.name},我能够快乐的学习`); } //程序员所写代码执行完毕 5.return this //程序员看不到 }