深入理解JavaScript系列(18):面向对象编程之E(6)


function A() {
  // 更新新创建的对象
  this.x = 10;
  // 但返回的是不同的对象
  return [1, 2, 3];
}
 
var a = new A();
console.log(a.x, a); undefined, [1, 2, 3]

引用15章函数——创建函数的算法小节,我们可以看到该函数是一个原生对象,包含[[Construct]] ]和[[Call]] ]属性以及显示的prototype原型属性——未来对象的原型(注:NativeObject是对于native object原生对象的约定,在下面的伪代码中使用)。

复制代码 代码如下:


F = new NativeObject();
 
F.[[Class]] = "Function"
 
.... // 其它属性
 
F.[[Call]] = <reference to function> // function自身
 
F.[[Construct]] = internalConstructor // 普通的内部构造函数
 
.... // 其它属性
 
// F构造函数创建的对象原型
__objectPrototype = {};
__objectPrototype.constructor = F // {DontEnum}
F.prototype = __objectPrototype

[[Call]] ]是除[[Class]]属性(这里等同于"Function" )之外区分对象的主要方式,因此,对象的内部[[Call]]属性作为函数调用。 这样的对象用typeof运算操作符的话返回的是"function"。然而它主要是和原生对象有关,有些情况的实现在用typeof获取值的是不一样的,例如:window.alert (...)在IE中的效果:

复制代码 代码如下:


// IE浏览器中 - "Object", "object", 其它浏览器 - "Function", "function"
alert(Object.prototype.toString.call(window.alert));
alert(typeof window.alert); // "Object"

内部方法[[Construct]]是通过使用带new运算符的构造函数来激活的,正如我们所说的这个方法是负责内存分配和对象创建的。如果没有参数,调用构造函数的括号也可以省略:

复制代码 代码如下:


function A(x) { // constructor А
  this.x = x || 10;
}
 
// 不传参数的话,括号也可以省略
var a = new A; // or new A();
alert(a.x); // 10
 
// 显式传入参数x
var b = new A(20);
alert(b.x); // 20


我们也知道,构造函数(初始化阶段)里的shis被设置为新创建的对象 。

让我们研究一下对象创建的算法。

对象创建的算法

内部方法[[Construct]] 的行为可以描述成如下:

复制代码 代码如下:


F.[[Construct]](initialParameters):
 
O = new NativeObject();
 
// 属性[[Class]]被设置为"Object"
O.[[Class]] = "Object"
 
// 引用F.prototype的时候获取该对象g
var __objectPrototype = F.prototype;
 
// 如果__objectPrototype是对象,就:
O.[[Prototype]] = __objectPrototype
// 否则:
O.[[Prototype]] = Object.prototype;
// 这里O.[[Prototype]]是Object对象的原型
 
// 新创建对象初始化的时候应用了F.[[Call]]
// 将this设置为新创建的对象O
// 参数和F里的initialParameters是一样的
R = F.[[Call]](initialParameters); this === O;
// 这里R是[[Call]]的返回值
// 在JS里看,像这样:
// R = F.apply(O, initialParameters);
 
// 如果R是对象
return R
// 否则
return O

请注意两个主要特点:

1.首先,新创建对象的原型是从当前时刻函数的prototype属性获取的(这意味着同一个构造函数创建的两个创建对象的原型可以不同是因为函数的prototype属性也可以不同)。
2.其次,正如我们上面提到的,如果在对象初始化的时候,[[Call]]返回的是对象,这恰恰是用于整个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]


让我们来详细了解一下原型

原型

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wgfgzd.html