js原型与原型链
prototype与proto与constructor区别
function Person() {
};
let person = new Person();
person.name = 'Jim';
console.log(person.name);// Jim
👆的例子就是我们使用构造函数去创建对象的方式。通过new关键字去创建了person实例对象。
下面我们进行具体的解读:
prototype
首先每个对象都具有prototype属性,这个属性使我们有能力想对象添加属性和方法。
function employee(name,job,born)
{
this.name=name;
this.job=job;
this.born=born;
}
var bill=new employee("Bill Gates","Engineer",1985);
employee.prototype.salary=null;
bill.salary=20000;
20000
bill
employee {name: "Bill Gates", job: "Engineer", born: 1985, salary: 20000}
var Area = new employee('Area Jim','Engineer',1993)
test.salary
null
通过上面的例子 我们可以看出我们可以通过使用prototype这个属性给我们的构造函数,或者对象添加属性和方法。
那prototype这个属性到底指向的是什么呢?
其实,函数的prototype属性指向的是一个对象,这个对象就是正在调用的构造函数而创建的实例的原型。也是上面例子中的bill和Area的原型。
我们理解了prototype指向的是谁,但是现在又有了一个新问题,就是什么是原型?
其实原型就是:每一个JavaScript对象(不包括null)在创建的时候与之关联
的另外一个对象,这个对象就是我们所说的原型,每个对象都会从原型继承
属性。

我们从图中可以看到构造函数和实例原型之间的关系,那么我我们应该怎么表示实例与实例原型的关系呢,也就是bill与employee.prototype之间的关系呢?👇
proto
let flag = true
flag.__proto__
Boolean {false, constructor: ƒ, toString: ƒ, valueOf: ƒ}
let str = ''
str.__proto__
String {"", length: 0, constructor: ƒ, anchor: ƒ, big: ƒ, blink: ƒ, …}
let arr = []
arr.__proto__
[constructor: ƒ, concat: ƒ, copyWithin: ƒ, fill: ƒ, find: ƒ, …]
let number = 1
number.__proto__
Number {0, constructor: ƒ, toExponential: ƒ, toFixed: ƒ, toPrecision: ƒ, toString: ƒ, …}
let obj = {}
obj.__proto__
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
每个JavaScript对象(null除外)都具有一个属性,这个属性就是_proto_,这个属性会指向该对象的原型。
大家可以在控制台输入下面的代码尝试一下:
function Person() {
}
var person = new Person();
console.log(person.__proto__ === Person.prototype);
true

既然实例对象与构造函数都指向原型,那原型是否有属性指向构造函数或者指向实例呢?
constructor
function Person() {
}
var person = new Person()
console.log(Person === person.prototype.constructor)
true
从上面的代码我们可以看出原型是指向构造函数的,即属性constructor属性。
因为一个构造函数是可以产生多个实例的,即每个实例原型的constructor属性都指向关联的构造函数。

当获取person.constructor时,当前的person实例时没有constructor属性的,当读取不到的时候会查找person的原型Person.prototype中去读取:
person.constructor === Person.prototype.constructor
true
实例与原型的关系
当js执行环境在读取实例属性是,找不到属性,会查询与对象关联的原型中的属性,如果还查不到,就回去查找原型的原型,知道找到最顶层为止。
🌰
function Person() {
}
Person.prototype.name = 'Jim'
var person = new Person()
person.name = 'Kimi'
console.log(person.name) //Kimi
delete person.name
console.log(person.name) //Jim
从上面的🌰我们可以看到 我们给实例的person对象添加了新的name值,并且打印person.name时,结果为Kimi,当我们把实例对象person的name属性删除时,再去读取person.name时,这是在person这个实例中name属性已经被删除了,那么js就会去person的原型person.proto,也就是Person.prototype中去查找name属性,这是返回的name为Jim。
如果在原型中还没有找到呢?原型的原型又是啥?
原型的原型
前面我们提到了原型是一个对象,既然原型是一个对象,那么我们可以使用最原始的方法去声明创建它:
var obj = new Object()
obj.name = 'Jim'
console.log(obj.name) //Jim
所以原型对象是通过Object构造函数生成的,也就是说实例的proto指向的是构造函数的prototype。

原型的原型的原型是啥 --- 原型链
console.log(Object.prototype.__proto__)
null
null
到底是啥?代表什么?
所以当查询属性查询到了Object.prototype就可以停止了。

⬆️蓝色的这条线代表的就是原型链。
proto ,绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.proto 时,可以理解成返回了 Object.getPrototypeOf(obj)。
最后
饮用《你不知道的JavaScript》中的话:继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。