资讯专栏INFORMATION COLUMN

深入浅出面向对象和原型【概念篇3】—— 原型链和继承

levinit / 3247人阅读

摘要:由一个问题引发的思考这个方法是从哪儿蹦出来的首先我们要清楚数组也是对象,而且是对象的实例也就是说,下面两种形式是完全等价的只不过是一种字面量的写法,在深入浅出面向对象和原型概念篇文章里,我们提到过类会有一个属性,而这个类的实例可以通过属性访

1.由一个问题引发的思考
    let arr1 = [1, 2, 3]
    let arr2 = [4, 5, 6]
    arr1.concat(arr2) // [1, 2, 3, 4, 5, 6]
concat这个方法是从哪儿蹦出来的??
    首先我们要清楚 数组也是对象,而且是Array对象的实例
    
    console.log(arr1 instanceof Array) // true

    也就是说,下面两种形式是完全等价的
    let arr1 = [1, 2, 3]
    let arr2 = new Array(1, 2, 3)

    只不过[1,2,3]是一种字面量的写法

ok,在深入浅出面向对象和原型【概念篇2】文章里,我们提到过

类会有一个prototype属性,而这个类的实例可以通过__proto__属性访问类的prototype属性

既然arr1是Array对象的实例,那么arr1自然可以通过其__proto__属性访问到其类Array的属性
只要Array的prototype里有concat()这个方法,那么arr1自然能用

    console.log(arr1.__proto__ === Array.prototype) // true
2.第二个问题 arr1.valueOf(),valueOf从哪儿来的?
按照第一个问题的解决思路,valueOf应该是Array的prototype属性里的
但是在Array的prototype属性里并没有找到valueOf方法


但是我们又看到了Array的prototype里竟然也有__proto__属性,并且指向Object,在这里我们找到了valueOf

这是为什么呢?

现在我们需要知道的是Array是Object的实例

    console.log(Array instanceof Object) // true
好了,现在我们知道了
1.arr1 是 Array 的实例
2.Array 是 Object 的实例

当arr1找不到valueOf时,会通过其自身的__proto__属性去找Array的prototype,看看里面有没有
如果Array的prototype里没有的话,接下来会通过Array的prototype里的__proto__属性去找Object的prototype,看看里面有没有

而反观arr1寻找concat时,因为直接就在Array的prototype里找到了,所以不会再去通过__proto__寻找Object的prototype里有没有
我们把这个过程抽象化表达

当一个实例调用一个方法时

如果实例本身没有这个方法,那么这个实例会通过自身的__proto__属性去访问其类的prototype属性

如果该实例的类的prototype属性也没有,那么会通过该类prototype属性里的__proto__属性去访问该类的类的prototype属性

若还没有找到,则以此类推,直到找到该方法或者父级类的prototype内的__proto__为null时为止

这个不断通过__proto__属性和prototype属性寻找的过程,叫做原型链

3.自己实现继承 3.1 什么是继承
继承是指一个对象直接使用另一对象的属性和方法
3.2 继承的目的
继承的目的其实就是省代码,什么意思呢?

假设你现在是js设计师,你已经设计了Object对象
现在你需要基于Object对象再设计一个Array对象
并且Array对象可以直接使用Object对象的属性和方法
这个时候你肯定希望能够通过一种方式能直接让Array对象使用Object对象的属性和方法
而不是把Object对象的属性和方法重新写到Array里去,这样做太累了

而这种方式就叫继承
3.3 实现继承的要点
由继承的定义,我们可知,实现继承必须满足两个条件
1.得到一个类的属性
2.得到一个类的方法
第一步:我们先定义两个类
// 第一个类
function People(name, age) {
    this.name = name
    this.age = age
}

People.prototype = {
    printName: function () {
        console.log(this.name)
    }
}

// 第二个类
function Male(sex) {
    this.sex = sex
}

Male.prototype = {
    printSex: function () {
        console.log(this.sex)
    }
}
第二步:让Male的每个实例都获取到People的属性
function Male(sex, name, age) {
    // 注意Male的实例在这里就是this
    // 所以我们要让this获取到People的属性即可
    // 实现方式——让People的this被替换成Male的this,且让People被直接调用
    People.bind(this)(name, age)
    this.sex = sex
}

console.log(new Male("man", "bruce", "16")) // {name: "bruce", age: "16", sex: "man"}
第三步:让Male的每个实例都获取到People的方法

Object.create()

    // 例子
    let a = Object.create({"zxz": 1})
    console.log(a.__proto__) // {zxz: 1}
    // Object.create() 方法会返回一个对象,这个对象拥有被指定的原型
    // 且这个对象可通过自身的__proto__属性访问这个原型
    Male.prototype = Object.create(People.prototype)
    // 这一步结束后,Male的实例就可以通过原型链拥有People的方法

    // 接着,我们才能对Male自己要新增的方法进行添加,否则会被覆盖掉

    Male.prototype.printSex = function () {
        console.log(this.sex)
    }

    console.log((new Male("man", "bruce", "16")).__proto__.__proto__ === People.prototype)
第四步:注意constructor属性
我们都知道constructor属性指向其类
当我们完成第三步后
需要注意的是

console.log(Male.prototype.constructor === People.prototype.constructor) // true

因为执行了Object.create()的原因,此时Male原型上的constructor被指定成People了
现在需要我们手动赋值将其更改

Male.prototype.constructor = Male
现在,我们可以把上述步骤统一起来
function People(name, age) {
    this.name = name
    this.age = age
}

People.prototype = {
    printName: function () {
        console.log(this.name)
    }
}

function Male(sex, name, age) {
    People.bind(this)(name, age)
    this.sex = sex
}

Male.prototype = Object.create(People.prototype)
Male.prototype.constructor = Male
Male.prototype.printSex = function () {
    console.log(this.sex)
}
3.4 把继承获取方法的关键步骤封装起来
function inherit(fatherObject, childObject) {
    let _prototype = Object.create(fatherObject.prototype);
    _prototype.constructor = childObject;
    childObject.prototype = _prototype;
}
3.5 完整代码
function inherit(fatherObject, childObject) {
    let _prototype = Object.create(fatherObject.prototype);
    _prototype.constructor = childObject;
    childObject.prototype = _prototype;
}

function People(name, age) {
    this.name = name
    this.age = age
}

People.prototype = {
    printName: function () {
        console.log(this.name)
    }
}

function Male(sex, name, age) {
    People.bind(this)(name, age)
    this.sex = sex
}

inherit(People, Male)
Male.prototype.printSex = function () {
    console.log(this.sex)
}

let bruce = new Male("man", "bruce", 16)
4.实现继承的其它方法
function People(name, age) {
    this.name = name
    this.age = age
}

People.prototype.sayName = function () {
    console.log(this.name)
}

function Student(name, age, score) {
    People.call(this, name, age)
    this.score = score
}

function create(prototypeObj) {
    let empty = function () {}
    empty.prototype = prototypeObj
    return new empty()
    // return值如下
    // {
    //     __proto__:prototypeObj
    // }
}

Student.prototype = create(People.prototype)

Student.prototype.work = function () {
    console.log("work")
}
5.hasOwnProperty
当Male的实例继承了Male的方法和People的属性和方法后,如何分辨某个属性或方法是自己的还是通过原型链继承来的呢?
这就要用到 hasOwnProperty
该方法可以判断一个对象是否包含自定义属性而不是原型链上的属性
并且会忽略掉那些从原型链上继承到的属性

console.log(bruce.hasOwnProperty("age")) // true
console.log(bruce.hasOwnProperty("printSex")) // false

因为printSex方法是bruce实例通过原型链从Male类的原型上获取的,因此会被hasOwnProperty忽略

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92945.html

相关文章

  • 深入javascript——原型继承

    摘要:在使用原型链实现继承时有一些需要我们注意的地方注意继承后的变化。在了解原型链时,不要忽略掉在末端还有默认的对象,这也是我们能在所有对象中使用等对象内置方法的原因。 在上一篇post中,介绍了原型的概念,了解到在javascript中构造函数、原型对象、实例三个好基友之间的关系:每一个构造函数都有一个守护神——原型对象,原型对象心里面也存着一个构造函数的位置,两情相悦,而实例呢却又...

    UCloud 评论0 收藏0
  • 详解javascript的类

    摘要:原文地址详解的类博主博客地址的个人博客从当初的一个弹窗语言,一步步发展成为现在前后端通吃的庞然大物。那么,的类又该怎么定义呢在面向对象编程中,类是对象的模板,定义了同一组对象又称实例共有的属性和方法。这个等同于的属性现已弃用。。 前言 生活有度,人生添寿。 原文地址:详解javascript的类 博主博客地址:Damonare的个人博客   Javascript从当初的一个弹窗语言,一...

    hufeng 评论0 收藏0
  • 详解javascript的类

    摘要:原文地址详解的类博主博客地址的个人博客从当初的一个弹窗语言,一步步发展成为现在前后端通吃的庞然大物。那么,的类又该怎么定义呢在面向对象编程中,类是对象的模板,定义了同一组对象又称实例共有的属性和方法。这个等同于的属性现已弃用。。 前言 生活有度,人生添寿。 原文地址:详解javascript的类 博主博客地址:Damonare的个人博客   Javascript从当初的一个弹窗语言,一...

    marek 评论0 收藏0
  • 剖析JS的原型继承

    摘要:接下来我们来聊一下的原型链继承和类。组合继承为了复用方法,我们使用组合继承的方式,即利用构造函数继承属性,利用原型链继承方法,融合它们的优点,避免缺陷,成为中最常用的继承。 JavaScript是一门面向对象的设计语言,在JS里除了null和undefined,其余一切皆为对象。其中Array/Function/Date/RegExp是Object对象的特殊实例实现,Boolean/N...

    darkerXi 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<