原型/原型链学习笔记

原型对象

什么是原型对象

在JavaScript中,对象由构造函数来新建,每个构造函数内部都有一个prototype 属性,这个属性指向一个对象,包含了由该构造函数创建的实例对象共享的所有属性和所有方法。这个prototype属性的值就是原型对象。

对象的继承机制

由于所有的实例对象共享同一个prototype对象,那么从外界看起来,prototype对象就好像是实例对象的原型,而实例对象则好像"继承"了prototype对象一样。

注意:

  • 原型对象是通过引用来传递的,对象内的属性和方法都是同一个内存地址,可以提高运行效率。

  • 每个创建的实例对象中并没有一份属于自己的原型副本,当修改原型时,与之相关的对象也会继承这一改变。

    function Person(name) {
        this.name = name
    }
    Person.prototype = {
        getName: function() {}
    }
    var p = new Person('hello')
    console.log(p.__proto__ === Person.prototype) // true
    console.log(p.constructor === Object) // true
    p.constructor = Person
    console.log(p.__proto__ === p.constructor.prototype) // true
  • 上面例子显示,修改原型时,实例对象的构造函数不再是原来的构造函数,直接给实例对象的用对象赋值时,实例对象的构造函数指向了根构造函数Object。所以这时候p.constructor === Object ,而不是p.constructor === Person。要想成立,就要用constructor指回来

访问原型对象

_proto_

当使用构造函数新建一个对象后,在这个对象的内部将包含一个指针,这个指针指向构造函数的 prototype 属性的值,即构造函数的原型对象。这个指针在浏览器中为__proto__属性。所以一般情况下:

对象.__proto__ === 对象的构造函数.prototype  

注意:

  • 最好不要使用__proto__这个属性,因为它不是规范中规定的。 可以通过Object.getPrototypeOf()方法来获取对象的原型。

原型链

当访问一个对象的属性时,如果对象内部不存在这个属性,name就会去这个对象的原型对象里去找这个属性,这个原型对象又会有自己的原型,所以就会一直找下去,这就是原型链的具体表现。

原型链上的所有原型都是对象,一般来说都会继承 Object.prototype ,所以这就是新建的对象也能够使用 Object.prototype.toString() 等方法的原因。

构造函数

什么是构造函数

类(class)就是对象的模版。 js不是基于类的,而是基于构造函数(constructor)和原型链(prototype)。

所谓"构造函数",用来初始化新创建对象的函数,内部使用了this变量。

对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。

构造函数被赋予一个prototype属性,该属性指向了实例对象的原型对象。
图片说明

构造函数的特点

  1. 函数体内使用this关键字,代表了所要生成的对象实例
  2. 生成实例对象,必须使用new关键字实例化
  3. 为了与普通函数区别第一个字母通常大写

使用构造函数创建对象的利与弊

:实例对象具有相同的属性和方法,方便调用。

:实例对象调用了相同的方法,占用了内存空间,浪费系统资源。

new关键字

new命令的作用,就是执行构造函数,创建一个用户定义的对象类型的实例对象具有构造函数的内置对象的实例对象

new 关键字会进行如下的操作:

  1. 创建一个空对象,作为将要返回的对象实例
  2. 将这个空对象的原型(__proto__),指向了构造函数的prototype属性
  3. 函数内部的this指向这个新对象
  4. 执行构造函数内的代码(为这个新对象添加属性)。
  5. 判断函数的返回(return)值类型,如果是一个对象,返回return语句指定的对象。否则,就会不管return语句,返回this对象。
    图片说明

constructor属性

返回创建实例对象的构造函数的引用。注意,此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串。

function Foo(name) {
  this.name = name;
}
var f1 = new Foo("Godsb");
console.log("f1:", f1); // constructor继承自原型对象,指向了构造函数的引用
console.log("Foo.prototype:", Foo.prototype);
console.log("f1.constructor === Foo:", f1.constructor === Foo);
console.log(
  "f1.__proto__.constructor === Foo:",
  f1.__proto__.constructor === Foo
);

实例对象f1本身没有constructor属性,于是会从f1的原型f1.__proto__查找constructor属性,也就是Foo.prototype.constructor

图片说明

实例对象

什么是实例对象

由构造函数创建的对象一般被称为实例对象。实例对象将自动引用原型对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用原型对象的。

实例对象会自动含有一个constructor属性,这个constructor是通过继承原型对象继承来的,它指向了f1的构造函数Foo

实例对象__proto__指向构造函数prototype

总结

谁会有prototype和__proto__属性?

  • typeof === object的有__proto__ 除了null和Object.prototype
  • typeof === function的有__proto__prototype
  • null和undefined都没有

prototype

  • prototype这个属性诞生是为了让实例对象之间做到数据共享,减少资源浪费。

  • prototype属性的值是一个对象(又称为原型对象),所有实例对象需要共享的属性和方法,都放在这个对象里面;

  • 那些不需要共享的属性和方法(私有属性/方法),就放在构造函数里面。

_proto_

  • __proto__一般情况指向的是该对象的构造函数的prototypea.constructor.prototype === A.prototype),但还有其他的情况。
  • 主要作用是对象通过__proto__属性来共享构造函数的原型对象(想要传递的属性和方法)

例子

function A(){}
var a = new A()
console.log(a.__proto__ === A.prototype) //true
console.log(a.constructor.prototype === A.prototype); //true
console.log(a.__proto__.__proto__ === A.prototype.__proto__) //true
console.log(a.__proto__.__proto__ === A.prototype.constructor.prototype) // false

依据上面a.__proto__ === A.prototype,那么a.__proto__.__proto__就等同A.prototype.__proto__ ,继续推导得到 A.prototype.constructor.prototype,但结果是false,所以原型链需要基于一定规则才能推导。

原型链的规则

图片说明

  1. Object.prototype.__proto__ === null

    (Object.prototype没有__proto__属性)

  2. Object.__proto__ === Function.prototype

    (Object为构造函数,所有构造函数的__proto__都指向Function.prototype

  3. let a=10 a.__proto__ === Number.prototype

    (基本类型的转换会先把基本类型转换为对应的构造函数)

  4. Object.__proto__ === Function.prototype === Function.__proto__(Function为构造函数)

    Object,Number, Error,Function等等这些函数都是Function创建的,它们的constructor为Function,需要额外注意的是Function.constructor也是Function。

    Object.constructor.prototype  === Function.prototype  // true
    Function.constructor === Function// true
  5. Function.prototype.__proto__ === Object.prototype

    Function.prototype.constructor === Object // false
  6. 自定义函数,默认情况下,有下面的规律:

    function A(){}
    var a = new A();
    //__proto__一般情况指向的是该对象的构造函数的prototype
    console.log(a.__proto__ === A.prototype);//true
    //因为A是一个函数,是Function对象创建的,constructor为Function,由第三条规则可知等于Function.prototype
    console.log(A.__proto__ === Function.prototype); //true
    //A函数的prototype是一个对象,这个对象的构造函数是Object,所以指向Object构造函数的prototype
    //A.prototype.constructor.prototype === Object.prototype
    console.log(A.prototype.__proto__ === Object.prototype);//true
    //A是函数,函数的构造函数是Function
    console.log(A.constructor === Function);//true
  7. 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(除了“null”以外);

  8. 所有的引用类型(数组、对象、函数),都有一个_proto_(隐式原型)属性,属性值指向(完全相等)其构造函数的prototype属性的值(原型对象);

  9. 所有的函数,都有一个prototype(显式原型)属性,属性值是一个原型对象;

  10. 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去__proto__(即它的构造函数的prototype中)寻找。