之前的43条,44条讨论了属性的枚举,但都没有彻底地解决属性查找中原型污染的问题。看下面关于字典的一些操作

'zhangsan' in dict;
dict.zhangsan;
dict.zhangsan=22;

js的对象操作总是经继承的方式工作的。即使是一个空的对象字面量也是继承了Object.protoype属性。

var dict={};
'zhangsan' in dict;//false
'lisi' in dict;//false
'wangwu' in dict;//false'
toString' in dict;//true'
valueOf' in dict;//true

无法避免从Object.prototype对象继承方法。

去除原型污染

Object.prototype提供了hasOwnProperty方法。当测试字典条目时,它可以避免原型污染,这正好可以解决之前的问题。看一段代码

dict.hasOwnProperty('zhangsan');//false
dict.hasOwnProperty('toString');//false
dict.hasOwnProperty('valueOf');//false

可以利用上面代码的特性,来对属性查找使用,可以避免其受原型污染

dict.hasOwnProperty('zhangsan')?dict.hasOwnProperty('zhangsan'):undefined;
dict.hasOwnProperty('x')?dict.hasOwnProperty('x'):undefined;

这里还有一个问题,我们是使用dict对象的hasOwnProperty方法,但其实它自身并没有这个方法,而是继承自Object.prototype对象。如果dict字典对象有一个同为"hasOwnProperty"名称的属性,那么原型中的hasOwnProperty方法不会被访问到。这里会优先读取自身包含的属性,找不到才会从原型链中查找。

dict.hasOwnProperty=10;
dict.hasOwnProperty('zhangsan');//这里会产生一个错误

虽然字典很少会存储这样的属性名。但小概率事件也会发生,因为你不知道处理的数据,是不是来自第三方。下面就介绍一种最安全的方法,不做任何假设。这里不用字典对象来访问hasOwnProperty方法,可能被改写。

使用Object.prototpye.hasOwnProperty

这里直接使用Object.prototype中的hasOwnProperty方法,然后使用函数的call方法,把函数的接收者绑定到字典对象。
首先,先提取出hasOwnProperty方法

var hasOwn=Object.prototype.hasOwnProperty;

var hasOwn={}.hasOwnProperty;

然后,确定函数运行的接收者,使用call方法来指定

hasOwn.call(dict,'zhangsan');

这里就可以安全地调用Object.prototype.hasOwnProperty方法来对字典对象的属性名进行检测了。

var dict={};
dict.zhangsan=12;
hasOwn.call(dict,'hasOwnProperty');//false
hasOwn.call(dict,'zhangsan');//true dict.hasOwnProperty=10;
hasOwn.call(dict,'hasOwnProperty');//true
hasOwn.call(dict,'zhangsan');//true

为了避免所以地方都插入上面的检测代码,可以把该模式抽象为Dict的方法。

Dict初级版

Dict构造函数封装了所有在单一数据类型定义中编写健壮字典的技术细节。代码好下

function Dict(elements){
this.elements=elements||{};
}
Dict.prototype.has=function(key){
return {}.prototype.hasOwnProperty.call(this.elements,key);
};
Dict.prototype.get=function(key){
return this.has(key)?this.elements[key]:undefined;
};
Dict.prototype.set=function(key,val){
this.elements[key]=val;
};
Dict.prototype.remove=function(key){
delete this.elements[key];
};

这样的实现比使用js默认的对象语法更健壮,而且也同样方便使用。

var dict=new Dict({
zhangsan:12,
lisi:23,
wangwu:40
});
dict.has('zhangsang');//true
dict.has('lisi');//true
dict.has('toString');//false

__proto__自身污染

之前在44条中提到,在一些特殊js环境中,特殊的属性名__proto__可能导致自身的污染问题。在某些环境中,__proto__只是简单地继承自Object.prototype,因此空对象是真正的空对象。
一些环境下

var empty=Object.create(null);
'__proto__' in empty;//false
var hasOwn={}.hasOwnProperty;
hasOwn.call(empty,'__proto__');//false

在其它环境下

var empty=Object.create(null);
'__proto__' in empty;//true
var hasOwn={}.hasOwnProperty;
hasOwn.call(empty,'__proto__');//false

某些环境下
因为存在一个实例属性__proto__而永久污染所有的对象

var empty=Object.create(null);
'__proto__' in empty;//true
var hasOwn={}.hasOwnProperty;
hasOwn.call(empty,'__proto__');//true

在不同的环境中,__proto__的值无法确定

var dict=new Dict();
dict.has('__proto__');//无法确定

为了达到代码的可移植性和安全性,只能对__proto__关键字增加一些操作。

Dict最终版

下面就是Dict更安全、更复杂的最终实现。

function Dict(elements){
this.elements=elements||{};
this.hasSpecialProto=false;
this.specialProto=undefined;
}
Dict.prototype.has=function(key){
if(key === '__proto__'){
return this.hasSpecialProto;
}
return {}.prototype.hasOwnProperty.call(this.elements,key);
};
Dict.prototype.get=function(key){
if(key === '__proto__'){
return this.specialProto;
}
return this.has(key)?this.elements[key]:undefined;
};
Dict.prototype.set=function(key,val){
if(key === '__proto__'){
this.hasSpecialProto=true;
this.specialProto=val;
}else{
this.elements[key]=val;
}
};
Dict.prototype.remove=function(key){
if(key === '__proto__'){
this.hasSpecialProto=false;
this.specialProto=undefined;
}else{
delete this.elements[key];
}
};

不管环境处不处理__proto__属性,以上代码都能工作。

var dict=new Dict();
dict.has('__proto__');//false

提示

  • 使用hasOwnProperty方法避免原型污染

  • 使用词法作用域和call方法避免覆盖hasOwnProperty方法

  • 考虑在封装hasOwnProperty测试样板代码的类中实现字典操作

  • 使用字典类避免将'__proto__'作为key来使用

[Effective JavaScript 笔记]第45条:使用hasOwnProperty方法以避免原型污染的更多相关文章

  1. [Effective JavaScript 笔记]第50条:迭代方法优于循环

    "懒"程序员才是好程序员.复制和粘贴样板代码,一但代码有错误,或代码功能修改,那么程序在修改的时候,程序员需要找到所有相同功能的代码一处处进行修改.这会使人重复发明轮子,而且在别人 ...

  2. [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象

    js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...

  3. [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符

    “1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...

  4. [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码

    函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...

  5. [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法

    js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...

  6. [Effective JavaScript 笔记]第20条:使用call方法自定义接收者来调用方法

    不好的实践 函数或方法的接收者(即绑定到特殊关键字this的值)是由调用者的语法决定的.方法调用语法将方法被查找的对象绑定到this变量,(可参阅之前文章<理解函数调用.方法调用及构造函数调用之 ...

  7. [Effective JavaScript 笔记]第35条:使用闭包存储私有数据

    js的对象系统并没有特别鼓励或强制信息隐藏.所有的属性名都是一个字符串,任意一个程序都可以简单地通过访问属性名来获取相应的对象属性.例如,for...in循环.ES5的Object.keys()和Ob ...

  8. [Effective JavaScript 笔记]第41条:将原型视为实现细节

    对象原型链 一个对象给其使用者提供了轻量.简单.强大的操作集.使用者与一个对象最基本的交互是获取其属性值和调用其方法.这些操作不是特别在意属性存储在原型继承结构的哪个位置.随着时间推移,实现对象时可能 ...

  9. [Effective JavaScript 笔记]第43条:使用Object的直接实例构造轻量级的字典

    js对象的核心是一个字符串属性名与属性值的映射表.使用对象实现字典易如反掌,字典是可变长的字符串与值的映射集合. for...in js提供了枚举一个对象属性名的利器--for...in循环. var ...

随机推荐

  1. *HDU3038 并查集

    How Many Answers Are Wrong Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Ja ...

  2. Javascript的shift()和push(),unshift()和pop()方法简介

    栈方法: Javascript为数组专门提供了push()和pop()方法,以便实现类似栈的行为.来看下面的例子: var colors=new Array();       //创建一个数组 var ...

  3. C++ 画星号图形——空心矩形(核心代码记录)

    int mi=(int)a; int mj=(int)b; ;i<mi;i++) { ;j<mj;j++) { ||i==mi-) cout<<"*"; | ...

  4. 使用非Web方式从CA申请证书

    背景介绍:关于从CA申请证书这点事,网上的那些教程基本都是让我们通过访问https://server/certsrv这样的网页来操作的,我一直希望不依赖IIS就把这事干了,于是就有了下面的文章. 1. ...

  5. http详解

    mac地址是网卡出厂的时候给定的固定地址.ip是当前节点被分配的地址,mac和ip相互配对: ip间通信- http本身是无状态无连接的,因为有这样的特性才能让他适用于当今这么复杂的网络环境. 但是有 ...

  6. vim正则表达式~转

    vim正则表达式(转) Vim中的正则表达式功能很强大,如果能自由运用,则可以完成很多难以想象的操作. 如果你比较熟悉Perl的正规表达式,可以直接参照与Perl正则表达式的区别一节. 一.使用正则表 ...

  7. 国家以及国家语言的json数据格式,提供给网友参考。

    目前接触到一个需求,需要全球主要国家的选择以及语言的选择,如下图: 这是国家选项 这是语言的选项: 很简单有木有? 本来打算直接给文件,但是好像没有文件上传,所以就提供一个思路和代码,你们照着弄吧. ...

  8. 网页语言有html,php.jsp,无论什么语言浏览器总是能正常显示,这个解析工作是浏览器完成的吗?

    不是,浏览器最基本的语言是html也就是说浏览器只看得懂html.css.js等其他的服务器端动态脚本,比如你说的php.jsp等,解析工作是在服务器完成的!打个比方,你在电脑显示屏上看到的一切东西, ...

  9. linux下文件搜索命令学习笔记

    1. locate:按照文件名搜索文件 locate filename 与find在整个操作系统中遍历搜索不同,locate命令在/var/lib/mlocate这个后台数据库中按照文件名搜索,所以优 ...

  10. LightOj 1298 - One Theorem, One Year(DP + 欧拉)

    题目链接:http://lightoj.com/volume_showproblem.php?problem=1298 题意:给你两个数 n, p,表示一个数是由前 k 个素数组成的,共有 n 个素数 ...