一、创建对象的几种方法

1.字面量
字面量var a = {}其实是var a = new Object()的语法糖,推荐使用前者
var a = [] 其实是var a = new Array()的语法糖,推荐使用前者
function Foo(){} 其实是var Foo = new Function()的语法糖,推荐使用前者
2.构造函数

  1. var Foo = function(name) {
  2. this.name = name
  3. }
  4. var f = new Foo()


3.Object.create(Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的proto

  1. var P = {name:'o3'};
  2. var o3 = Object.create(P);

对象规则:

  • 所有引用类型都有 proto属性(隐式原型),一个普通的属性值
  • 所有的函数,都有一个prototype属性(显式原型),属性值也是一个普通的对象
  • 所有的引用类型(数组,对象,函数)proto属性值指向它的构造函数的prototype属性值
  • 当试图得到一个引用类型(数组,对象,函数)的某个属性时,如果这个引用类型本身没有这个属性,那么会去它的proto(即它的构造函数的prototype)中寻找

    二、prototype

    每个函数都有一个 prototype 属性。函数的prototype就是一个对象,这个就是调用该构造函数时创建的实例所指的原型。可以这样理解,每个js对象生成的时候就会与之关联另一个特殊的对象,这个特殊的对象就是原型,每个创建的对象都会从原型“继承”属性。
    让我们用一张图表示构造函数和实例原型之间的关系:
    image.png

上图中我们用 Object.prototype 表示实例原型。
实例和实例原型的关系我们该怎么表示呢?下面就要用到另外一个对象 proto

三、proto

上面我们有提到每个对象都有这个属性,叫proto,这个属性会指向该对象的(实例)原型。
代码表示如下:

  1. function Person() {
  2. }
  3. var person = new Person();
  4. console.log(person.__proto__ === Person.prototype); // true

于是我们更新下关系图:
image.png
既然实例对象和构造函数都可以指向原型,那么原型是否有属性指向构造函数或者实例呢?

四、constructor

指向实例倒是没有,因为一个构造函数可以生成多个实例,但是原型指向构造函数倒是有的,这就要讲到第三个属性:constructor,每个原型都有一个 constructor 属性指向关联的构造函数。
为了验证这一点,我们可以尝试:

  1. function Person() {
  2. }
  3. console.log(Person === Person.prototype.constructor); // true

所以再更新下关系图:
image.png
综上我们已经得出:

  1. function Person() {
  2. }
  3. var person = new Person();
  4. console.log(person.__proto__ == Person.prototype) // true
  5. console.log(Person.prototype.constructor == Person) // true
  6. // 顺便学习一个ES5的方法,可以获得对象的原型
  7. console.log(Object.getPrototypeOf(person) === Person.prototype) // true

五、实例与原型

当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。

六、原型的原型

在前面,我们已经讲了原型也是一个对象,既然是对象,我们就可以用最原始的方式创建它,那就是:

  1. var obj = new Object();
  2. obj.name = 'Kevin'
  3. console.log(obj.name) // Kevin

其实原型对象就是通过 Object 构造函数生成的,结合之前所讲,实例的 proto 指向构造函数的 prototype ,所以我们再更新下关系图:
image.png
七、原型链
那 Object.prototype 的原型呢?

  1. console.log(Object.prototype.__proto__ === null) // true

所以 Object.prototype.proto 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。
所以查找属性的时候查到 Object.prototype 就可以停止查找了。
最后一张关系图也可以更新为:
image.png
图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。

补充

真的是继承吗?
继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。

原型的优点

1.公用原型上的对象(方法),节约了堆内存

  1. function Fn() {
  2. this.a = {name: 'june'}
  3. }
  4. let f1 = new Fn()
  5. let f2 = new Fn()
  6. f1.a === f2.a // false
  7. function Fn() {
  8. }
  9. Fn.prototype.a = {name: 'june'}
  10. let f1 = new Fn()
  11. let f2 = new Fn()
  12. f1.a === f2.a // true