1. 原型链类
1.1 创建对象有几种方法
1.1.1 字面量
1 | var o1 = {name: 'o1'}; |
结果:
1 | {name: "o1"} |
1.1.2 显式的构造函数
1 | var M = function(){this.name='o3'}; |
结果:
1 | M {name: "o3"} |
注:任何函数被 new 运算符计算以后,都可以被称之为构造函数。
1.1.3 Object.create
1 | var P = {name: 'o4'}; |
结果:1
{}
解释:
Object.create() 方法创建的对象是用原型链来连接的,即把参数(对象)作为原型对象的。1
o4.__proto__ // p
1.2 原型链、构造函数、实例和原型链
- 构造函数通过 new 运算符得到实例
- 用 new 运算符得到的函数为构造函数,相反则是普通函数。任何函数都可以被当作构造函数
- 函数都有一个 prototype 属性(声明函数时自动添加),该属性指向原型对象
- 原型对象通过 constructor(构造器) 来获得被引用的构造函数
举个栗子:1
2var M = function(){this.name='o3'};
var o3 = new M();
构造器:M
实例:o3
1 | M.prototype.constructor === M |
输出: true
1 | o3.__proto__ === M.prototype |
输出: true
原型链:从实例往上找,找到它的原型对象,原型对象再向上找到原型对象,直到 Object 的原型对象。这一过程是通过 __proto__
向上找的。
多个实例共用一个原型对象,原型对象上的方法被实例共享,举个栗子:
1 | var M = function(){ |
如果实例的某个方法,如果实例本身找不到这个方法时,将会沿着原型链,从它的原型对象上找,如果找到,则停止查找,返回。反之,继续向上一级原型对象上找,直到 Object。
注: 函数才会有 prototype
,只有对象(函数既是函数也是对象)才有 proto。
1 | M.__proto__ === Function.prototype; |
输出: true
1.3 instanceof 的原理
instanceof 实际判断的是实例对象的 __proto__
和构造函数的 prototype
是不是同一个引用(原型对象),如果是,则返回 true,反之返回 false。判断时,但原型链上的原型对象都会返回 true。
举个栗子:
1 | var M = function(){ |
因为
1 | o3.__proto__ === M.prototype // true |
问:如果 a 继承 b,b 继承 c,a 生成的实例 instanceof b 或 c 都会返回 true,如何判断它是哪个的实例呢?
答: constructor
1 | o3.__proto__.constructor === M; // true |
1.4 new 运算符
new 运算符是用于执行构造函数,创建新实例的。
一些碎碎念:
任何函数都可以通过
函数名()
的方式被执行,但这种执行方式 this 代码不做任何操作,只执行可执行的代码。
以下模拟了新对象被创建的过程(对应上图的是三个步骤):
1 | var new2 = function(func){ |
举个栗子:
1 | function Foo(){ |
上边的栗子中,由于没有返回对象,因此 new 出来的结果就是 this 指定为新实例的上下文,对象包含 baz
和 bar
属性。
同时,由于新实例继承了 Foo.prototype,所以实例的原型对象上会有 foobar 属性。
如果改成下边这样:
1 | function Foo(){ |
由于构造函数中返回了对象,那么返回的对象会代替成为新实例。因此在新实例中并不会包含属性 baz 和 bar,最终的返回结果是:
1 | foo.baz; // undefined |
特别要注意的一点。新对象被创建继承的是 foo.prototype,而不是 foo!因此,构造函数本身的属性是不会出现在 new 出的对象中的,但会出现在实例对象的构造函数上。
举个栗子:
1 | function Foo(){ |
创建出的新实例是没有 baz 属性的,只有构造器中的 this 属性和方法会出现在新实例中。
我们把 foo 对象打印出来,会发现 baz 出现在 foo.__proto__.constructor
上