把以前的文章重写了一遍,以为真的把继承讲的很透彻了,引个流,你们能够看看。vue
明确一点:JavaScript并非真正的面向对象语言,没有真正的类,因此咱们也没有类继承es6
实现继承==有且仅有两种方式,call和原型链==编程
在介绍继承前咱们先介绍下其余概念数组
一个函数,有三种角色。
当成普通函数,当成构造函数(类),当成对象babel
function Person (nickname) { var age = 15 //当普通函数使 私有属性 this.age = 30 //当构造函数使 实例属性 } Person.prototype.age = 50 //当构造函数使 原型属性 Person.age =100 //当对象使 静态属性(类属性)
举例:
Array.isArray是类上的方法
Array.push是Array原型上的方法
Array.toString是沿着原型链查找到的object类上的原型方法app
继承原则:
使用call继承实例上的属性
使用原型链继承原型上的属性mvvm
const Person = function (name) { this.name = name } Person.prototype.introduce = function(){ Object.entries(this).forEach((item)=>{ console.log(`my ${item[0]} is ${item[1]}`) }) } const Student = function (name,age) { Person.call(this,name) this.age = age } Student.prototype = new Person() //这里new了父类一次,增长了额外开销 Student.prototype.constructor = Student //这一句可让student.constructor.name由Person变为Student 方便确认构造函数 let student = new Student('小明',15) student.introduce() 继承父类原型方法的同时继承父类实例上的属性 //my name is 小明 //my age is 15
组合继承有一个缺点,会额外new父类一次,增长了额外开销(想想若是父类特别大这消耗会有多大)函数
咱们仔细研究一下这一句话,为何它就能实现原型链继承优化
在上一篇文章中咱们学过,实例能访问类上的原型this
若是子类实例能访问父类的原型,那么咱们是否是能够说子类继承了父类?
可是子类实例只能访问子类原型呀,因此可我可让子类的原型等于父类的实例,由于父类的实例能够访问父类的原型,这就至关于子类实例能够访问父类原型了
这里你可能会问,为何不直接这么写student.prototype = Person.prototype,这样子实例也能够访问父实例呀
**没错!单从访问上来讲,你是对的。可是若是我后面先重写子类的原型,
好比我想写student.prototype = null,由于如今子类父类原型共用同一地址,父类也被改了,这个不符合咱们的初衷 **
还记得3.1说的原型链继承有个地方能够优化吗?在咱们知道了3.2原型链继承的写法后,咱们产生这样一个疑问,
这也是优化的两个方向
自动生成_proto_,改的是类的原型的指向
先说第二种优化,使用new自动生成_proto_,可是确定不能直接new父类吧,咱们new出一个空对象,而后改变这个类的原型指向咱们须要继承的
好比咱们须要继承obj
也就是能访问obj
实例能访问类的原型,让类的原型的地址指向Obj,实现继承
类为空类,减小开销
var create = function(obj){
var fn = funcion(){} //空类 fn.prototype = obj //改变类的原型的指向,指向要继承的对象 reurturn new fn() //自动生成_proto_
}
var A = create(B) //A能找到B(经过_proto_)
这个方法被es6实现了
Object.create()方法建立一个新对象,使用现有的对象来提供新建立的对象的__proto__。
const person = { isHuman: false, printIntroduction: function () { console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`); } }; const me = Object.create(person); me.name = "Matthew"; // "name" is a property set on "me", but not on "person" me.isHuman = true; // inherited properties can be overwritten me.printIntroduction(); //My name is Matthew. Am I human? true
不用new,直接改_proto_
不就是让子类原型指向父类的原型吗
student.prototype 直接指向 Person.prototype 有问题,那咱就不让它直接指向了,让它间接指向
student.prototype指针不变,它找不到属性,会找它的_proto_吧,我让它的_proto_指向Person.prototype不就好了
也就是 student.prototype._proto_ = Person.prototype
因此对于这句话 Student.prototype = new Person() 的优化,
重心放在了怎么减小开销来创建联系上
咱们能够既经过增长一个中间空对象(减小开销),来完成优化
Student.prototype = Object.create(Person.prototype)
也能够增长一个中间属性来完成优化
Student.prototype.__proto__ = Person.prototype
都能创建父类子类的联系
既然new在“类”的建立里面必须使用,那么咱们就说一下new到底干了啥事情题外话,new干了啥事,必定要从new完之后实例和类的关系来入手记忆,实例和类啥关系?两个关系实例是否是又类上面的实例属性,同时_proto_的指向关系
因此new 办了三件事
1.建立一个对象o继承构造函数
2.让构造函数的this变为o,并执行构造函数,将返回值设置为k
3.若是k是对象则返回对象,若是不是则返回o
//仿写new function new1(func) { var o = Object.create(func.prototype) var k = func.apply(o,arguments[1]) return typeof k === 'object'? k: o } const x = new1(Student,['张三']) x.name //'张三' x.eat //'i am hungry,i want to eat!'
咱们回过头再分析一下构造函数模式继承
const Person = function (name) { this.name = name } const Students = function (name) { Person.call(this,name) //this是student实例 } const xm = new Students('小明') //分析这里干了什么 console.log(xm) //Students {name: "小明"}
1.让空对象o继承Students(o能访问Students的原型)
2.student执行,执行Person的代码,this是o,而且传入name, o.name='小明'返回的k是undefined
3.返回o,也就是返回{name:'小明'}
class Person { } class Student extends person{ }
在babel es2015-loose模式下编译后的源码以下
"use strict"; function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; //修正constructor,避免constructor判断的不对,也通常用不到 subClass.__proto__ = superClass; //这句话看不懂,感受没啥用呀 } var Person = function Person() { }; var Student = /*#__PURE__*/ function (_person) { _inheritsLoose(Student, _person); function Student() { return _person.apply(this, arguments) || this; } return Student; }(person);
严格模式下,高级单例模式返回一个Student, 能够看到Person的实例属性用的Person的构造函数+apply继承的
原型属性用的_inheritsLoose这个方法继承的
_inheritsLoose方法貌似就是咱们以前说的寄生组合继承
咱们知道vue里面的数组有变异方法,变异方法有啥功能呢,就拿push来讲,一方面数组会变,另一方面有响应式(假设触发render方法)
思路:APO编程思想
数组之因此有push方法,是由于Array.prototype上有push方法
咱们须要实现本身的push方法,挂在Array.prototype上对原型链追踪进行拦截,可是呢又不能改变原型链上对非变异方法
原型链示意:
Vue里面添加过监控的数组实例--->咱们本身实现的变异方法-->原来的Array.prototype
const arrList = ['push','pop','shift','unshfit','reverse','sort','splice'] const render = ()=>{console.log('响应式,渲染视图')} const proto = Object.create(Array) arrList.forEach((method)=>{ proto[method] = function(){ render() Array.prototype[method].call(this,...arguments) } }) var data = [1,2,3] data.__proto__ = proto //mvvm响应式原理,若是添加响应式的目标是数组,我就执行这个操做 data.push(4) // 响应式,渲染视图,(data[1,2,3,4])
本节详细介绍了继承的原理以及优化,对于es6的继承语法糖也作了剖析。同时介绍了一下mvvm下数组借用继承实现响应式的用法,因为本人水平有限,若是有什么不对的地方,欢迎留言指出。