你不知道的JavaScript--Item13 理解 prototype, getPrototypeOf 和__proto___getprototypeof __proto__

为了充分了解获取原型的各种方式,以下是一个例子:

function User(name, passwordHash) {  
    this.name = name;  
    this.passwordHash = passwordHash;  
}  
User.prototype.toString = function() {  
    return "[User " + this.name + "]";  
};  
User.prototype.checkPassword = function(password) {  
    return hash(password) === this.passwordHash;  
};  
var u = new User("sfalken", "0ef33ae791068ec64b502d6cb0191387");  

User函数拥有一个默认的prototype属性,该属性的值是一个空对象。在以上的例子中,向prototype对象添加了两个方法,分别是toString和checkPassword。当调用User构造函数得到一个新的对象u时,它的原型对象会被自动赋值到User.prototype对象。即u.prototype === User.prototype会返回true。

User函数,User.prototype,对象u之间的关系可以表示如下:

这里写图片描述

上图中的箭头表示的是继承关系。当访问u对象的某些属性时,会首先尝试读取u对象上的属性,如果u对象上并没有这个属性,就会查找其原型对象。

比如当调用u.checkPassword()时,因为checkPassword定义在其原型对象上,所以在u对象上找不到该属性,就在u的原型上查找,查找顺序是u-> u.prototype(User.prototype)。

前面提到过,getPrototypeOf方法是ES5中用来得到某个对象的原型对象的标准方法。因此:

Object.getPrototypeOf(u) === User.prototype; // true  

在一些环境中,同时提供了一个非标准的*_ proto _* 属性用来得到某个对象的原型对象。当环境不提供ES5的标准方法getPrototypeOf方法时,可以暂时使用该属性作为替代。可以使用下面的代码测试环境中是否支持*_ proto _*:

u._\_ proto _\_ === User.prototype; // true  

所以在JavaScript中,类的概念是由构造函数(User)和用于实例间共享方法的原型对象(User.prototype)共同完成的。构造函数中负责构造每个对象特有的属性,比如上述例子中的name和password属性。而其原型对象中负责存放所有对象共有的属性,比如上述例子中的checkPassword和toString方法。就像下面这张图表示的那样:

这里写图片描述

2、获取对象优先使用Object.getPrototypeOf,而不是*_ proto _*

在ES5中引入了Object.getPrototypeOf作为获取对象原型对象的标准API。但是在很多执行环境中,也提供了一个特殊的*_ proto _*属性来达到同样的目的。

因为并不是所有的环境都提供了这个*_ proto _*属性,且每个环境的实现方式各不相同,因此一些结果可能不一致,例如,对于拥有null原型的对象:

// 在某些环境中  
var empty = Object.create(null); // object with no prototype 
"\_proto \_" in empty; // false (in some environments) 

// 在某些环境中 
var empty = Object.create(null); // object with no prototype 
"\_proto\_" in empty; // true (in some environments) 

所以当环境中支持Object.getPrototypeOf方法时,优先使用它。即使不支持,也可以为了实现一个:

if (typeof Object.getPrototypeOf === "undefined") {  
    Object.getPrototypeOf = function(obj) {  
        var t = typeof obj;  
        if (!obj || (t !== "object" && t !== "function")) {  
            throw new TypeError("not an object");  
        }  
        return obj._proto_;  
    };  
}  

上述代码首先会对当前环境进行检查,如果已经支持了Object.getPrototypeOf,就不会再重复定义。

3、绝不要修改*_ proto _*

和Object.getPrototypeOf相比,*_ proto _*的特殊之处还体现在它能够修改一个对象的原型继承链。因为它是一个属性,除了执行获取它的操作外,还能够对它进行设置。

但是,绝不要修改*_ proto _*。原因如下:

  • 首先,最显而易见的原因就是便携性。因为不是所有的JavaScript执行环境都支持这一属性,所以使用了*_ proto _* 之后,代码就不能在那些不支持*_ proto _*的环境中运行了。
  • 其次,是性能上的考虑。现在的JavaScript引擎的实现都会针对对象属性的存取作出大量的优化,因为这些操作是最常用的。当修改了对象的*_ proto _*后,就相当于修改了对象的整个继承结构,这样做回导致很多优化都不再可用。
  • 最后,最重要的原因是需要保证程序的可靠性。因为改变*_ proto _*属性后,对象的原型继承链也许会被完全地改变。当程序中有其他代码依赖于原来的继承链时,就会出现不可意料的错误。通常而言,原型继承链需要保持稳定。

当需要为一个新创建的对象赋予一个原型对象时,可以使用ES5提供的Object.create方法。对于未实现ES5标准的环境,可以给出来一个不依赖于*_ proto _*的Object.create方法的实现。

4、解决 _ proto _兼容问题, 让构造函数不再依赖new关键字

在将function当做构造函数使用时,需要确保该函数是通过new关键字进行调用的。

function User(name, passwordHash) {  
    this.name = name;  
    this.passwordHash = passwordHash;  
}  

如果在调用上述构造函数时,忘记了使用new关键字,那么:

var u = User("baravelli", "d8b74df393528d51cd19980ae0aa028e");  
u; // undefined 
this.name; // "baravelli" 
this.passwordHash; // "d8b74df393528d51cd19980ae0aa028e" 

可以发现得到的u是undefined,而this.name以及this.passwordHash则被赋了值。但是这里的this指向的则是全局对象。

如果将构造函数声明为依赖于strict模式:

function User(name, passwordHash) {  
 "use strict";  
    this.name = name;  
    this.passwordHash = passwordHash;  
}  
var u = User("baravelli", "d8b74df393528d51cd19980ae0aa028e");  
// error: this is undefined 

那么在忘记使用new关键字的时候,在调用this.name= name的时候会抛出TypeError错误。这是因为在strict模式下,this的默认指向会被设置为undefined而不是全局对象。

那么,是否有种方法能够保证在调用一个函数时,无论使用了new关键字与否,该函数都能够被当做构造函数呢?下面的代码是一种实现方式,使用了instanceof操作:

function User(name, passwordHash) {  
    if (!(this instanceof User)) {  
        return new User(name, passwordHash);  
    }  
    this.name = name;  
    this.passwordHash = passwordHash;  
}  

var x = User("baravelli", "d8b74df393528d51cd19980ae0aa028e");  
var y = new User("baravelli", "d8b74df393528d51cd19980ae0aa028e");  
x instanceof User; // true 
y instanceof User; // true 

以上的if代码块就是用来处理没有使用new进行调用的情况的。当没有使用new时,this的指向并不是一个User的实例,而在使用了new关键字时,this的指向是一个User类型的实例。

另一个更加适合在ES5环境中使用的实现方式如下:

function User(name, passwordHash) {  
    var self = this instanceof User ? this : Object.create(User.prototype);  
    self.name = name;  
    self.passwordHash = passwordHash;  
    return self;  


### 最后

本人分享一下这次字节跳动、美团、头条等大厂的面试真题涉及到的知识点,以及我个人的学习方法、学习路线等,当然也整理了一些学习文档资料出来是给大家的。知识点涉及比较全面,包括但不限于**前端基础,HTML,CSS,JavaScript,Vue,ES6,HTTP,浏览器,算法等等**


![](https://img-blog.csdnimg.cn/img_convert/a57a9b7a47e82a6b021bc8e121f09651.webp?x-oss-process=image/format,png)

**前端视频资料:**
人分享一下这次字节跳动、美团、头条等大厂的面试真题涉及到的知识点,以及我个人的学习方法、学习路线等,当然也整理了一些学习文档资料出来是给大家的。知识点涉及比较全面,包括但不限于**前端基础,HTML,CSS,JavaScript,Vue,ES6,HTTP,浏览器,算法等等**


[外链图片转存中...(img-bPIkQ7Jt-1720118193779)]

**前端视频资料:**
![](https://img-blog.csdnimg.cn/img_convert/651f653ef3bdadae0e16a137999dc939.webp?x-oss-process=image/format,png)
  • 18
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值