js --prototype、__proto__与constructor

219 阅读7分钟

js --prototype、__proto__与constructor

1. 了解js 对象必须要知道三个属性'prototype、__proto__与constructor',在ES标准定义中
'__proto__'其实是暴露了通过它访问的对象的内部`[[Prototype]]`
2.'__proto__''constructor'属性是'对象'所独有的,现在这句话有问题等会详细说
3.'prototype' 属性是'函数'所独有的,但是函数是特殊的对象,因此也具有'__proto__''constructor'
属性
简单的说对象没有'prototype',函数具有'prototype、__proto__与constructor'三个属性
// 对象打三个属性
const aObj = {}
console.log(aObj .__proto__) // 有值
console.log(aObj .constructor) // 有值
console.log(aObj .prototype) // undefined

// 方法打印三个属性
function aFun(){}
undefined
console.log(aFun.__proto__)  // 有值
console.log(aFun.constructor) // 有值
console.log(aFun.prototype) // 有值
函数是特殊的对象身上的属性
function test(){}
Object.getOwnPropertyNames(test)
// 打印结果
["length", "name", "arguments", "caller", "prototype"]
prototype -- 原型对象
1.'prototype' -- 该属性的值是一个对象,该属性为该函数的'原型对象',注意了就像
上面说的该属性是函数独有的
2.通过下面的打印结果可以看出,'P.prototype' 打印出来是一个'对象',这个对象中包含
了两个属性'__proto__与constructor',注:'__proto__ 和[[Prototype]]' 是等同的
 2.1.默认情况下,所有函数的原型对象'prototype'都拥有'constructor'属性,该属性指向与之
 关联的构造函数,像下面案例 'P.prototype.constructor' 就是构造函数P 
 2.2.'prototype'同样拥有自己的原型对象('__proto__'),上面说过对象拥有'__proto__' 属性,
 'prototype'也是对象因此它也是具有'__proto__' 属性
3.总结:函数都有一个'prototype'属性即'原型对象',该属对象,内部一般有两个属性分别是
'constructor''__proto__',其中'constructor' 为函数本身即构造函数,'__proto__' 指向了
'Object.prototype',可以看图三去理解
  • 第二条说明
function P(){}
console.log(P.prototype)
  • 打印结果
  • 2.1 条案例说明
// 构造函数P
function P(){}
P.prototype.constructor === P // true
  • 第三点
__proto__
1.对象中有'__proto__'这个属性,叫原型,也是一个对象,这个属性是给浏览器使用,不是标准的属性
MDN对这个这里的描述该属性属于废弃属性,本质上说'__proto__'暴露了通过它访问的对象的内部`[[Prototype]]`

2.该属性废弃后后续想获得'__proto__'要怎么做,这里mdn 也给答案,ECMAScript 2015规范中
被标准化为传统功能,以确保Web浏览器的兼容性。为了更好的支持,建议只使用
'Object.getPrototypeOf/Reflect.getPrototypeOf ',相对的如果你想给'__proto__' 赋值,
'Object.setPrototypeOf/Reflect.setPrototypeOf'(尽管如此,设置对象的[[Prototype]]是一个缓慢的操作,如果性能是一个问题,应该避免)

3.实例对象的'__proto__'指向了构造函数的原型对象'prototype',下面案例来解释,PP为构造函数
通过PP创建了对象'pp','pp'这个实例对象指向创建他的构造函数的'PP.prototype'
  • 案例说明第二条
var a = {}
Object.getPrototypeOf(a) === a.__proto__ // true

mdn 描述链接

  • 案例解释第三条
function PP(){}
var pp = new PP() // true
pp.__proto__ === PP.prototype
constructor -- 构造函数
1.constructor 构造函数,当'constructor ' 在原型对象'prototype'时候他等同于当前构造函数,这一
点在 'prototype -- 原型对象' 这个章节讲解过
2.之前说过'constructor'属性是'对象'所独有的(这句话不完全对),对象的'constructor'又是谁?
看下面案例,可以得到回答对象的构造函数是创建其构造函数
上面也说过函数也是对象,那么函数的构造函数是谁?
看下面案例,可以得到回答指向其实是Function,那么就可以解释函数也是对象即函数是通过
构造函数'Function'可以得到'var PP = new Function()'
3.总结:
 3.1.在原型对象中('prototype') 的'constructor ' 指向的就是自己所在的原型对象所在的构造函
 数
 3.2.在普通实例对象上对象的构造函数是创建其构造函数(这句话不完全对,下面继承会详细讲这个)
function PP(){}
var pp = new PP() // true
pp.constructor === PP

PP.constructor === Function
true
做个总结
1.自定义函数时(箭头函数除外)也会默认生成,生成的显式原型对象(prototype)其中属性 
'constructor' ,
该属性指向函数自身。
2.'prototype(显式原型)'"__proto__(隐式原型)"
 2.1.'prototype''函数的属性',是函数特有当然除了箭头函数
 2.2.'__proto__'是对象的属性,指向该对象的构造函数的原型对象,这个是浏览器厂商提出的下面
继承与原型链(一)
1.通过将对象 a 的__proto__属性赋值为对象 b,即a.__proto__ = b,此时使用a.__proto__便
可以访问 b 的属性和方法,像下面案例中a对象没有'age'属性但是a.__proto__ = b,此时b有age
属性因此a 也可以访问到'age' 属性,简单的总结JavaScript 可以在两个对象之间创建一个关联,
这个使得一个对象可以访问另一个对象的属性和方法,从而实现了继承
2.实际通过上面的讲解了解,对象的'__proto__'指向了构造函数的原型对象'prototype',也就是
以下面案例种的A构造函数做说明,实际上'a.__proto__ = A.prototype',又已知每一个原型对象
('prototype')中都有两个属性'constructor''__proto__',那现在就可以知道为什么
'a.constructor === A .prototype.constructor ===A ',本身来说'a'没有'constructor' 属性,但是
a的'__proto__'这座桥帮我们去'A .prototype' 这个原型对象中找看看有没有'constructor'因为
找了因此a也具有'constructor'
3.为什么'a.__proto__  !== A.prototype.__proto__',按照上面'constructor' 找寻方式,此时
'a.__proto__   和 A.prototype.__proto__'等同的,实际却不是这样,但从逻辑方面分析
'a.__proto__ === A.prototype' ,如果'a.__proto__  === A.prototype.__proto__' 岂不是
' A.prototype === A.prototype.__proto__' 陷入了无休止的嵌套,实际js'属性遮蔽'可以理解成
就近原则,来看下面的案例'解释第三条',很明显虽然原型对象上又name 属性但是就近的关系
让先找到是对象上的'name'
  • 解释第一条
function A (name) {
    this.name = name
}
function B (age) {
    this.age = age
}
const a = new A("a")
const b = new B(12)

// 遵循新版规范使用方法 下面写法和a.__proto__ = b 等同
Object.setPrototypeOf(a, b)

console.log(a.name); // a
console.log(a.age); // 12
  • 解释第三条
function A (name) {
    this.name = name
}
A.prototype.name = "原型对象上的name 属性"

const a = new A('w')

console.log(a.name); // w

继承与原型链(二)
1.现在知道如果对象无法响应某个请求,它会把这个请求委托给它自己的原型
2.以下面的案例做更详细的解释
  2.1.首先,尝试遍历对象a中的所有属性,但没有找到name这个属性。
  2.2.查找name属性的这个请求被委托给对象a的构造器的原型,它被a.__proto__
  记录着并且指向A.prototype,而A.prototype被设置为对象obj。
  2.3.在对象obj中找到了name属性,并返回它的值。

3.进一步证明打印是自己的而不是原型对象'prototype'上的,使用hasOwnProperty 返回的是true 
则使用的是实例对象自己的,false怎相反
  • 解释第二条
var obj = { name: 'sven' };

var A = function(){};
A.prototype = obj;

var a = new A();
console.log( a.name ); 
  • 第三条的案例
 function Person() {}
// 每一个函数都有一个原型属性prototype
// 他们都会指向实例对象因此在Person这个
// 构造函数的prototype加属性即可创建的对象共享
Person.prototype.name = 'wang'

const p1 = new Person()
// 当前的name 到底是p1的还是 Person的
console.log(p1.hasOwnProperty('name'))

const p2 = new Person()
p2.name = 'p2'
// 当前的name 到底是p1的还是 Person的
console.log(p2.hasOwnProperty('name'))

打印结果:
false
true
继承与原型链(三)
1.这样的其实解决一个问题相同构造函数创建的对象它们的'__proto__' 都指向了构造函数的
对象的都是共享他们构造函数'prototype',就可以利用这点通过原型来解决---------数据共享,节省
内存空间
2.通过下面案例可以看出'a2''a1' 共享了getName方法
function A(name){
    this.name = name
}
undefined
function A(name){
    this.name = name
}
A.prototype.getName = function(){
    return this.name
}
ƒ (){
    return this.name
}
const a1 = new A('a1')
const a2 = new A('a2')

总结继承与原型链
1.这里总结直接使用了参考文章链接中'JavaScript 如何实现继承'的案例
2JavaScript 中,是通过遍历原型链的方式,来访问对象的方法和属性,简单的说就是
我自身没有我就通过'__proto__' 找到我的构造函数的'prototype'上,构造函数原型对象没有
他就去找构造函数上'__proto__' 链接的原型对象一直都没找到的情况下,最后找到null 终止
function Person (name) {
    this.name = name;
}
var lily = new Person("Lily");

小补充对象创建过程

参考

文章参考

帮你彻底搞懂JS中的prototype、__proto__与constructor(图解)
03 | JavaScript 如何实现继承?