首先,JavaScript 并没有其它基于类的语言所定义的“方法“,只有函数(function)。在 JavaScript 里,任何函数都可以添加到对象上作为对象的属性。当继承的函数被调用时,this
指向的是当前继承的对象,而不是继承的函数所在的原型对象。
在 JavaScript 中,构造器其实就是一个普通的函数,当使用new
操作符来作用这个函数时,他就可以被称为构造函数。
要创建一个新实例对象,需要使用new
操作符,以这种方式调用构造函数实际上会经历以下4个步骤:
这里有篇很好的文章解释了new的处理过程以及原型继承的相关内容:Javascript – How Prototypal Inheritance really works
function New(f) {
var n = {'__proto__': f.prototype}; // 步骤1
return function () {
f.apply(n, arguments); // 步骤2&3
return n; // 步骤4
}
}
可以创建对象来证明以上代码有效:
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.print = function () {
console.log(this.x, this.y);
}
var p = New(Point)(10, 20);
p.print(); // expected output: 10 20
看起来,new
语句基本就是以上函数的一个语法糖。
构造函数的返回值
当使用new
关键字时,构造函数一定会返回一个对象,默认返回的是指向this
的对象,如果不给this
添加属性,则返回一个“空”的对象。
注意,即使不写return
,也会隐式返回指向this
的对象。当然也可以返回任意的其它对象,如果写了return false
或者return "123"
之类的非对象,会被忽略,依然返回指向this
的对象。
var ObjectCreator = function () {
this.name = "This name";
var that = {};
that.name = "That name";
return that;
};
var o = new ObjectCreator();
console.log(o.name); // expected output: That name
潜在问题
在《Javascript语言精粹》B.11(鸡肋)里,作者Douglas Crockford提倡更好的策略是不使用new
,原因是因为如果忘记使用new
,会把this
绑定到全局对象,造成全局污染,并且没有警告。但是不使用new
,就需要手动指定prototype
,并完成步骤1~4,如果因为某个语法一旦写错会出问题而弃用它,有些违背语法创立的初衷。诚然,造个轮子肯定能解决一些问题,并且由于只使用基础语法,看起来更强壮,但是程序员那么懒,谁喜欢多写代码呢?
prototype 和 __proto__
每一个函数都有一个prototype
属性,每一个由该函数实例化的对象都包含一个隐时指针(__proto__
)指向该函数的prototype
属性。所以,prototype
属性体现的是构造函数和实例之间的关系。
而__proto__
,在 ES6 之前至少在规范中是没有这个属性的,但是浏览器厂商自行实现了,在 ES6 规范中,它其实是Object.prototype
对外暴露的一个访问器属性,具有 [[Get]] 和 [[Set]] 的方法,用来操作对象的原型。我们在使用对象字面量创建对象的时候,__proto__
属性能够设置对象的prototype
,可以用来作为Object.create()
的替代方案。
function A() {}
var a = new A();
a.__proto__ === A.prototype; // expected output: true
但是,函数的prototype
和__proto__
属性没有关系,因为
A.__proto__ === Function.prototype; // expected output: true
而Object.create(proto[, propertiesObject])
可以理解为不执行构造函数的new
。