对象是编程中一个最为重要的数据的结构,其产生在不同的语言中有不同的方式。
- 在以类为中心的面向对象的编程语言中,由类创建对象。
- 在以原型为编程思想的语言中,创建对象只需要克隆一个对象即可获得。 在原型继承方面,js需要遵循的原型编程的基本规则是
- 所有的数据都是对象。
- 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它。
- 对象会记住它的原型。
- 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型。
1.所有的数据都是对象
我们知道undifined也是一种数据结构,如果,不考虑未定义的变量undifined,那么,其余的数据类型都可以通过"包装类"的方式变成对象类型数据来处理。
var num = new Number(123);
var flag = new Boolean(true);
var str = new String('aaa');
// (1) num,flag,str ==> Number、Boolean、String
console.log(num instanceof Number); // true
console.log(flag instanceof Boolean); // true
console.log(str instanceof String); // true
// (2) Number、Boolean、String ==> Object
console.log(Number instanceof Object); // true
console.log(Boolean instanceof Object); // true
console.log(String instanceof Object); // true
// (3) num,flag,str ==> Object
console.log(num instanceof Object); // true
console.log(flag instanceof Object); // true
console.log(str instanceof Object); // true
从以上可以看出,num、flag、str和Number、Boolean、String有关联,Number、Boolean、String与Object有关联,然后,num、flag、str与Object也有关联,那么是不是可以推测其中有一些内在的关联正在沿着一条链传递。 事实上,js中的根对象是Object.propotype对象,该对象是一个空对象。我们再js中遇到的每一个对象,可以认为都是从Object.propotype对象克隆而来,Object.propotype就是他们的原型。
2.要得到一个对象,不是实例化类,而是找到一个对象作为原型并克隆它
我们知道创建一个对象一般有两种方式
- var obj = {}
- var obj = new Object() 此时,引擎会从Object.propotype上面克隆一个对象出来。 其中,new运算符从构造函数函数中得到一个对象的demo如下:
function Person (name) {
this.name = name
}
Person.prototype.getName = function () {
return this.name
}
var p = new Person('阿猫')
console.log(p.name) // 阿猫
console.log(p.getName()) // 阿猫
console.log(Object.getPrototypeOf(p) === Person.prototype) // true
以上例子中的Person在例子是作为构造函数存在,当然,也可以直接作为普通函数被调用。在new运算符来创建对象的过程,实际上也是先克隆Object.prototype对象,再进行一些其他的操作,new运算符创建对象的主要流程是:
- 从Oject.propotype上克隆一个空的新对象
- 新对象的__proto__指向构造函数的prototype
- 让this指向新对象,并且将外部传入的属性赋值给新对象
- 返回新对象 根据new运算符的流程(其流程也就是其功能)模拟new运算符的实现
function Person(name) {
this.name = name
}
Person.prototype.getName = function () {
return this.name
}
var newClone = function () {
// (1)从Oject.propotype上克隆一个空的新对象
var obj = new Object();
// (2)获取外部传入的构造函数,并且让空对象指向正确的原型
var Constructor = [].shift.call(arguments)
obj.__proto__ = Constructor.prototype
// (3)让this指向新对象,并且将外部传入的属性赋值给新对象
var result = Constructor.apply(obj, arguments)
// (4) 返回新对象
return typeof ret === 'Object' ? result : obj
}
var p = newClone(Person, '阿狗')
console.log(p.name) // 阿狗
console.log(p.getName()) // 阿狗
console.log(Object.getPrototypeOf(p) === Person.prototype) // true
3.对象会记住它的原型
就js的真实实现来说,其实并不能说对象有原型,而只能说对香的构造函数有原型。也不能说对象把请求委托给它自己的原型,准确的说是,对象把请求委托给它的构造器的原型。那么,对象通过什么方式将它的请求交给它的构造器的原型呢? js给对象提供了一个名为__proto__的隐藏属性,对象的的__proto__属性默认会指向它的构造器的原型对象,即{Constructor}.propotype。 例如:
var obj = new Object()
console.log(obj.__proto__ === Object.prototype) // true
// 附加
console.log(Object.getPrototypeOf(obj) === Object.prototype) // true
console.log(obj.constructor.prototype === Object.prototype) // true
js中__proto__就是"对象"和"对象构造器的原型"联系起来的纽带
4.如果对象无法响应某个请求,它会把这个请求委托给它自己的原型
var A = function () {}
A.prototype = {
age: 30
}
var B = function () {}
B.prototype = new A()
var b = new B()
console.log(b.age) // 30
console.log(b.__proto__ === B.prototype) // true
console.log(b.__proto__.__proto__ === A.prototype) // true
console.log(b.__proto__.__proto__.__proto__ === Object.prototype) // true
从以上示例中可以看出,b中虽未定义age属性,但是,依然可以成功打印b.age,其流程是:
- 遍历b中所有属性,未找到age属性
- 查找age的请求被委托给对象b的构造器的原型,它在b.__proto__记录并且指向B.prototype,而B.prototype是由new A()创建而来
- 在new A()创建的对象中依然未找到age属性,于是请求被继续委托给这个对象构造器的原型A.prototype
- 在A.prototype中找到age,并返回
假设要在b中寻找job属性,那么查询顺序依旧如上,但是,最终在A._proto_,即Object.prototype中也未找到,寻找结束。
其实,以上沿着构造函数原型一直查找某个属性,直到在Object.prototype中终止而形成的链条,就是原型链。
参考书籍:《JavaScript设计模式与开发实践》(曾探)