脚本宝典收集整理的这篇文章主要介绍了

JS核心知识点梳理——原型、继承(下)

脚本宝典小编觉得挺不错的,现在分享给大家,也给大家做个参考,希望能帮助你少写一行代码,多一份安全和惬意。

clipboard.png

引言

正如上篇所提到的,有些人认为JavaScript并不是真正的面向对象语言,在经典的面向对象语言中,您可能倾向于定义类对象,然后您可以简单地定义哪些类继承哪些类,JavaScript使用了另一套实现方式,继承的对象函数并不是通过复制而来,而是通过原型链继承(通常被称为 原型式继承 —— prototypal inheritance)。

函数的三种角色

一个函数,有三种角色。
当成普通函数,当成构造函数,当成对象

function Person (nickname) {
        var age = 15 //普通函数    标记1
        this.nickname = nickname     //构造函数
        this.sayName = function() {
            console.log(this.nickname)
        }
        console.log(age) 标记2
    }
    Person.nickname = '张三' //对象

    var p1 = new Person('李四') //'15'       
    console.log(Person.nickname) //'张三'    
    p1.sayName() //'李四'  标记4

如果标记1处改成 var nickname = 'fyy' 标记2处改成 console.log(nickname)
则后面的输出分别为 'fyy' '张三' 'fyy'

实例属性、原型属性、私有属性、静态属性

实例属性:又称成员就是实例里面的属性

var p1 = new Person('fyy',11)
p1// {age:11,name:'fyy'} 
//name和age就是实例属性

原型属性:就是类的原型上的属性
Person.prototype.say = function(){}
ar p1 = new Person('fyy',11)
p1.say // say就是调用的Person的原型属性

私有属性:就是只能在类的内部访问的方法和属性

class IncreasingCounter {
  #count = 0;
  get value() {
    console.log('Getting the current value!');
    return this.#count;
  }
  increment() {
    this.#count++;
  }
}
const counter = new IncreasingCounter();
counter.#count // 报错
counter.#count = 42 // 报错

静态方法:类上的静态方法就表示该方法不会被实例继承,而是直接通过类来调用
Array的isArray就是静态方法,只能通过实例调用因为挂在原型上没有意义,必然是Array的实例才能调用
同理还有String的fromCharCode等等

Array.isArray([1,2,3])    //

//定义
class Array {
    static isArray(){//...}
    constructor(){
    //...
    }
    //...
}

继承的方式

说到继承,首先得明白继承的是什么东西。个人认为继承应该分为成员属性和原型属性的继承。
实例属性是不能共用的属性或者方法,比如身份证。
原型属性是能共用的属性或者方法,比如爱好属性,吃饭方法。

实例属性的继承(构造函数+call

这个比较简单,实例属性用利用构造函数和call或者apply

const Person = function (name) {
        this.name = name
    }
const Students = function (name) {
        Person.call(this,name)
    }
const xm = new Students('小明')
console.log(xm)  //Students {name: "小明"}

原型属性的继承

这里里面坑有点多,大家听我娓娓道来。
通过原型链实现原型属性继承肯定没错,但是我们设计的时候有个原则 子类需要有自己的原型,父类也必须要有自己的原型,子实例在自己的原型上找不到属性的时候才会到父原型上去找
子类.prototype = 父类.prototype 这样肯定不行,虽然能继承父类原型的方法,但是子类的原型和父类的原型是同一个,给子类原型添加方法的时候,相当于给父类的原型也添加了一个方法。so我们应该有一个缓冲

子类实例---->子类原型------->中间对象------->父类原型 //沿着箭头能访问,表现上符合我们的设计原则

最常见的是使用父类的实例当这个中间对象。

Children.prototype = new Parent()    

但是了这么做有个不好的地方。会实例化一次父类。如果父类特别复杂,比如axios,那么会带来很多额外的开销。

我们看一下中间对象有什么作用,实际上只起了一个隔离和原型重定向的作用。完全可以用一个空对象实现这个功能

//实现中间对象
var fn = function() {}
fn.prototype = Parent.prototype
Children.prototype = new fn()

实际上,这个就是Oject.create()的实现

//Oject.create()
Object.create = function(obj){
    var fn = funcion(){}
    fn.prototype = obj
    reurturn new fn() 
}

终极继承解决方案

现在既要继承实例属性,又要继承原型属性。

    const Person = function (name) {
        this.name = name
    }
    Person.prototype.eat = function () {
        console.log('i am hungry,i want to eat!')
    }
    const Student = function (name) {
        Person.call(this,name)
    }
    Student.prototype = Object.create(Person.prototype)   //注意这里!原型重定向的后遗症
    const xm = new Student ('小明')
    xm.name //'小明' 
    xm.eat() //i am hungry,i want to eat!
    console.log(xm)
    //Student {name: "小明"}
    //  name: "小明"
    //  __proto__: Person
    //      __proto__: //这一层是个空对象,只有一个__proto__属性指向Person的原型
    //          eat: ƒ ()
    //            constructor: ƒ (name)
    //            __proto__: Object

但是这里 xm.constructor.name // Person
这里因为原型重定向后没有重置construtor,xm本身没有construtor只能找我们创建的空对象,空对象没有construtor所以继续往上找,找到了Person.prototype上的construtor为 Person
所以解决思路很简单,在改写原型链的时候重置一下constructor就行了

...
var temObj =  Object.create(Person.prototype)
temObj.constructor = Student
Student.prototype =temObj
...
 xm.constructor.name // Student  ok搞定  

new干了啥

既然new在“类”的创建里面必须使用,那么我们就说一下new到底干了啥事情

1.创建一个对象o继承构造函数
2.让构造函数的this变为o,并执行构造函数,将返回值设置为k
3.如果k

//仿写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:'小明'}

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;
        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方法貌似就是我们之前说的原型属性继承的终极解决方案吧?
未完待续....

mvvm中definePrototype

综合训练

总结

总结

以上是脚本宝典为你收集整理的

JS核心知识点梳理——原型、继承(下)

全部内容,希望文章能够帮你解决

JS核心知识点梳理——原型、继承(下)

所遇到的程序开发问题,欢迎加入QQ群277859234一起讨论学习。如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典网站推荐给程序员好友。 本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。

80%的人都看过