本文已参与「 新人创作礼 」活动,一起开启掘金创作之路
我前面几章节已经说过了,如何实现一个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__
属性将对象连接起来的这条链路即我们所谓的原型链。