js进阶之构造函数、原型链

136 阅读4分钟

什么是构造函数

constructor 返回创建实例对象时构造函数的引用。此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串。

function Parent(age) {
    this.age = age;
}

var p = new Parent(50);
p.constructor === Parent; // true
p.constructor === Object; // false

构造函数本身就是一个函数,与普通函数没有任何区别,不过为了规范一般将其首字母大写。构造函数和普通函数的区别在于,使用 new 生成实例的函数就是构造函数,直接调用的就是普通函数。

那是不是意味着普通函数创建的实例没有 constructor 属性呢?不一定。

// 普通函数
function parent2(age) {
    this.age = age;
}
var p2 = parent2(50);
// undefined

// 普通函数
function parent3(age) {
    return {
        age: age
    }
}
var p3 = parent3(50);
p3.constructor === Object; // true

Symbol 是构造函数吗

MDN 是这样介绍 Symbol 的:

The Symbol() function returns a value of type symbol, has static properties that expose several members of built-in objects, has static methods that expose the global symbol registry, and resembles a built-in object class but is incomplete as a constructor because it does not support the syntax "new Symbol()". Symbol 是基本数据类型,但作为构造函数来说它并不完整,因为它不支持语法 new Symbol(),Chrome 认为其不是构造函数,如果要生成实例直接使用 Symbol() 即可。(来自 MDN)

new Symbol(123); // Symbol is not a constructor 

Symbol(123); // Symbol(123)

虽然是基本数据类型,但 Symbol(123) 实例可以获取 constructor 属性值。

var sym = Symbol(123);
console.log( sym );
// Symbol(123)

console.log( sym.constructor );
// ƒ Symbol() { [native code] }

这里的 constructor 属性来自哪里?其实是 Symbol 原型上的,即 Symbol.prototype.constructor 返回创建实例原型的函数, 默认为 Symbol 函数。

function Type() { };
var    types = [1, "muyiy", true, Symbol(123)];

for(var i = 0; i < types.length; i++) {
    types[i].constructor = Type;
    types[i] = [ types[i].constructor, types[i] instanceof Type, types[i].toString() ];
};

console.log( types.join("\n") );
// function Number() { [native code] }, false, 1
// function String() { [native code] }, false, muyiy
// function Boolean() { [native code] }, false, true
// function Symbol() { [native code] }, false, Symbol(123)

为什么呢?因为创建他们的是只读的原生构造函数(native constructors),这个例子也说明了依赖一个对象的 constructor 属性并不安全。

模拟实现 new

说到这里就要聊聊 new 的实现了,实现代码如下。

function create() {
    // 1、创建一个空的对象
    var obj = new Object(),
    // 2、获得构造函数,同时删除 arguments 中第一个参数
    Con = [].shift.call(arguments);
    // 3、链接到原型,obj 可以访问构造函数原型中的属性
    Object.setPrototypeOf(obj, Con.prototype);
    // 4、绑定 this 实现继承,obj 可以访问到构造函数中的属性
    var ret = Con.apply(obj, arguments);
    // 5、优先返回构造函数返回的对象
    return ret instanceof Object ? ret : obj;
};

原型

原型链

每个对象拥有一个原型对象,通过 proto 指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null。这种关系被称为原型链 (prototype chain),通过原型链一个对象会拥有定义在其他对象中的属性和方法。

我们看下面一个例子

function Parent(age) {
    this.age = age;
}

var p = new Parent(50);
p.constructor === Parent; // true

这里 parent.constructor 指向 Parent,那是不是意味着 parent 实例存在 constructor 属性呢?并不是。

我们打印下 parent 值就知道了。

由图可以看到实例对象 p 本身没有 constructor 属性,是通过原型链向上查找 proto ,最终查找到 constructor 属性,该属性指向 Parent。

function Parent(age) {
    this.age = age;
}
var p = new Parent(50);

p;    // Parent {age: 50}
p.__proto__ === Parent.prototype; // true
p.__proto__.__proto__ === Object.prototype; // true
p.__proto__.__proto__.__proto__ === null; // true

小结

Symbol 作为构造函数来说并不完整,因为不支持语法 new Symbol(),但其原型上拥有 constructor 属性,即 Symbol.prototype.constructor。

引用类型 constructor 属性值是可以修改的,但是对于基本类型来说是只读的,当然 null 和 undefined 没有 constructor 属性。

proto 是每个实例上都有的属性,prototype 是构造函数的属性,这两个并不一样,但 p.proto 和 Parent.prototype 指向同一个对象。

proto 属性在 ES6 时被标准化,但因为性能问题并不推荐使用,推荐使用 Object.getPrototypeOf()。

每个对象拥有一个原型对象,通过 proto 指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null,这就是原型链。