JavsScript二三事(1)

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)->.......),导致溢出。类似下面图片。

JavsScript二三事(1)_第1张图片
1.png

解决的方法就是给自定义函数命个与浏览器自带函数不一样的名称,类似下面这种。

版本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();

执行结果是:

JavsScript二三事(1)_第2张图片
2.png

也就是说,即使匿名函数被定义在一个非window对象里面,它指向的也是window。不过虽说函数指向的是window但是它还是能够访问外层函数的属性的,这就牵扯出了变量作用域。千万别把this、变量作用域、原型链弄混。来看看下面的演示:

JavsScript二三事(1)_第3张图片
3.png

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;
}

你可能感兴趣的:(JavsScript二三事(1))