1. 构造函数

构造函数实际上就是一个函数,但是为了区分普通函数和构造函数,常常将构造函数的首字母大写。

构造函数和普通函数的区别在于:直接调用的函数是普通函数,使用new生成实例的函数是构造函数。

每个构造函数都有一个prototype属性,

实例对象可通过它的constructor访问到它的构造函数:obj.constructor == Con.prototype.constructor == Con

2. 原型

每个对象都有一个原型对象,对象以原型对象为模板,从原型继承属性和方法。准确的说,这些属性和方法定义在对象实例的构造函数的prototype属性上,prototype属性的值是一个对象,我们期望被原型链下游的对象继承的属性和方法都被储存在其中

每个实例对象都从原型继承了一个constructor属性,该属性指向了用于构造此实例对象的构造函数。此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串!

  • 原型对象是一个内部对象,没有官方的方法用于直接访问一个对象的原型对象
  • 原型链中的“链接”被定义在一个内部属性中,用[[prototype]]表示
  • __proto__则是现代浏览器提供的一个属性,用于访问对象的原型,但不推荐使用!

注意:

  • 如果你关心性能的问题,就不要在一个对象中修改它的[[prototype]]属性!例如:obj.__proto__ = ...或者是Object.setPropertyOf()都不推荐!
  • 建议以Object.getPropertyOf()代替Object.prototype.__proto__

每个实例都有一个__proto__属性,实例对象可以通过__proto__访问到它的原型对象;每个构造函数都有一个prototype属性p.__proto__和Parent.prototype都指向同一个对象:原型Parent.prototype。Parent和Parent.prototype之间存在一个循环引用。三者关系如下图所示:

需要说明的是:__proto__和prototype不是一回事,对象上并没有prototype属性! 

3. 不同方法创建对象

使用字面量的方式创建对象:

var obj = { a: 2 }  // 原型链:o --> Object.prototype --> null
var arr = [1, 2, 3]  // 原型链:arr --> Array.prototype --> Object.prototype --> null
var fun = function () { }  // 原型链:fun --> Function.prototype --> Object.prototype --> null

使用构造器new一个对象:var obj = new fun()

  • 如果fun是JavaScript提供的内建构造器函数之一:Function / Boolean / Object / Array / Date / Number / String,那么obj的[[prototype]]值是fun.prototype
  • 如果fun是自定义的构造函数,则obj的值就是该构造器的prototype属性
let obj = new Function() // obj --> Function.prototype --> Object.prototype --> null

function Person() { }
var person1 = new Person() // person1 --> Person.prototype --> Object.prototype --> null

function Person() {
    return {}
}
var person2 = new Person() // person2 --> Object.prototype --> null

使用Object.create()创建对象

ECMAScript5中引入一个新方法:Object.create()。可以调用该方法创建一个新对象,新对象的原型就是create里的第一个参数

 

var Animal = { age: 1 }
var son = Object.create(Animal);
console.log(son.age) // 输出1,继承自Animal的age属性
// 原型链:son --> Animal.prototype --> Object.prototype --> null

使用class关键字创建对象:

ECMAScript6引入了一套新的关键字用来实现class:class、constructor、static、extends、super。下例是用MDN上的例子,ES6的这部分我还没有仔细的研究........尴尬=_=

"use strict";

class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

class Square extends Polygon {
  constructor(sideLength) {
    super(sideLength, sideLength);
  }
  get area() {
    return this.height * this.width;
  }
  set sideLength(newLength) {
    this.height = newLength;
    this.width = newLength;
  }
}

var square = new Square(2);

 

关于性能的问题:

在原型链上查找属性比较耗时,如果访问的属性不存在时会遍历整个原型链。要检查对象是否具有自己定义的属性而不是其原型链上的额某个属性时,可以使用hasOwnProperty方法

4. 原型链

每个对象都有一个原型对象,通过__proto__属性指针指向上一个原型,继承原型的属性和方法,同时该原型也有它的原型对象,这样一层一层最终指向null。这个关系被称作原型链

因此当我们找一个对象的属性和方法时不仅仅会在该对象上找,还会去找它的原型对象上的属性和方法,一直向上找直到找到名称匹配的属性和方法或者是直到null。如下图所示:

 

 那么需要明确一个问题:原型链的实现是基于__proto__的!而不是prototype

所以这里随之而来带来一个问题:如果动态的设置函数的prototype是会影响后续的原型链指向的。看下例:

        function A() {
            return 'a'
        }
        A.prototype.method = function () {
            return 'method A'
        }

        function B() {
            return 'b'
        }
        B.prototype.method = function () {
            return 'method B'
        }

        console.dir(A.prototype)  // Object
        A.prototype = B;  // 将A.prototype指向B,而B是个函数
        var obj = new A();
        console.dir(A.prototype)   // f B()
        obj.method()  // Uncaught TypeError: obj.method is not a function

 console.dir输出结果如下:

可以看到,如果直接输出A.prototype,则得到Object;但是当执行A.prototype = B 之后,A.__proto__是指向一个匿名函数f(),A.__proto__.__proto__才指向Object。A.prototype = B这个操作实际上是改变了A的原型链指向并不会执行B的原型!!!所以不要这么写!!!

那如果我们想让A继承B该怎么写呢?换句话说,那当实现原型链继承的时候应该如何写呢?

5. 原型链继承

原型链继承的本质:重写原对象,代之以一个新的实例。也就是说,原型实际上会变成另一个构造函数的一个实例~

以上题为例,我们需要将上例中的A.prototype = B指向B的一个实例,即A.prototype = new B(),这样就可以实现原型链继承例,A也能够继承B原型上的所有属性和方法。上例的输出结果如下:

 

这时,执行B.method()也可以得到结果了。

原型链继承的问题:

  • 当原型属性上有引用类型的值被实例操作时,这个操作会影响到多个实例,因为原型上的属性会被所有的实例共享!
  • 从上一个截图可以看到A.prototype = new B()执行后,A.prototype上丢失了constructor属性,解决办法:自己重写constructor属性:A.prototype.constructor = B,将其指向B!

        

  • 如果要使用原型链继承,并且想要给子类型添加新的方法或属性时,必须将添加操作放在原型替换操作之后,即在A.prototype = new B()之后。

  • 属性遮蔽问题:此时如果给A添加一个新方法newMethod并且在A的父级B上有一个同名的方法newMethod,那么在调用该方法时是调用A上的newMethod,而不是调用B上的这个方法!如果就想要调用B上的newMethod怎么办呢?可以使用__proto__实现:obj.__proto__.__proto__.newMethod()

6. Object.prototype/Function.prototype/function Function/function Object

Object.prototype

  • Object.prototype是Object的原型,它是一个对象但不是通过Object构造函数创建的,是ECMAScript规范创造的一个对象
  • 不考虑null的情况下,它是原型链的顶端
  • 所有原型对象的__proto__都指向Object.prototype
  • Object.prototype.__proto__指向的是null

Function.prototype

  • 它是一个函数(对象),但是它没有prototype属性,因为:Function.prototype.prototype输出undefined
  • Function.prototype.__proto__指向的是Object.prototype
console.log(Function.prototype) // ƒ () { [native code] }
console.log(Object.__proto__ == Function.prototype)  // true
console.log(Object.__proto__ == Object.prototype)   // false

function Object()

  • Object作为构造函数出现时,生成的对象实例的__proto__指向Object.prototype。
  • Object.__proto__指向的是Function.prototype
console.log(Object.__proto__)  // ƒ () { [native code] }
console.log(Object.__proto__ == Function.prototype)  // true
console.log(Object.__proto__ == Object.prototype)   // false

function Function()

  • Function构造函数是一个对象,它的__proto__指向Function.prototype
  • Function是作为众多function出来的函数的基类,因此所有的函数的__proto__都指向Function.prototype,包括构造函数Function()和Object()的__proto__

QUESTION:关于Function对象是不是由Function构造函数创建出来的?

参考:

第5期 原型prototype

对象原型

从探究Function.__proto__===Function.prototype过程中的一些收获

 

下面给出有关原型链的一张万能图!