我们都知道可以用{}(对象字面量)创建对象,就是俗称的语法糖。它的底层原理是通过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关键字调用时,会自动执行下面的操作:
- 创建(或者说构造)一个全新的对象,称为实例对象。
- 将实例对象的
__proto__属性指向构造函数的prototype属性。 - 实例对象会绑定到函数调用的this。
- 函数默认隐式返回该实例对象(即
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]])。
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__);
以上代码中的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__);
函数是一种特殊对象,它是否也有__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__);
原型链
通过以上内容我们已经知晓了prototype和proto之间的的关系,原型链的本质沿着__proto__找prototype。