JavaScript原型

123 阅读4分钟

我们都知道可以用{}(对象字面量)创建对象,就是俗称的语法糖。它的底层原理是通过Obejct()构造函数实例化创建对象,这种方法存在一些弊端,不方便添加属性,需要用.操作符进行添加属性。

var obj = new Object();
obj.a = 1;
obj.b = function(){};
obj.c = {
    name: "Jack",
    age: 18
}
console.log(obj.a) // 1

函数和数组也是对象类型,它们也可以通过对应的构造函数(Funciton()Array())实例化创建对象,用.操作符添加属性。

var fun = new Function();
fun.a = 1;
console.log(fun.a); // 1
var arr = new Array();
arr.b = 2;
console.log(arr.b); // 2

自定义构造函数

Obejct()Function()Array()这三个构造函数都是JS的内置函数,实际上我们还可以自定义构造函数,注意自定义构造函数时,首字母要大写,这是为了和普通函数进行区分。

这里有一个重要但是非常细微的区别:实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。它们只是被new操作符调用的普通函数而已。

function Car(){
    this.color = "black",
    this.brand = "Benz",
    this.drive = function(){
        console.log("I am running.");
    }
}
var car = new Car();
console.log(car.color);
// black

实例化原理

当函数通过new关键字调用时,会自动执行下面的操作:

  1. 创建(或者说构造)一个全新的对象,称为实例对象。
  2. 将实例对象的__proto__属性指向构造函数的prototype属性。
  3. 实例对象会绑定到函数调用的this。
  4. 函数默认隐式返回该实例对象(即return出去。)如果我们手动return原始值,是不起作用的。但是如果return了对象类型,是有用的。
function Student(){
    this.a = 1;
    return 9;
};
var student = new Student();
console.log(student); // Student {a: 1}
function Student(){
    this.a = 1;
    return [];
};
var student = new Student();
console.log(student); // []

new关键字的最主要作用就是把this指向从window改成了实例对象,以下是伪代码。

function Car(color, brand){
    // 生成一个隐式空对象并和this绑定
    var this = {};
    // 往this对象中添加属性
    this.color = color;
    this.brand = brand;
    // 将this对象 return出去
    return this;
}

var car1 = new Car('red', 'Mazda');
console.log(car1.color);
// red

Prototype

我们已经知道了创建对象的底层原理是构造函数实例化,也就意味着对象是函数创建的,同时函数也是一种特殊对象。似乎是一种鸡生蛋蛋生鸡的关系,而这种关系的关键点就是prototype属性。

每个函数都有一个属性叫做prototype,它的值是对象类型并且其中默认有一个constructor属性指向这个函数本身。还有一个隐式属性__proto__(也写作[[prototype]])。

构造器.png

prototype中除了默认属性,我们还可以自定义别的属性。此时我们发现构造函数的实例化对象也可以访问prototype中的属性。

function Student(){};
Student.prototype.a = 1;
console.log(Student.prototype.a); // 1
var student = new Student();
console.log(student.a); // 1

proto

之前我们提到了prototype中还有另一个属性__proto__,实际上每一个对象都有__proto__属性,就像每一个函数都有prototype一样。

  • __proto__属性指向创建该对象的函数的prototype

  • 我们可以称呼prototype为原型,__proto__为隐式原型。

var obj = {};
console.log(obj.__proto__);

顶端.png

以上代码中的obj本质上是通过Object 函数创建的,所以obj的__proto__属性指向Object函数的prototype

  • obj.__proto__ === Object.prototype

Object.prototype本身也是一个对象,那么它必然也有__proto__属性。

通过以下代码可以发现指向的是null,可以理解为Object.prototype就是JS中对象的顶端。

console.log(Object.prototype.__proto__); // null
console.log(obj.__proto__.__proto__); // null

再看看其它构造函数的prototype的__proto__指向哪里,打印结果是指向Object.prototype。

意味着其它构造函数的原型都是由Object函数创建的,进一步说明对象都是函数创建

console.log(Array.prototype.__proto__);
console.log(Function.prototype.__proto__);

顶端.png

函数是一种特殊对象,它是否也有__proto__属性,它又指向哪里?

通过以下代码可以发现函数的__proto__都指向Function构造函数的prototype,说明函数都是由Funciton构造函数创建的,并且Function创建了自身。

console.log(Array.__proto__ === Function.prototype); // true
console.log(Student.__proto__ === Function.prototype); // true
console.log(Function.__proto__ === Function.prototype); // true
console.log(Object.__proto__ === Function.prototype); // true

最后一个问题:Function.prototype也是一个对象,它的__proto__是不是也指向Object.prototype?

答案是肯定的。因为Function.prototype也是一个普通的被Object创建的对象,所以也遵循基本的规则。

console.log(Function.prototype.__proto__);

顶端.png

原型链

通过以上内容我们已经知晓了prototypeproto之间的的关系,原型链的本质沿着__proto__prototype