一篇文章让你彻底了解 JS 原型链

前言

原型是 JavaScript 中一个比较难理解的概念,原型相关的属性也比较多,对象有"[[prototype]]"属性,函数对象有"prototype"属性,原型对象有"constructor"属性。

认识原型(什么是原型)

开始原型的介绍之前,我们首先说说什么是原型?

定义

在 JavaScript 中,原型也是一个对象,通过原型可以实现对象的属性继承,JavaScript 的对象中都包含了一个"[[prototype]]"内部属性,这个属性所对应的就是该对象的原型。
"[[prototype]]"作为对象的内部属性,是不能被直接访问的。所有为了方便查看一个对象的原型,Chrome 等大型浏览器厂商提供了"__proto__"这个非标准的访问器(ECMA 引入了标准对象原型访问器 "Object.getPrototype(Object)")

实例分析
    function Animal(name, type) {
        this.name = name;
        this.type = type;
        this.getInfo = function(){
            console.info("当前动物属性==>",this.name + 'is' + this.type)
        }
    }
    let dog = new Animal("狗", "犬科哺乳动物") // 当前动物属性==> 狗is犬科哺乳动物

Step-->1: 查看对象 dog 的原型

    console.info("__proto__",dog.__proto__);
    // __proto__ Objectconstructor: ƒ Animal(name, type)__proto__:
    console.info("constructor=====>",dog.constructor)
     //constructor=====> ƒ Animal(name, type) {
     //       this.name = name;
     //       this.type = type;
     //       this.getInfo = function(){
    //      console.info("当前动物属性==>",this.name + 'is' +     this.type)
结果分析
  • "Animal{}"对象就是 dog 的原型,通过 Chrome 展开可以看到,"Animal{}"作为一个原型对象,也有"__proto__"属性(对应原型的原型)
  • 在这段代码中,还用到"constructor"属性。在 JavaScript的对象原型对象中。还包含一个"constructor"属性,这个属性对应创建所有指向该原型的实例的构造函数
    // 拓展 可以判断一个对象是不是数组类型
    function isArray(arr){
        return arr.constructor.toString().indexOf("Array") > -1;
    }

*在这里,dog 对象本身没有"constructor"这个属性,但是通过原型链查找,这到了 dog 原型(dog.__proto__)的 "constructor"属性,并找到了Animal函数

Step-->2: 查看对象 dog 的原型(dog.__proto__)的原型

既然 dog 的原型"Animal{}"也是一个对象,那么我们就同样可以来查看 dog 的原型(dog.__proto__)的原型

    console.info(dog.__proto__ === Animal.prototype)
    console.info(Animal.prototype.__proto__)
    console.info(Animal.prototype.constructor)
    console.info(Animal.prototype.constructor === Animal)
结果分析
  • 首先看"dog.__proto__ === Animal.prototype",在 JavaScript 中,每个函数都有一个 prototype 属性,当一个函数被用作构造函数来创建实例时,该函数的 prototype 属性值将被作为原型赋值给所有对象实例(也就是设置实例的__proto__属性),也就是说,所有实例的原型引用的是函数的 prototype 属性。了解了构造函数的 prototype 属性之后。一定就会明白为什么第一句是 true 了
  • 当通过"Animal.prototype.__proto__"语句获取dog 对象原型的原型时候,将得到"Object{}"对象,后面将会看到所有对象的原型都将追溯到"Object{}"对象。
  • 对于原型对象"Animal.prototype"对象的"constructor"根据前面的介绍,将对应 Animal 函数本身。

通过上面可以看到,"Animal.prototype"对象和 Animal 函数对象通过"constructor"和 "prototype"属性实现了相互引用

Step-->3: 查看对象 Object 的原型

通过前一部分可以看到,will的原型的原型是"Object{}"对象。实际上在JavaScript中,所有对象的原型都将追溯到"Object {}"对象。
下面通过一段代码看看"Object {}"对象:

    console.log(Animal.prototype.__proto__ === Object.prototype);
    console.log(typeof Object);
    console.log(Object);
    console.log(Object.prototype);
    console.log(Object.prototype.__proto__);
    console.log(Object.prototype.constructor);
结果分析
  • Object对象本身是一个函数对象。
  • 既然是Object函数,就肯定会有prototype属性,所以可以看到"Object.prototype"的值就是"Object {}"这个原型对象。
  • 反过来,当访问"Object.prototype"对象的"constructor"这个属性的时候,就得到了Obejct函数。
  • 另外,当通过"Object.prototype.__proto__"获取Object原型的原型的时候,将会得到"null",也就是说"Object {}"原型对象就是原型链的终点了。

Step-->4: 查看对象Function的原型

在上面的例子中,Animal是一个构造函数,在JavaScript中函数也是对象,所以,我们也可以通过"__proto__"属性来查找Animal函数对象的原型。

    console.log(Animal.__proto__ === Function.prototype);
    console.log(Animal.constructor === Function)
    console.log(typeof Function);
    console.log(Function);
    console.log(Function.prototype);
    console.log(Function.prototype.__proto__);
    console.log(Function.prototype.constructor);
结果分析
  • 在JavaScript中有个Function对象(类似Object),这个对象本身是个函数;所有的函数(包括Function,Object)的原型(__proto__)都是"Function.prototype"。
  • Function对象作为一个函数,就会有prototype属性,该属性将对应"function () {}"对象。
  • Function对象作为一个对象,就有"__proto__"属性,该属性对应"Function.prototype",也就是说,"Function.__proto__ === Function.prototype"
  • 对于Function的原型对象"Function.prototype",该原型对象的"__proto__"属性将对应"Object {}"
对比 prototype 和 proto

对于"prototype"和"__proto__"这两个属性有的时候可能会弄混,"Person.prototype"和"Person.__proto__"是完全不同的。

在这里对"prototype"和"__proto__"进行简单的介绍:

  • 对于所有的对象,都有__proto__属性,这个属性对应该对象的原型
  • 对于函数对象,除了__proto__属性之外,还有prototype属性,当一个函数被用作构造函数来创建实例时,该函数的prototype属性值将被作为原型赋值给所有对象实例(也就是设置实例的__proto__属性)

对于上图的总结如下:
  • 所有的对象都有"__proto__"属性,该属性对应该对象的原型
  • 所有的函数对象都有"prototype"属性,该属性的值会被赋值给该函数创建的对象的"__proto__"属性
  • 所有的原型对象都有"constructor"属性,该属性对应创建所有指向该原型的实例的构造函数
  • 函数对象和原型对象通过"prototype"和"constructor"属性进行相互关联
  • 因为每个对象和原型都有原型,对象的原型指向对象的父,而父的原型又指向父的父,这种原型层层连接起来的就构成了原型链。
hasOwnProperty

"hasOwnProperty"是"Object.prototype"的一个方法,该方法能判断一个对象是否包含自定义属性而不是原型链上的属性,因为"hasOwnProperty" 是 JavaScript 中唯一一个处理属性但是不查找原型链的函数。

相信你还记得文章最开始的例子中,通过dog我们可以访问"constructor"这个属性,并得到dog的构造函数Animal。这里结合"hasOwnProperty"这个函数就可以看到,dog对象并没有"constructor"这个属性。

从下面的输出可以看到,"constructor"是dog的原型(dog.__proto__)的属性,但是通过原型链的查找,dog对象可以发现并使用"constructor"属性。

"hasOwnProperty"还有一个重要的使用场景,就是用来遍历对象的属性。

function Person(name, age){
    this.name = name;
    this.age = age;
}

Person.prototype.getInfo = function(){
    console.log(this.name + " is " + this.age + " years old");
};


var will = new Person("Will", 28);

for(var attr in will){
    console.log(attr);
}
// name
// age
// getInfo

for(var attr in will){
    if(will.hasOwnProperty(attr)){
        console.log(attr);
    }
}
// name
// age

总结

本文介绍了JavaScript中原型相关的概念,对于原型可以归纳出下面一些点:

所有的对象都有"[[prototype]]"属性(通过__proto__访问),该属性对应对象的原型
所有的函数对象都有"prototype"属性,该属性的值会被赋值给该函数创建的对象的"__proto__"属性
所有的原型对象都有"constructor"属性,该属性对应创建所有指向该原型的实例的构造函数
函数对象和原型对象通过"prototype"和"constructor"属性进行相互关联
通过这些介绍,相信一定可以对原型有个清晰的认识。

你可能感兴趣的:(javascript)