说说JavaScript 中的new吧
在其他语言中,new操作符都是用来实例化创建一个对象的,JavaScript 中同样如此,但是它又有一些不同。为了说清楚这个问题我们先来看一下JavaScript 中的类、原型、原型链、继承这些概念吧。
JavaScript 中没有传统类的概念,它的类就是一个方法,也就是说JavaScript 中是通过function来定义类的。比如我们可以这样子来定义一个类。
function Person(name, age) { this.name = name; this.age = age; this.sing = function() { alert(this.name) } }
那么,有类了就一定存在着继承,而js的继承跟传统的类继承模型不同,它是使用 prototype 原型模型。这经常被当作是 JavaScript 的缺点被提及,其实基于原型的继承模型比传统的类继承还要强大。 实现传统的类继承模型是很简单,但是实现 js中的原型继承则要困难的多。JavaScript 使用原型链的继承方式。我们来看下这个例子。
function Foo() { this.value = 42; } Foo.prototype = { method: function() {} }; function Bar() {} // 设置Bar的prototype属性为Foo的实例对象 Bar.prototype = new Foo(); Bar.prototype.foo = 'Hello World'; // 修正Bar.prototype.constructor为Bar本身 Bar.prototype.constructor = Bar; var bar= new Bar() // 创建Bar的一个新实例 // 原型链 test [Bar的实例] Bar.prototype [Foo的实例] { foo: 'Hello World' } Foo.prototype {method: ...}; Object.prototype {toString: ... /* etc. */};
这里我觉得有必要来说一下原型、原型链和实例之间的关系。JavaScript中,每个函数都有一个prototype属性,这是一个指针,指向了这个函数的原型对象。这个对象包含这个函数创建的实例的共享属性和方法。也就是说原型对象中的属性和方法是所有实例共享。而这个原型对象有一个constructor属性,指向了该构造函数。每个通过该构造函数创建的对象都包含一个指向原型对象的内部指针__proto__。 原型链作为实现继承的主要方法,其基本思想是:让原型对象等于另一个类型的实例,这样原型对象将包含一个指向另一个原型的指针,相应的,另一个原型中也包含着一个指向另一个构造函数的指针,假如另一个原型又是另一个类型的实例,如此层层递进,就构成了实例与原型的链条,这个链条就称之为原型链.
回到主题上,但我们使用new Foo()创造出一个示例这过程中它做了写什么事呢?
1、创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
2、属性和方法被加入到 this 引用的对象中。
3、新创建的对象由 this 所引用,并且最后隐式的返回 this 。
2、属性和方法被加入到 this 引用的对象中。
3、新创建的对象由 this 所引用,并且最后隐式的返回 this 。
var foo = {}; foo.__proto__ = Object.prototype; Object.call(foo);
同理,当我们new Bar()的时候,也是创建了一个空对象,并且 this 变量引用该对象,同时,Bar.prototype = new Foo();然后bar.__proto__ = Foo.prototype,最后,由Foo.call(bar)隐式的返回了this; 其中,Bar.prototype = new Foo()会使得Bar.prototype.constructor == Foo,所以这里我们要使用Bar.prototype.constructor = Bar;把Bar自身的构造函数修正过来。
到这里我们可以发现,JavaScript中的new操作与其说是新建了一个示例,更不如说做是由一个工厂模式产生出来一个实例。
我们再来看个例子,这个是汤姆大叔博客中的例子。
function A() {} A.prototype.x = 10; var a = new A(); alert(a.x); // 10 – 从原型上得到 // 设置.prototype属性为新对象 // 为什么显式声明.constructor属性将在下面说明 A.prototype = { constructor: A, y: 100 }; var b = new A(); // 对象"b"有了新属性 alert(b.x); // undefined alert(b.y); // 100 – 从原型上得到 // 但a对象的原型依然可以得到原来的结果 alert(a.x); // 10 - 从原型上得到 function B() { this.x = 10; return new Array(); } // 如果"B"构造函数没有返回(或返回this) // 那么this对象就可以使用,但是下面的情况返回的是array var b = new B(); alert(b.x); // undefined alert(Object.prototype.toString.call(b)); // [object Array]
这里有两个主要特点:
- 首先,新创建对象的原型是从当前时刻函数的prototype属性获取的(这意味着同一个构造函数创建的两个创建对象的原型可以不同是因为函数的prototype属性也可以不同)。
- 其次,正如我们上面提到的,如果在对象初始化的时候,[[Call]]返回的是对象,这恰恰是用于整个new操作符的结果
文中例子来源于:http://bonsaiden.github.io/JavaScript-Garden/zh/