Section A
标签:代码优化 闭包
下面有三个给元素添加事件的浏览器兼容版本,这三个版本代表着不同的效率。
版本1:
var addEvent = function(ele, type, listener, useCapture) {
if(ele.addEventListener) {
return ele.addEventListener(type, listener, useCapture);
} else {
return ele.addEvent('on' + type, listener);
}
}
版本2:
var addEvent = (function() {
if(document.addEventListener) {
return function(ele, type, listener, useCapture) {
ele.addEventListener(type, listener, useCapture);
}
} else {
return function(ele, type, listener, useCapture) {
ele.attachEvent('on' + type, listener);
}
}
})();
版本3:
var addEvent = document.addEventListener ?
function(ele, type, listener, useCapture) {
ele.addEventListener(type, listener, useCapture);
} :
function() {}unction(ele, type, listener, useCapture) {
ele.attachEvent('on' + type, listener);
};
可以看出第一个版本简单清晰,也是是最常见的写法:当给元素添加事件时判断浏览器是否支持addEventListener如果不支持使用addEvent。但是这样做的话,每次给元素添加事件都要做一次兼容性判断,而这显然是没必要的。所幸,函数的功能无比强大,让我们看看版本二。版本二中,当外层函数执行到给addEvent赋值时,匿名函数会对addEventListener兼容性做出判断,并返回一个相应的函数。
在支持addEventListener的环境下:
addEvent = function(ele, type, listener, useCapture) {
ele.addEventListener(type, listener, useCapture);
}
不支持的环境下:
addEvent = function(ele, type, listener, useCapture) {
ele.attachEvent('on' + type, listener);
};
这样就不需要每次调用addEvent都做判断了。但是这样写看起来不美观,也难以理解。Javascript给我们提供个三元操作符A ? a : b; 它会将A转换为Bool值进行判断,如果为真则该表达式变为a,为假则表达式变为b。于是有了版本三,我们用该三元操作符替换了外层包裹的匿名自执行函数,它返回与版本二同样的值,简单明了。
Section B
标签:常见错误 函数命名 无限递归
下面是获取元素实际样式getComputedStyle的兼容性版本实现。下面是版本一。
版本1:
window.getComputedStyle = function(element) {
if(window.getComputedStyle) {
return window.getComputedStyle;
} else {
return element.currentStyle;
}
}
版本一是很多人的都会写成的版本。但是这个版本却是错误的,在调用该函数后,会出现溢出。为什么呢?上面逻辑很正确啊:首先判断浏览器是否支持getComputedStyle,然后返回浏览器自己支持的属性或函数。先来看下面这个例子。
function haha() {
if(window.haha) alert(1);
}
haha();
在浏览器执行,浏览器会弹出一个1,能看出什么吗?其实当我们声明了haha函数,浏览器就已经存在window.haha了,所以执行到haha()里面的if(window.haha),结果必为真,于是乎这个if就显得没有任何意义了。上面的getComputedStyle也一样,由于我们使用了window对象自带的getComputedStyle作为自定义函数名,我们自定义的getComputedStyle函数是替代了原有函数的,于是执行我们自定义的函数返回的结果一定是window.getComputedStyle。于是,我们造就了个无限循环函数(getComputedStyle(elment)->getComputedStyle(element)->.......),导致溢出。类似下面图片。
解决的方法就是给自定义函数命个与浏览器自带函数不一样的名称,类似下面这种。
版本2:
window.getElementStyle = function(element) {
if(window.getComputedStyle) {
return window.getComputedStyle;
} else {
return element.currentStyle;
}
}
所以有一点要注意,避免让自定义的轮子与原本的轮子重名,除非你要改写原本的轮子,并保证不使用原有轮子。
Section C
标签:匿名函数 this
下面是个匿名函数有关的有趣的点:
(function haha() {
if(window.haha) alert(1);
})();
还记得上面有个类似的函数吗,不同的是,这里我用括号把它包裹起来了,于是它...并没有弹出1,也就意味着被包裹起来的haha并不是window对象直接的一员(插一下javascript执行顺序,javascript是使用静态作用域的,函数在执行前会先将变量初始化)。事实上haha并不是window对象直接的一员,而是匿名函数的一员。当要执行该匿名函数时,会首先初始化该匿名函数,在内部声明叫hahad的函数,匿名函数则指向window对象。而又一个事实是,匿名函数的外部环境一定是window对象的,如何证明这点呢?看下面的例子。
var obj = {
foo: function() {
console.log(this);
(function haha() {
console.log(this);
if(window.haha) alert(1);
})();
}
}
obj.foo();
执行结果是:
也就是说,即使匿名函数被定义在一个非window对象里面,它指向的也是window。不过虽说函数指向的是window但是它还是能够访问外层函数的属性的,这就牵扯出了变量作用域。千万别把this、变量作用域、原型链弄混。来看看下面的演示:
Section D
标签:this 变量作用域 原型链
this是在函数内部使用的一个指针,指向调用函数的对象。Javascript变量作用域是在程序源码中定义该变量的区域。很显然this指向的对象对应了某个作用域。
变量作用域在源码中就是被定义了的,并且它们一起构成强大的作用域链。于是乎一个函数天生就能访问外部函数的变量,我称这种能力为“先天异能”。这种异能甚是强大,如果把属性必做财产,方法比做打手,那么子函数就拥有使唤父辈财产和打手的权利。不过美中却有不足,比如,子函数对同名变量的访问是有顺序的,也就是说如果函数A的上层函数B中定义了var a = 10;A自己也定义了var a = 6; 那么A函数conosle.log(a)打印出的会是6而不是10。那么如果函数A想操作的是爸爸B的财产a该怎么办呢?this能够很好的解决这个问题。
相比于作用域链,我称this为“后天异能”,它有寻根溯源的能力。我们能够通过this访问this指向对象的属性与方法,因此this必须是可变的。不过this只有一个,而且它是函数内部自动生成的,要怎样使用这么珍贵的技能呢?.....(未完)
尾
写累了。。。待续。。。。。
(续:继续BB this,扯一扯原型链,或许还能扯出其他什么东西,我总是这样。下面是为下篇准备的代码,牵扯到自定义浏览器属性访问 函数绑定 高阶函数 函数柯里化)
var Duck = {
type: 'duck',
speak: function() {
alert('我是' + this.type);
}
}
function createObj(pro) {
if(Object.create) return Object.create(pro);
var F = new Function();
F.prototype = pro;
return new F;
}
var duck = createObj(Duck);
duck.speak();
HTMLElement.prototype.__defineGetter__('children', function() {
return Array.prototype.filter.call(this.childNodes, function(node) {
return node.nodeType === 1;
});
});
错误版本:
HTMLElement.prototype.__defineGetter__('children', function() {
if(this.children) return this.children;
else return Array.prototype.filter.apply(this.childNodes, function(node) {
return node.nodeType === 1;
});
});
window.__defineGetter__('foo', function() {
alert(1);
});
window.foo;
function foo() {
alert(1);
}
foo.apply(null);
function foo() {
alert(1);
}
var a = foo.bind(null);
a();
Object.prototype.bind = function() {
var _this = this,
_target = arguments[0],
_args = [].prototype.slice.call(arguments, 1);
return function() {
this.apply(_target, _args);
}
}
function reduce(fn, base, array) {
array.forEach(function(ele) {
base = fn(ele, base);
});
return base;
}
function forEach(fn, array) {
for(var i = 0; i < array.length; i++) {
fn(array[i]);
}
}
function sum(array) {
function add(a, b) {return a + b;};
return reduce(add, 0, array);
}
function add(m) {
var total = m;
var helper = function(n) {
if(n == undefined) {return total}
else {
total += n;
return helper;
}
}
helper.valueOf = function() {
return total;
}
return helper;
}