常常,我不懂原型,不懂__proto__和prototype,今天浅学一下。
prototype和__proto__都指向原型对象。
__proto__:对象属性。每个对象都有。
prototype:函数特有属性。
let obj = {};
console.log('__proto__' in obj); // true
console.log('prototype' in obj); // false
const fun = function () {};
console.log('__proto__' in fun); // true
console.log('prototype' in fun); // true
并不是所有的 Function 对象都拥有 prototype 属性。典型的是箭头函数。
const fn = () => {};
console.log('prototype' in fn); // false
const method = ({foo () {}}).foo;
console.log('prototype' in method); // false
async function asyncFunction() {}
console.log('prototype' in asyncFunction); // false
对象的__proto__属性,返回该对象的原型。
默认情况下,函数的prototype属性,指向一个对象。对于普通函数来说,该属性基本无用。对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型。
let obj = {};
console.log(obj.__proto__ === Object.prototype); // true
const f = function () {}
console.log(f.__proto__ === Function.prototype); // true
const F = function () {};
console.log((new F()).__proto__ === F.prototype); // true
prototype 属性
作用:
JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。
构造函数的prototype属性,用来定义所有实例对象共享的属性和方法。
const Cat = function (name) {
this.name = name;
}
Cat.prototype.host = '张三';
Cat.prototype.aowu = function () {
return this.name + ': 嗷呜';
}
let nuomi = new Cat('糯米');
let tangyuan = new Cat('汤圆');
console.log(nuomi.aowu === tangyuan.aowu); // true
console.log(nuomi.host); // '张三'
console.log(tangyuan.host); // '张三'
实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法。
实例对象自身 有某个属性或方法的时候,它不会再去原型对象寻找这个属性或方法。
tangyuan.host = '李四';
console.log(nuomi.host); // '张三'
console.log(tangyuan.host); // '李四'
constructor 属性
默认情况下,prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。
const Cat = function (name) {}
console.log(Cat.prototype.constructor === Cat); // true
let nuomi = new Cat('糯米');
console.log(nuomi.constructor === Cat); // true
console.log(nuomi.hasOwnProperty('constructor')); // false
nuomi是构造函数Cat的实例对象,但是nuomi自身没有constructor属性,该属性其实是读取原型链上面的Cat.prototype.constructor属性。
介绍完prototype,继续原型链相关。
原型链
上文说到,对于构造函数来说,生成实例的时候,prototype属性会自动成为实例对象的原型。
即:实例对象.__proto__ === 构造函数.prototype 。
首先,我们定义构造函数F,用它生成一个实例对象f。f.__proto__指向构造函数F的prototype属性。即:
function F() {}
const f = new F();
console.log(f.__proto__ === F.prototype); // true
构造函数F是Function的实例,所以F.__proto__指向Function的prototype属性。
console.log(F.__proto__ === Function.prototype); // true
默认情况下,函数的prototype属性,指向一个对象,即函数的prototype属性是Object的实例。
函数.prototype的 __proto__指向Object.prototype。
所以,F.prototype. __proto__ 和 Function.prototype. __proto__ 均指向Object.prototype。
console.log(F.prototype.__proto__ === Object.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
但,除Function.prototype外,函数.prototype 的数据类型都是'object'
console.log(typeof Function.prototype); // 'function'
console.log(typeof Object.prototype); // 'object'
console.log(typeof F.prototype); // 'object'
console.log(typeof Date.prototype); // 'object'
函数(包括构造函数,系统内置的函数对象(Function、Object等))都是Function的实例。所以Function.__proto__指向Function.prototype,就不会那么不容易理解。
console.log(Function.__proto__ === Function.prototype); // true
console.log(Object.__proto__ === Function.prototype); // true
console.log(Array.__proto__ === Function.prototype); // true
console.log(Date.__proto__ === Function.prototype); // true
console.log(RegExp.__proto__ === Function.prototype); // true
console.log(Error.__proto__ === Function.prototype); // true
console.log(Number.__proto__ === Function.prototype); // true
console.log(String.__proto__ === Function.prototype); // true
console.log(Boolean.__proto__ === Function.prototype); // true
需要特别注意一下,Math,它不是构造函数。所有的属性和方法都在Math对象上调用。
console.log(Math.__proto__ === Object.prototype); // true
JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……
JavaScript中,万物皆对象!如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype。即所有对象都继承了Object.prototype的属性。
Object.prototype的原型呢?是null。null没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null。
console.log(Object.prototype.__proto__ === null); // true
另:__proto__并非标准属性(ECMA-262第5版将该属性或指针称为[[Prototype]]),只有浏览器才需要部署,其他环境可以没有这个属性。
因此,应尽量少用这个属性,而是用Object.getPrototypeOf()和Object.setPrototypeOf(),进行原型对象的读写操作。
至此,原型链相关知识基本梳理完毕。综上,可以绘制原型链的图谱了。
最后:
instanceof 运算符:运算符返回一个布尔值,表示对象是否为某个构造函数的实例。运算符左边是实例对象,右边是构造函数。原理是检查右边构造函数的prototype属性,是否在左边对象的原型链上。
Object.prototype.isPrototypeOf():用来判断该对象是否为参数对象的原型。
function F() {}
const f = new F();
console.log(f instanceof F); // true
console.log(f instanceof Object); // true
console.log(F.prototype.isPrototypeOf(f)); // true
console.log(Object.prototype.isPrototypeOf(f)); // true
// 一种特殊情况,判断失真
let obj = Object.create(null);
console.log(typeof obj); // 'object'
console.log(obj instanceof Object); // false
console.log(Object.prototype.isPrototypeOf(obj)); // false
The end.