__proto__ & prototype 关系 & 应用

77 阅读4分钟

本文已参与「 新人创作礼 」活动,一起开启掘金创作之路

关于如何进行图片优化 - 适合的才是最好的

我前面几章节已经说过了,如何实现一个intanceof。 实现这个方法的关键在于获取的原型。

Object.getPrototypeOf() 来获取对象的原型。

例如:/ddd/g.__proto__ 等价于 Object.getPrototypeOf(/ddd/g)

function aa() {
  this.a = 5;
}
let a1 = new aa();
let a2 = new aa();
a1 === a2   // false
a1.__proto__ === a2.__proto__   // true

这个例子说明, 前端的所有引用构造都是通过 __proto__来实现的。

也就是说只有构造的function才有prototype。

官方的解释是:

每个构造函数都是一个函数,它有一个名为“prototype”的属性,用于实现基于原型的继承和共享属性。构造函数创建的每个对象都有一个对其构造函数“prototype”属性值的隐式引用(称为对象的原型)。 当构造函数创建对象时,该对象隐式引用构造函数的原型属性,以解析属性引用。程序表达式构造函数可以引用构造函数的原型属性。原型,添加到对象原型的属性通过继承被共享原型的所有对象共享。另外,也可以使用object使用显式指定的原型创建新对象。

__proto__是每个对象都有的一个属性,而prototype是函数才会有的属性。

__proto__有什么作用呢?

只要是对象,他就具备很多js内置执行的方法 & 应用 也就是官放说到的 built-in

function fn1 (name) { 
  this.name = name;
}

fn1 有toString() 方法、prototype 、__proto__、valueOf

例如你从控制台输出fn1 、跟 fn1.valueOf() 得到的值是一样的。但是这些属性并非直接挂在fn1 上面的。他是通过__proto__来隐式调用的。

虽然这些方法可以通过fn1点出来,但是你并不能通过内置对象来访问。

fn1.name // "fn1"
fn1.toString() //'function fn1 (name) { \n  this.name = name;\n}'

那我们通过

fn1.hasOwnProperty("name")  // true
fn1.hasOwnProperty("length")  // true
fn1.hasOwnProperty("toString") // false

那如何得到fn1 下面有多少个属性呢 ?

for (let o in fn1 ) {
  console.log (o)
}

如何看到对象的显示属性:

//关于对象的遍历
//for...in
//Object.keys(fn1)
//Object.getOwnPropertyNames(fn1)
//Object.getOwnPropertySymbols(fn1)
//Reflect.ownKeys(fn1)

Object.getOwnPropertyNames(fn1)
Reflect.ownKeys(fn1)
// ['length', 'name', 'arguments', 'caller', 'prototype']

那我们如何得到,fn1链上的属性方法呢 ?

//hasOwnProperty 不能直接判断原型链的数据
Reflect.ownKeys(fn1.__proto__) //['length', 'name', 'arguments', 'caller', 'constructor', 'apply', 'bind', 'call', 'toString', Symbol(Symbol.hasInstance)]
Reflect.ownKeys(fn1.prototype) //['constructor']
//Object.getOwnPropertyNames也是可以获取的

那为啥fn1.__proto__不能直接的循环出来呢

任何的隐式属性多可以通过getOwnPropertyNames Reflect.ownKeys 得到。

那我们如何利用好,prototype 原型链来做一个事情呢?

let arr = [];
arr.__proto__.www = function (a)  { this.push(null);  this.push(a)}
//等同于
Array.prototype.www = function (a)  { this.push(null);  this.push(a)}
//这也就是
arr1.__proto__ === Array.prototype   //true

看到这个,我们看到很多框架他们都是自己的构造类型。那我们改如何做呢?

function customNiubi(age) {
  this.age = age;
  this.format = ()=>{
    console.log ("format")
  }
}
//如何去定义一个隐私属性呢
customNiubi.prototype.private1 = "p1";
let cus1 = new customNiubi( 18 )
console.log (cus1.age , cus1.private1)  //18 'p1'

这里我么可以打出p1属性。

cus1.hasOwnProperty("private1") //false
Reflect.ownKeys( cus1.__proto__ ) // (2) ['constructor', 'private1']

这就是如何去构造一个隐私方法。在便利的时候无需吐出来。

还有一句话大家要记住,所有的constructor 来源于函数对象。

只要是对象都具有constructor

例如:

"sss".constructor   //ƒ String() { [native code] }
String.constructor // ƒ Function() { [native code] }
Function.constructor //ƒ Function() { [native code] }

所以还有一句话是,所有的实例函数,指向的原函数本身。

如果你自定义的隐私属性,就需要从自己的prototype去实现, 不要去通过__proto__去改造。这样会破会整个链路数据透明性。

/a/g.__proto__ === RegExp.prototype // true
RegExp.__proto__ === Function.prototype // true
RegExp.constructor === Function  // true

上面这段代码,也就彻底说清楚, 复杂数据类型的如何定义。

一切的复杂数据类型都是可以通过function来进行构造的。其宗旨就是一个Function。

__proto__constructor属性是对象所独有的;prototype属性是函数所独有的,因为函数也是一种对象,所以函数也拥有__proto__constructor属性。

__proto__属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,一直找,直到__proto__属性的终点null,再往上找就相当于在null上取值,会报错。通过__proto__属性将对象连接起来的这条链路即我们所谓的原型链。