深刻理解Js中的继承

clipboard.png

1.引言

把以前的文章重写了一遍,以为真的把继承讲的很透彻了,引个流,你们能够看看。vue

明确一点:JavaScript并非真正的面向对象语言,没有真正的类,因此咱们也没有类继承es6

实现继承==有且仅有两种方式,call和原型链==编程

在介绍继承前咱们先介绍下其余概念数组

2.函数的三种角色

一个函数,有三种角色。
当成普通函数,当成构造函数(类),当成对象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

3.继承的方式

继承原则:
使用call继承实例上的属性
使用原型链继承原型上的属性mvvm

3.1 组合继承

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父类一次,增长了额外开销(想想若是父类特别大这消耗会有多大)函数

3.2 Student.prototype = new Person() 作了什么

咱们仔细研究一下这一句话,为何它就能实现原型链继承优化

在上一篇文章中咱们学过,实例能访问类上的原型this

若是子类实例能访问父类的原型,那么咱们是否是能够说子类继承了父类?

可是子类实例只能访问子类原型呀,因此可我可让子类的原型等于父类的实例,由于父类的实例能够访问父类的原型,这就至关于子类实例能够访问父类原型了

这里你可能会问,为何不直接这么写student.prototype = Person.prototype,这样子实例也能够访问父实例呀

**没错!单从访问上来讲,你是对的。可是若是我后面先重写子类的原型,
好比我想写student.prototype = null,由于如今子类父类原型共用同一地址,父类也被改了,这个不符合咱们的初衷 **

3.3 优化原理

还记得3.1说的原型链继承有个地方能够优化吗?在咱们知道了3.2原型链继承的写法后,咱们产生这样一个疑问,

  1. 真的要new一个对象,咱么实际只须要_Proto_来创建关系,能不能不复制父类的各类属性?
  2. 必定要new一个对象吗?实例和类原型的关系是经过_proto_来创建的,我手动设置这玩意,不new行不行?

这也是优化的两个方向

3.4 优化1

自动生成_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

3.5 优化2

不用new,直接改_proto_
不就是让子类原型指向父类的原型吗

student.prototype 直接指向 Person.prototype 有问题,那咱就不让它直接指向了,让它间接指向

student.prototype指针不变,它找不到属性,会找它的_proto_吧,我让它的_proto_指向Person.prototype不就好了

也就是 student.prototype._proto_ = Person.prototype

3.6总结一下优化

因此对于这句话 Student.prototype = new Person() 的优化,
重心放在了怎么减小开销来创建联系上

咱们能够既经过增长一个中间空对象(减小开销),来完成优化
Student.prototype = Object.create(Person.prototype)

也能够增长一个中间属性来完成优化
Student.prototype.__proto__ = Person.prototype

都能创建父类子类的联系

4.new干了啥

既然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:'小明'}

5.es6继承

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方法貌似就是咱们以前说的寄生组合继承

6.继承的应用:vue数组变异方法的实现

咱们知道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])

7.总结

本节详细介绍了继承的原理以及优化,对于es6的继承语法糖也作了剖析。同时介绍了一下mvvm下数组借用继承实现响应式的用法,因为本人水平有限,若是有什么不对的地方,欢迎留言指出。

相关文章
相关标签/搜索