谈谈你对原型和原型链的理解
每个对象都有__proto__属性,只有构造函数(普通函数也可以作为构造函数)才有prottotype属性,对象的__proto__属性指向其构造函数的prototype属性,prototype是一个对象,该对象的constructor属性指向构造函数自身,该对象的__proto__指向他的构造函数的prototype属性,这样通过__ptoto__构成的访问链条成为原型链,原型链的终点是null。
prototype的定义
在规范ES2019规范里,被定义为:给其他对象提供共享属性的对象。也就是它也是对象,只是被用以承担某个职能罢了。
function Foo() {}
const obj = new Foo();
obj.__proto__ === Foo.prototype;
Foo.__proto__ === Function.prototype;
Function.prototype.__proto__ === Object.prototype;
Object.prototype.__proto__ === null;
Object.__proto__ === Function.prototype;
Function.__proto__ === Function.prototype;
所有object对象都有一个隐式调用
每个js对象都有__proto__
属性,这就意味着obj
被隐式挂载了另一个对象的引用。这个属性不建议直接使用,只是早期为了我们能访问到内部属性[[prototype]]
。
也就是说,所谓的隐式,是指 非开发者亲自创建/操作。
历史问题__protot__
隐式地挂在引用:操作层面上隐式,偷偷的挂在了属性的动作。
隐式引用:关系层面上的隐式,这个属性不能直接访问。
ECMAScript规范描述prototype
是一个隐式引用,但现在有一些浏览器,已经私自实现了__proto__
这个属性,即可以通过obj.__proto__
这个显式的访问,到被定义为隐式属性的prototype
.
因此,情况是这样的,ECMAScript规范说prototype
应当是一个隐式引用。
- 通过
Object.getPrototypeOf(obj)
间接的访问对象的prototype
对象。 - 通过
Object.setPrototype(obj)
指定对象的prototype对象 - 部分浏览器已经可以
obj.__proto__
属性直接原型,所以通过obj.__proto__ = anotherObj
直接设置原型。 - ECMAScript 2015规范只好向事实低头,将
__proto__
属性纳入了规范的一部分。
__proto__
和constructor
是对象独有的。prototype属性是函数独有的
但是在Javascript中,函数也是对象,所以函数也拥有__proto__
和constructor
属性
function Person();
let bajiu = new Person();
prototype chain原型链
protottype
只是位于另一个对象隐式引用的普通对象。那么,也是具有一个对象的基本特征,也就是prototype
也有自己的隐式引用,有自己的prototype
对象。如此,构成了对象的原型的原型的原型的链条,知道某个对象的隐式引用为null,整个链条终止。
构造函数
构造函数本身也是一个函数,与普通函数没有任何区别,不过为了规范一般将其首字母大写。构造函数和普通函数的区别在于,使用new
生成实例的函数就是构造函数,直接调用就是普通函数。
那是不是意味着普通函数创建的实例没有constructor
属性?不一定
function parent1(age) {
return {
age: age
}
}
var p1 = parent1(25);
p1.constructor === Object; // true
Symbol
Symbol
是基本数据类型,但是作为构造函数说它并不完整,因为不支持new Symbol()
,Chrome认为其不是构造函数,如果要生成实例直接使用Symbol()
即可。
new Symbol(123); // Symbol is not a constructor
Symbol(123); // Symbol(123)
虽然是基本数据类型,但Symbol(123)
实例是可以获取constructor
属性值。
var sym = Symbol(123);
console.log(sym.constructor); // ƒ Symbol() { [native code] }
这里的constructor
属性来自Symbol
原型上的,即Symbol.protortype.constructor
返回创建实例的原型函数,默认为Symbol
函数。
constrcutor值只读吗?
分情况,对于引用类型来说constructor
属性值是可以修改的,但是对于基本类型(数字,字符串,布尔、Symbol)来说是只读的,null
和undefined
无constructor。
引用类型情况值可修改这个很好理解,比如原型链继承方案中,就需要对constructor
重新复制进行修正。
function Foo() {
this.age = 30;
}
function Bar() {}
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';
Bar.prototype.constructor === Foo; // true
// 修正constructor
Bar.prototype.constructor = Bar;
var bar = new Bar();
总结
-
Object是所有对象的祖先,所有对象都可以通过
__proto__
找到它 -
Function是所有函数的祖先,所有函数都可以
__proto__
找到它 -
函数的prototype是对象
-
对象的
__proto__
指向原型,__proto__
将对象和原型连接起来组成了原型链 -
每个普通对象都有
__proto__
属性,但只有函数对象才有prototype
属性 -
所有函数对象的
__proto__
都指向Function.prototype
,他是一个空函数。
思考题
原型分析
person.__proto__ // Person.prototype
Person.__proto__ // Function.prototype
Persion.prototype.__proto__ // Object.prototype
Object.__proto__ // Function.prototype
Object.prototype.__proto__ // null
Function.prototype
Function.prototype
是唯一一个typeof xxx.prototype
返回function
的原型。
console.log(typeof Function.prototype) // function
console.log(typeof Object.prototype) // object
console.log(typeof Number.prototype) // object
console.log(typeof Boolean.prototype) // object
console.log(typeof String.prototype) // object
console.log(typeof Array.prototype) // object
console.log(typeof RegExp.prototype) // object
console.log(typeof Error.prototype) // object
console.log(typeof Date.prototype) // object
console.log(typeof Object.prototype) // object
console.log(Function.prototype.__proto__ === Object.prototype) // true
Object 与 Function
Object instanceof Function; // true
Function instanceof Object; // true
JS是单继承的,Object.prototype
是原型链的顶端,虽有对象从它继承了包括toString
等方法和属性。
Object
本身是构造函数,继承了Function.prototype
,Function
也是对象,继承了Object.prototype
,这里就有一个鸡和蛋的问题。