听这个名字就很有意思,特意去查资料学习了解,发现他其实只是一个有趣的名词而已,自己的代码中经常用到,但是这么一说就显得贼专业!
动态语言与静态语言
动态语言
又称弱类型语言,动态语言是在运行时确定数据类型的语言。变量使用之前不需要类型声明,通常变量的类型是被赋值的那个值的类型。 例如PHP/ASP/Ruby/Python/Perl/ABAP/SQL/JavaScript/Unix Shell等等。
动态类型语言,就是类型的检查是在运行时做的,是不是合法的要到运行时才判断,例如JavaScript就没有编译错误,只有运行错误。
动态语言,是指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化。比如JavaScript便是一个典型的动态语言。
数据类型可以被忽略的语言。它与强类型定义语言相反, 一个变量可以赋不同数据类型的值。强类型定义语言在速度上可能略逊色于弱类型定义语言,但是强类型定义语言带来的严谨性能够有效的避免许多错误。
静态语言
又称强类型语言,静态语言是在编译时变量的数据类型即可确定的语言,多数静态类型语言要求在使用变量之前必须声明数据类型。 例如:C++、Java、Delphi、C#等。
强制数据类型定义的语言。也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型了。举个例子:如果你定义了一个整型变量a,那么程序根本不可能将a当作字符串类型处理。强类型定义语言是类型安全的语言。
猴子补丁
猴子补丁的含义是指在动态语言中,不去改变源码而对功能进行追加和变更。
猴子补丁这种东西充分利用了动态语言的灵活性,可以对现有的语言Api进行追加,替换,修改Bug,甚至性能优化等等。
JavaScript中使用
由于对象共享原型,因此每一个对象都可以增加、删除或修改原型的属性。这个有争议的实践通常称为猴子补丁。
猴子补丁的吸引力在于其强大。数组缺少一个有用的方法吗?你自己就可以增加它。
Array.prototype.split=function(i){
return [this.slice(0,i),this.slice(i)];
}
很完美,现在可以在任意的数组上调用这个方法了。但当多个库以不兼容的方式给同一个原型打猴子补丁时,另外的库使用同一个方法名给Array.prototype打猴子补丁。
Array.prototype.split=function(){
var i=Math.floor(this.length);
return [this.slice(0,i),this.slice(i)];
}
这样一来,使用split方法很有可能会出错,
解决办法:
- 如果库仅仅是将给原型打猴子补丁作为一种便利,那么可以将这些修改置于一个函数中,用户可以选择调用或忽略。
function addArrayMethods(){ Array.prototype.split=funciton(i){ return [this.slice(0,i),this.slice(i)] } }
- ployfill
if(typeof Array.prototype.map!=="function"){ Array.prototype.map=function(f,thisArg){ var res=[]; for(var i=0,n=this.length;i < n;i++){ res[i]=f.call(thisArg,this[i],i); } return res; } }
猴子补丁使用注意:
- 避免使用轻率的猴子补丁
- 记录程序库所执行的所有猴子补丁
- 考虑通过将修改置于一个导出函数中,使猴子补丁成为可选的
- 使用猴子补丁为缺失的标准API提供polyfills
原型污染
原型污染是指当枚举条目时,可能会导致出现一些在原型对象中不期望出现的属性和方法。
先看例子:
var book = new Array();
book.name = "Love in the Time of Cholera";
book.author = "Garcia Marquez";
book.date = "1985";
alert(book.name); //Love in the Time of Cholera
定义个Array对象,用于管理书本。结果很正确,看似没什么问题,但这个代码很脆弱,一不小心就会遇到原型污染的问题:
//为Array增加两个方法,first和last(猴子补丁后面会介绍)
Array.prototype.first = function() { //获取第一个
return this[0];
};
Array.prototype.last = function() { //获取最后一个
return this[this.length-1];
};
var bookAttributes = []; //定义个book的属性的数组
for (var v in book) { //将上面创建的Array对象book中属性一个个取出来,加入数组中
bookAttributes.push(v);
}
alert(bookAttributes); //name,author,date,first,last
避免原型污染造成影响的解决办法:
- 你可以用
hasOwnProperty
方法,来测试属性是否来自对象而非来自原型对象。 - 当然更好的方式应该是仅仅将Object的直接实例作为字典,而非Array,或Object的子类
当然你可能疑惑:仍旧可以像在Array.prototype中加入猴子补丁一样,在Object.prototype中增加属性,这样不还是会导致原型污染吗?确实如此,但Object对象是JavaScript的根对象,即便技术上能够实现,你也永远不要对Object对象做任何修改。
如果你是做业务项目,上述这些已经足以让你避免原型污染问题了。不过如果你要开发通用的库
,还需要考虑些额外的问题。
比如,你的库中提供has方法,能判断对像中是否有该属性(非来自原型对象的属性),你可能这么做:
Book.prototype.has = function(key) {
return this.elements.hasOwnProperty(key);
};
一切都很完美,但万一有人在对象中有一个自定义的同名的hasOwnProperty属性,这将覆盖掉ES5提供的Object.hasOwnProperty。当然你会认为绝不可能有人会将一个属性起名为hasOwnProperty。但作为通用接口,你最好不做任何假设,可以用call方法改进:
Book.prototype.has = function(key) {
return {}.hasOwnProperty.call(this.elements, key);
};
polyfill和shim的区别
polyfill 是 shim 的一种。
Polyfill你可以理解为“腻子”,就是装修的时候,可以把缺损的地方填充抹平。
有些人就写对应的Polyfill(Polyfill有很多),帮你把这些差异化抹平,不支持的变得支持了(简单来讲,写些代码判断当前浏览器有没有这个功能,没有的话,就写一些支持的补丁代码)。
举个例子,有些旧浏览器不支持Number.isNaN方法,Polyfill就可以是这样的:
if(!Number.isNaN) {
Number.isNaN = function(num) {
return(num !== num);
}
}
shim 是将不同 api 封装成一种,比如 jQuery 的 $.ajax 封装了 XMLHttpRequest 和 IE 用 ActiveXObject 方式创建 xhr 对象;
polyfill 特指 shim 成的 api 是遵循标准的,其典型做法是在IE浏览器中增加 window.XMLHttpRequest ,内部实现使用 ActiveXObject。
在实际中为了方便做对比,会特指 shim 的 api 不是遵循标准的,而是自己设计的。