一、创建对象的几种方法
1.字面量
字面量var a = {}
其实是var a = new Object()
的语法糖,推荐使用前者var a = []
其实是var a = new Array()
的语法糖,推荐使用前者function Foo(){}
其实是var Foo = new Function()
的语法糖,推荐使用前者
2.构造函数
var Foo = function(name) {
this.name = name
}
var f = new Foo()
3.Object.create(Object.create()
方法创建一个新对象,使用现有的对象来提供新创建的对象的proto )
var P = {name:'o3'};
var o3 = Object.create(P);
对象规则:
- 所有引用类型都有 proto属性(隐式原型),一个普通的属性值
- 所有的函数,都有一个prototype属性(显式原型),属性值也是一个普通的对象
- 所有的引用类型(数组,对象,函数)proto属性值指向它的构造函数的prototype属性值
- 当试图得到一个引用类型(数组,对象,函数)的某个属性时,如果这个引用类型本身没有这个属性,那么会去它的proto(即它的构造函数的prototype)中寻找
二、prototype
每个函数都有一个 prototype 属性。函数的prototype就是一个对象,这个就是调用该构造函数时创建的实例所指的原型。可以这样理解,每个js对象生成的时候就会与之关联另一个特殊的对象,这个特殊的对象就是原型,每个创建的对象都会从原型“继承”属性。
让我们用一张图表示构造函数和实例原型之间的关系:
上图中我们用 Object.prototype 表示实例原型。
实例和实例原型的关系我们该怎么表示呢?下面就要用到另外一个对象 proto
三、proto
上面我们有提到每个对象都有这个属性,叫proto,这个属性会指向该对象的(实例)原型。
代码表示如下:
function Person() {
}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
于是我们更新下关系图:
既然实例对象和构造函数都可以指向原型,那么原型是否有属性指向构造函数或者实例呢?
四、constructor
指向实例倒是没有,因为一个构造函数可以生成多个实例,但是原型指向构造函数倒是有的,这就要讲到第三个属性:constructor,每个原型都有一个 constructor 属性指向关联的构造函数。
为了验证这一点,我们可以尝试:
function Person() {
}
console.log(Person === Person.prototype.constructor); // true
所以再更新下关系图:
综上我们已经得出:
function Person() {
}
var person = new Person();
console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
五、实例与原型
当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。
六、原型的原型
在前面,我们已经讲了原型也是一个对象,既然是对象,我们就可以用最原始的方式创建它,那就是:
var obj = new Object();
obj.name = 'Kevin'
console.log(obj.name) // Kevin
其实原型对象就是通过 Object 构造函数生成的,结合之前所讲,实例的 proto 指向构造函数的 prototype ,所以我们再更新下关系图:
七、原型链
那 Object.prototype 的原型呢?
console.log(Object.prototype.__proto__ === null) // true
所以 Object.prototype.proto 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。
所以查找属性的时候查到 Object.prototype 就可以停止查找了。
最后一张关系图也可以更新为:
图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
补充
真的是继承吗?
继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。
原型的优点
1.公用原型上的对象(方法),节约了堆内存
function Fn() {
this.a = {name: 'june'}
}
let f1 = new Fn()
let f2 = new Fn()
f1.a === f2.a // false
function Fn() {
}
Fn.prototype.a = {name: 'june'}
let f1 = new Fn()
let f2 = new Fn()
f1.a === f2.a // true