最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 聊聊JS继承那些事

    正文概述 掘金(Twisted)   2020-12-10   523

    先谈谈这篇文章的来源,其实我个人在工作当中几乎没有用到继承,但是这块的内容也算是面试中必考的内容,所以在每个阶段准备面试的时候都会看下继承的内容,之前在网上看了很多大神的博客,也没看懂,可能是因为自己的水平问题,只能靠硬性的去记忆。

    不死心的我昨晚静下心来看了下红宝书第四版的继承部分,才恍然大悟,感觉自己终于可以理解一点,不需要再死记硬背了。所以在这篇文章记录下自己在理解继承中遇到的困境以及理解,仅作为记录学习使用。

    注:以下的代码部分都是来自mqyqingfeng大佬的博客

    1.原型继承

    function Parent (age) {
        this.name = 'kevin';
        this.age = age;
        this.list = [1,2,3]
    }
    
    Parent.prototype.getName = function () {
        console.log(this.name);
    }
    
    function Child () {
    
    }
    
    Child.prototype = new Parent();
    
    var child1 = new Child();
    
    console.log(child1.getName()) // kevin
    

    原型继承的好处就是书写方便容易理解,直接将子类的原型指向父类的实例就行。

    所以子类的原型上包含了,父类实例上拥有的所有的属性。我的理解就是,在父类中所有的属性(不管是构造函数内部还是原型上)都被挂载在了子类的原型上。

    这样就会造成的一个问题就是,当子类创建实例的时候,全都拥有了这些属性。突出的缺点就是当一个实例对于父类一些复杂数据类型(比如父类中的list)做修改时 ,其他实例都会发生改变,看下面代码:

    var child1 = new Child()
    var child2 = new Child()
    child1.list.push(4)
    console.log(child1.list) // [1,2,3,4]
    console.log(child2.list) // [1,2,3,4]
    

    在这里可能会问了,为什么要强调复杂数据类型呢?基础数据类型不会有这个问题吗?其实这也是我最开始的疑问。简单来说,只有复杂的数据类型(比如数组、对象)你才可以对他们进行一些属性的添加或者删除而不是去改变它的引用,如果是基础数据类型的话,只能去修改这个值,这时候就相当于在实例上添加了一个同名的属性,那么就会覆盖原型上的属性啦,看代码:

    var child1 = new Child()
    var child2 = new Child()
    child1.name = 'zhaosi' // 这里相当于为实例child1添加了一个name属性,就不会去读原型上的name了
    console.log(child1.name) // 'zhaosi'
    console.log(child2.name) // 'kevin'
    

    缺点2:无法向构造函数传参。可以回头看下Parent函数中有个age属性是需要实例传参的,但是你在子类的实例中找不到传参的入口

    2.构造函数继承

    function Parent (age) {
        this.names = ['kevin', 'daisy'];
        this.age = age;
    }
    
    function Child (age) {
        Parent.call(this, age);
    }
    
    var child1 = new Child();
    
    child1.names.push('yayu');
    
    console.log(child1.names); // ["kevin", "daisy", "yayu"]
    
    var child2 = new Child(22);
    
    console.log(child2.names); // ["kevin", "daisy"]
    console.log(child2.age); // 22
    

    构造函数继承的时候就是在子类构造函数的内部执行了Parent.call(this),将父类的this指向了子类。所以从这里就可以看出缺陷了,这种方式只能继承父类构造函数定义的属性或者方法,不能继承父类原型上的属性,你可能会说了,这有什么问题呢?我把所有的函数和属性就定义在父类构造函数的内部不就行了,不在原型上写。

    道理上来讲,这种是完全可行的,完全可以实现继承。但是回头想想我们写代码不就是为了精益求精吗?我们的目标不就是复用吗?如果将函数也定义在构造函数中,那我们每次new一个实例,就要创建一个函数,岂不是浪费资源?

    所以啊,构造函数继承也不是最理想的方式,它虽然解决了原型继承里的传参和复合类型数据共享的问题,但也带来的新的问题就是不能继承父类原型上的内容,换句话说就是一些可以公用的东西,在实例中不能公用。

    3.组合继承

    function Parent (name) {
        this.name = name;
        this.colors = ['red', 'blue', 'green'];
    }
    
    Parent.prototype.getName = function () {
        console.log(this.name)
    }
    
    function Child (name, age) {
    
        Parent.call(this, name); // 第二次调用
        
        this.age = age;
    
    }
    
    Child.prototype = new Parent(); // 第一次调用
    Child.prototype.constructor = Child;
    
    var child1 = new Child('kevin', '18');
    
    child1.colors.push('black');
    
    console.log(child1.name); // kevin
    console.log(child1.age); // 18
    console.log(child1.colors); // ["red", "blue", "green", "black"]
    
    var child2 = new Child('daisy', '20');
    
    console.log(child2.name); // daisy
    console.log(child2.age); // 20
    console.log(child2.colors); // ["red", "blue", "green"]
    

    组合继承解决了原型继承和构造函数继承的问题,将两种继承方式进行完美的结合。但是呢这种方式也会带来一个问题就是,在代码中可以看到,一共调用了两次父类的构造函数,这会导致一个什么问题呢?

    在第一次调用Child.prototype = new Parent()Child的原型继承了Parent也就是说Parent的属性已经赋值到子类的原型上了,但是在第二次调用的时候,又将这些属性赋值到实例上了,也就是说子类的实例和原型都包含这些属性,是不是重复了?我们看下代码:

    console.log(child2.colors) // ["red", "blue", "green"]
    console.log(Child.prototype.colors) // ["red", "blue", "green"]
    

    好了,我们的目标是只调用一次,这就来了寄生组合继承

    4.寄生组合继承

    我们只需要将Child.prototype = new Parent()这一步替换掉就行了,改造后如下

    function Parent (name) {
        this.name = name;
        this.colors = ['red', 'blue', 'green'];
    }
    
    Parent.prototype.getName = function () {
        console.log(this.name)
    }
    
    function Child (name, age) {
    
        Parent.call(this, name); // 第二次调用
        
        this.age = age;
    
    }
    
    Child.prototype = Parent.prototype;
    Child.prototype.constructor = Child; // 修正构造函数的指向
    

    这样真的就万事大吉了吗?我们来打印看下子类实例的构造函数吧?

    var p = new Parent()
    console.log(p.constructor) // Child
    

    这里为什么Parent实例的构造函数居然是Child呢?我们来改下我们刚才做的修改

    Child.prototype = Parent.prototype;
    Child.prototype.constructor = Child;
    // 等价于
    var Parent = {
    	name: 'zhaosi',
    	constrcutor: 'Parent'
    }
    var Child = Parent
    Child.constrcutor = 'Child'
    

    其实就是一个对象赋值的问题,赋值后一个对象的属性发生改变,另外一个自然也变了,所以在执行Child.prototype.constructor = Child的时候父类的constructor也指向Child了,那就修改下呗,不让两个原型直接关联,最终如下:

    function Parent (name) {
        this.name = name;
        this.colors = ['red', 'blue', 'green'];
    }
    
    Parent.prototype.getName = function () {
        console.log(this.name)
    }
    
    function Child (name, age) {
        Parent.call(this, name);
        this.age = age;
    }
    
    // 关键的三步
    var F = function () {};
    
    F.prototype = Parent.prototype;
    
    Child.prototype = new F();
    
    
    var child1 = new Child('kevin', '18');
    
    console.log(child1);
    

    我还想到两种方式,供参考不知道可不可行:

    Child.prototype = Parent.prototype;
    方式1:
    Child.prototype = Object.assign({}, Parent.prototype)
    方式2:
    Child.prototype = Object.create(Parent.prototype)
    

    起源地下载网 » 聊聊JS继承那些事

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元