原型与原型链

227 阅读4分钟

谈谈你对原型和原型链的理解

每个对象都有__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;

image.png

所有object对象都有一个隐式调用

每个js对象都有__proto__属性,这就意味着obj被隐式挂载了另一个对象的引用。这个属性不建议直接使用,只是早期为了我们能访问到内部属性[[prototype]]
也就是说,所谓的隐式,是指 非开发者亲自创建/操作。 image.png

历史问题__protot__

隐式地挂在引用:操作层面上隐式,偷偷的挂在了属性的动作。
隐式引用:关系层面上的隐式,这个属性不能直接访问。
ECMAScript规范描述prototype是一个隐式引用,但现在有一些浏览器,已经私自实现了__proto__这个属性,即可以通过obj.__proto__这个显式的访问,到被定义为隐式属性的prototype. 因此,情况是这样的,ECMAScript规范说prototype应当是一个隐式引用。

  1. 通过Object.getPrototypeOf(obj)间接的访问对象的prototype对象。
  2. 通过Object.setPrototype(obj)指定对象的prototype对象
  3. 部分浏览器已经可以obj.__proto__属性直接原型,所以通过obj.__proto__ = anotherObj直接设置原型。
  4. ECMAScript 2015规范只好向事实低头,将__proto__属性纳入了规范的一部分。

__proto__constructor对象独有的。prototype属性是函数独有的

但是在Javascript中,函数也是对象,所以函数也拥有__proto__constructor属性

function Person();
let bajiu = new Person();

image.png

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)来说是只读的,nullundefined无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,这里就有一个鸡和蛋的问题。

资料

  1. 深入理解JavaScript
  2. 【THE LAST TIME】一文吃透所有JS原型相关知识点