原型、构造函数、原型链还傻傻分不清?原型对象和隐式原型又是什么东西?来和我一起梳理一下吧
原型
全貌
-
User构造函数实例化了一个User实例,所以User构造函数的prototype原型(对象)==User实例的__proto__隐式原型,它们构成了这样的三角关系。 -
User构造函数本质上是new Function()而来的一个实例,所以User构造函数、Function构造函数和Function原型(对象)之间也有这样的三角关系。 -
Function原型(对象)也是个对象呀,所以是new Object()而来,那么Function原型(对象)是Object构造函数的一个实例,则Function原型(对象)、Object构造函数和Object原型(对象)也构成了这样的三角关系。 -
特殊的点:
Object原型(对象)的隐式原型是null,也就是说Object.prototype.__proto__是nullObject构造函数本质上也是new Function()而来的一个实例,那么,其原型对象就是Function原型(对象),即Object.prototype == Function.prototype- 在JS中,万物皆对象,
Function构造函数本质上也是对象,但它比较特殊,它的隐式原型是它的原型(对象),也就是Function.__proto__ == Function.prototype
构造函数
构造函数和普通函数本质上没什么区别,只不过使用了
new关键字创建对象的函数,被叫做了构造函数。构造函数的首字母一般是大写,用以区分普通函数,当然不大写也不会有什么错误。等价于类class
function Person(name, age) {
this.name = name;
this.age = age;
this.species = '人类';
this.say = function () {
console.log("Hello");
}
}
let person = new Person('xiaoming', 20);
class Person {
constructor(name, age){
this.name = name;
this.age = age;
this.species = '人类';
this.say = function () {
console.log("Hello");
}
}
}
let person = new Person('xiaoming', 20);
以上两种写法都可以用来创建对象,效果等价。
使用new操作符调用构造函数,做了如下几步操作:
- 创建一个新的对象;
- 将新对象的隐式原型(
_proto_)指向构造函数的原型(对象)(prototype); - 将构造函数的
this指向新对象; - 执行构造函数的代码;
- 如果构造函数返回非空对象,则返回此对象,否则,则返回新建的对象;
构造函数和普通函数在本质上并没有区别,构造函数首字母大写只是一个约定成俗的规范而已。
使用 new 操作符调用普通函数也可以创建实例,所以,正确的说,使用 new 操作符调用函数,可使该函数成为构造函数。直接调用 person 函数的话,由于是全局环境下调用,所以 person 的 this 绑定的是 window 。
原型(对象)(prototype)
在js中,每一个函数类型的数据(构造函数) ,都有一个叫做
prototype的属性,这个属性指向的是一个对象,就是所谓的原型(对象),它是一个对象。对于原型(对象) 来说,它有个constructor属性,指向它的构造函数。
二者互相指
原型(对象)最主要的作用就是用来存放实例对象的 公有属性 和 公有方法。
把这些公有的属性和方法放在原型对象里共享,避免重复创建相同的属性和方法造成浪费:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.species = '人类';
Person.prototype.say = function () {
console.log("Hello");
}
let per1 = new Person('xiaoming', 20);
let per2 = new Person('xiaohong', 19);
console.log(per1.species); // 人类
console.log(per2.species); // 人类
per1.say(); // Hello
per2.say(); // Hello
console.log(per1.constructor); // Person()
// 这个constructor是原型对象的属性,在这里能被实例对象使用,是因为到实例对象的__proto__上找去了
// 这就要说到原型链了。
获取对象的(隐式)原型
- Object 对象的一个方法
isPrototypeOf可以确定对象和(隐式)原型是否有关系,console.log(Person.prototype.isPrototypeOf(person1)); // true - Object 对象的一个方法
getPrototypeOf可以获取对象的(隐式)原型,console.log(Object.getPrototypeOf(person1) === Person.prototype); // true
重写对象的(隐式)原型
- Object 对象提供一个方法
setPrototypeOf重写对象(隐式)原型。
let father = {
name: 'father'
}
let child = {
childName: 'child'
}
Object.setPrototypeOf(child, father);
console.log(child.childName); // child
console.log(child.name); // father
console.log(Object.getPrototypeOf(child) === father); // true
- Object 对象提供方法
create可以在创建对象的时候指定对象的(隐式)原型。
let father = {
name: 'father'
}
let child = Object.create(father)
child.childName = 'child'
console.log(child.childName); // child
console.log(child.name); // father
console.log(Object.getPrototypeOf(child) === father); // true
遮蔽原型
实例不能重写原型的属性,但是实例可以定义一个属性来遮蔽原型上的同名属性。
Object 的原型提供了一个方法
hasOwnPrototype用于判断属性是在(隐式)原型还是实例上,在实例上,返回true,在原型上,返回false。
let Person = function () {};
Person.prototype.name = 'proto';
let person1 = new Person();
person1.name = 'Tom';
console.log(person1.name); // Tom
// 引擎会先在实例中找 name 属性,因为实例有 name 属性,所以就输出 Tom。
console.log(person1.hasOwnPrototype(name)); // true
delete person1.name;
// 使用 delete 操作符可以删除实例的属性
console.log(person1.name); // proto
// 引擎会在实例中查找属性 name ,找不到时,引擎会在实例的原型中找。
console.log(person1.hasOwnPrototype(name)); // false
隐式原型(__proto___)
隐式原型是利用
__proto__属性查找原型,这个属性 指向当前对象的构造函数的原型对象,这个属性是对象类型数据的属性,所以 实例对象可以使用
console.log(per1.__proto__ === Person.prototype); // true
console.log(per2.__proto__ === Person.prototype); // true
形象的比喻
- 构造函数
雕刻师,其
prototype指向原型(对象) - 原型(对象)
设计图/设计原型。它也有
__proto__次稿设计图/设计原型,初稿设计图/设计原型。 最最早的初稿是一张白纸,也就是说
Object.prototype.__proto__是null - 通过构造函数
new出来的实例对象一个个雕塑作品,其
__proto__指向指向原型(对象)
原型链
简介
原型链的核心就是依赖对象的
__proto__的指向,当自身不存在的属性时,就一层层的扒出创建对象的构造函数,直至到Object时,就没有__proto__指向了。
不仅实例有一个__proto__属性,原型对象也有一个__proto__属性指向它的原型,从中继承方法和属性,一层一层,以此类推,直到Object.prototype = null,通俗来讲:原型链就是多个对象通过__proto__的方式连接了起来。
instanceof
instanceof就是基于 原型链 判断变量的类型
function myInstanceof(left, right) {
if (typeof left !== 'object' || left === null) return false;
let pro = Object.getPrototypeOf(left);
while(true) {
if(pro === null) return false;
if(pro === right.prototype) return true;
pro = Object.getPrototypeOf(pro);
}
}
// 测试函数
myInstanceof(12, Number) // false
myInstanceof(new Number('123'), Number) // true
myInstanceof([], Array) // true
总结
Object是所有对象的爸爸或者祖先,所有对象都可以通过_proto_找到它;Function是所有函数的爸爸,所有的函数都可以通过_proto_找到它;- 函数的
prototype是一个对象,叫原型(对象),包含一些属性和方法; - 对象的隐式原型
__proto__属性指向其构造函数的原型(对象),__proto__将多个对象和隐式原型连接起来,叫做原型链;