/js-ninja

Reading notes for JavaScript Ninja

JavaScript忍者秘籍

简介

本文参考John Resig和Bear Bibeault的《JavaScript忍者秘籍》,是为方便未来学习和查阅JavaScript而创建的代码笔记。

目录

  1. 调试
  2. 函数
  3. 函数续
  4. 闭包
  5. 原型与面向对象
  6. 正则表达式

1. 调试

  • 用于所有现代浏览器的日志记录
function log() {
    try {
        console.log.apply(console, arguments);
        // 用console.log记录日志信息
    } catch (e) {
        try {
            opera.postError.apply(opera, arguments);
            // 捕获失败,尝试过时版本的opera的专有方法记录
        } catch (e) {
            alert(Arrary.prototype.join.call(arguments, " "));
            // 如果都不行 使用alert()函数
        }
    }
}
  • 用于测试jQuery的DOM测试用例
<script src="dist/jquery.js"></script>
<script>
$(document).ready(function() {
    $("#test").append("test");
});
</script>
<style>
#test {
    width: 100px;
    height: 100px;
    background: red;
}
</style>
<div id="test"></div>
  • 测试框架:QUnit | YUI Test | JSUnit
  • Javascript assert()的简单实现
<html>
<head>
    <title>Test Suite</title>
    <script>
    function assert(value, desc) {
        var li = document.createElement("li");
        li.className = value ? "pass" : "fail";
        li.appendChild(document.craeteTextNode(desc));
        document.getElementById("results").appendChild(li);
    }
    window.onload = function() {
        assert(true, "The test suite is running.");
        assert(false, "Fail!");
    };
    </script>
    <style>
    #result li.pass {
        color: green;
    }
    #result li.fail {
        color: red;
    }
    </style>
</head>
<body>
    <ul id="results"></ul>
</body>
</html>
  • 测试分组
<html>

<head>
    <title>Test Suite</title>
    <script>
    (function() {
        var results;
        this.assert = function assert(value, desc) {
            var li = document.createElement("li");
            li.className = value ? "pass" : "fail";
            li.appendChild(document.craeteTextNode(desc));
            document.getElementById("results").appendChild(li);
            if (!value) {
                li.parentNode.parentNode.className = "fail";
            }
            return li;
        };
        this.test = function test(name, fn) {
            results = document.getElementById("results");
            results = assert(true, name).appendChild(document.createElement("ul"));
            fn();
        };
    })();

    window.onload = function() {
        test("A test.", function() {
            assert(true, "First assertion completed");
            assert(true, "Second assertion completed");
            assert(true, "Third assertion completed");
        });
        test("Another test.", function() {
            assert(true, "First assertion completed");
            assert(false, "Second test failed");
            assert(true, "Third assertion completed");
        });
        test("A third test.", function() {
            assert(null, "fail");
            assert(5, "pass");
        });
    };
    </script>
    <style>
    #result li.pass {
        color: green;
    }

    #result li.fail {
        color: red;
    }
    </style>
</head>

<body>
    <ul id="results"></ul>
</body>

</html>
  • 异步测试
<html>

<head>
    <title>Test Suite</title>
    <script>
    (function() {
        var quenue = [],
            paused = false,
            results;
        this.test = function(name, fn) {
            quenue.push(function() {
                results = document.getElementById("results");
                results = assert(true, name).appendChild(document.createElement("ul"));
                fn();
            });
            runTest();
        };
        this.pause = function() {
            paused = true;
        };
        this.resume = function() {
            paused = false;
            setTimeOut(runTest, 1);
        };

        function runTest() {
            if (!paused && quenue.length) {
                quenue.shift()();
                if (!paused) {
                    resume();
                }
            }
        }

        this.assert = function assert(value, desc) {
            var li = document.createElement("li");
            li.className = value ? "pass" : "fail";
            li.appendChild(document.craeteTextNode(desc));
            results.appendChild(li);
            if (!value) {
                li.parentNode.parentNode.className = "fail";
            }
            return li;
        };
    })();
    window.onload = function() {
        test("Async Test #1", function() {
            pause();
            setTimeOut(function() {
                assert(true, "First test completed");
                resume();
            }, 1000);
        });
        test("Async Test #2", function() {
            pause();
            setTimeOut(function() {
                assert(true, "Second test completed");
                resume();
            }, 1000);
        });
    };
    </script>
    <style>
    #result li.pass {
        color: green;
    }

    #result li.fail {
        color: red;
    }
    </style>
</head>

<body>
  <ul id="results"></ul>
</body>

</html>

返回目录


2. 函数

  • 使用断言测试函数声明
<script type = "text/javascript" >
// 声明一个命名函数
function isNimble() { return true; }
assert(typeof window.isNimble === "function", "isNimble() defined");
assert(typeof isNimble.name === "isNimble", "isNimble() has a name");

// 创建匿名函数,并赋值给变量
var canFly = function() { return true; };
assert(typeof window.canFly === "function", "canFly() defined");
assert(canFly.name = "", "canFly() has no name");

// 创建匿名函数,并引用到window的一个属性上
window.isDeadly = function() { return true; };
assert(typeof window.isDeadly === "function", "isDeadly() defined");

function outer() {
    assert(typeof inner === "function", "inner() in scope before declaration");

    function inner() {}
    assert(typeof inner === "function", "inner() in scope after declaration");
    assert(window.inner === undefined, "inner() not in global scope");
}
// outer()可以在全局作用域访问到,inner()不行
outer();
assert(window.inner === undefined, "inner() still not in global scope");

// 真正起控制作用的是该函数的真正的字面量名称
window.wieldSword = function swingSword() { return true; };
assert(window.wieldSword.name === 'swingSword', "wieldSword's real name is swingSword"); 
</script>
  • 作为函数调用与作为方法调用
<script type="text/javascript">
function creep() { return this; }
// 作为函数进行调用并验证该函数上下文是全局作用域
assert(creep() === window, "creeping in window");
var sneak = creep;
// 使用sneak变量调用函数
assert(sneak() === window, "sneaking in window");
var ninja = {
skulk: creep
};
// 通过skulk属性调用,creep()作为ninja的一个方法进行调用
assert(ninja.skulk() === ninja, "The 1st ninja is skulking");
</script>
  • 使用构造器进行调用 创建一个名为Ninja()的函数,该函数将设置ninja的skulk技能,用于构建ninjas。
<script type="text/javascript">
funtion Ninja() {
    this.skulk = function() { return this; };
}
var ninja1 = new Ninja();
var ninja2 = new Ninja();
assert(ninja1.skulk() === ninja1, "The 1st ninja is skulking");
assert(ninja2.skulk() === ninja2, "The 2nd ninja is skulking");
</script>

作为方法进行调用,该上下文是方法的拥有者;作为全局函数进行调用,上下文永远是window,作为构造器进行调用,其上下文是新创建的对象实例。在函数调用时,JavaScript提供了apply()和call()方法,可以显示指定任何一个对象为其函数的上下文。

  • 使用apply()和call()方法指定函数上下文
<script type="text/javascript">
  function juggle() {
      var result = 0;
      for (var n = 0; n < arguments.length; n++) {
          result += arguments[n];
      }
      this.result = result;
  }
  var ninja1 = {};
  var ninja2 = {};
  juggle.apply(ninja1, [1, 2, 3, 4]);
  juggle.call(ninja2, 5, 6, 7, 8);
  assert(ninja1.result === 10, "juggled via apply");
  assert(ninja2.result === 26, "juggled via call");
</script>

call()和apply()功能基本相同。如果在变量里有很多无关的值或者是指定为字面量,使用call()方法可以直接将其作为参数列表传进去。但是如果这些参数,已经在一个数组里,或者容易收集到数组里,apply()是更好的选择。

返回目录


3. 函数续

  • 使用匿名函数的示例
<script type="text/javascript">
  // 为load事件创建一个匿名函数作为事件处理程序
  window.onload = function() {
    assert(true, 'power!');
  };
  // 将其作为ninja的一个方法,使用shout属性调用
  var ninja = {
    shout: function() {
      assert(true, "Ninja");
    }
  };
  ninja.shout();
  // 作为参数传递给window对象的setTimeOut()函数
  setTimeOut(function() {
    assert(true, 'Forever!');
  }, 500);
</script>
  • 使用内联函数进行递归
<script type="text/javascript">
  var ninja = {
    // 定义内联函数signal 在函数体内使用名称进行递归调用
    chrip: function signal(n) {
      return n > 1 ? signal(n - 1) + "-chrip" : "chrip";
    }
  };
  // signal作为ninja对象的方法调用正常
  assert(ninja.chrip(3) == "chrip-chrip-chrip", "Works as we would expect!");
  // 将函数的引用复制给samurai
  var samurai = {
    chrip: ninja.chrip
  };
  // 清空ninja对象
  ninja = {};
  // 清除ninja的chrip属性 不影响内联函数用名字进行递归调用
  assert(samurai.chrip(3) == "chrip-chrip-chrip", "The method correctly calls itself.");
</script>
  • 缓存记忆计算过的结果
<script type="text/javascript">
  function isPrime(value) {
  	// 创建缓存
    if (!isPrime.answers) isPrime.answers = {};
    if (isPrime.answers[value] != null) {
      return isPrime.answers[value];
    }
    var prime = value != 1;
    for (var i = 2; i < value; i++) {
      if (value % i == 0) {
        prime = false;
        break;
      }
    }
    // 没有缓存,判断该值是否为素数,将结果缓存
    return isPrime.answers[value] = prime;
  }
  assert(isPrime(5), "5 is prime!");
  assert(isPrime.answers[5], "The answer was cached!");
</script>
  • 缓存DOM元素
function getElements(name) { 
	if(!getElements.cache) getElements.cache={}; 
	return getElements.cache[name]=getElements.cache[name] || document.getElementsByTagName(name);
}
  • 检测并遍历可变长度的参数列表
<script type="text/javascript">
    function merge(root) {
        for (var i = 1; i < arguments.length; i++) {
            for (var key in arguments[i]) {
                root[key] = arguments[i][key];
            }
        }
        return root;
    }
    // 调用merge()函数
    var merged = merge({ name: "Batou" }, { city: "Nihama" });
    assert(merged.name == "Batou", "The original name is intact.");
    assert(merged.city == "Nihama", "And the city has been copied over.");
    </script>
  • 对arguments列表进行切片
<script type="text/javascript">
    function multiMax(multi) {
        return multi * Math.max.apply(Math, Array.prototype.slice.call(arguments, 1));
        /* 如果是multi*Math.max.apply(Math,arguments.slice(1));会报错
        因为arguments参数引用的不是真正的数组;Array.prototype.slice()这一原生JS数组方法,
        通常是通过其函数上下文操作数组的,这里通过call()方法将我们的对象强制作为slice()方法的上下文*/
        assert(multiMax(3, 1, 2, 3) == 9, "3*3=9 (第一个参数,剩余最大参数.)");
    }
    </script>
  • 重载函数的方法及测试
<script type="text/javascript">
    function addMethod(object, name, fn) {
        // 保存原有函数,因为调用的时候可能不匹配传入的参数个数
        var old = object[name];
        // 创建一个新匿名函数作为新方法
        object[name] = function() {
            // 若匿名函数的形参个数和实参个数匹配,调用该函数
            if (fn.length == arguments.length)
                return fn.apply(this, arguments)
            // 若传入参数不匹配,调用原来的参数
            else if (typeof old == 'function')
                return old.apply(this, arguments);
        };
    }
    var ninjas = {
        values: ["Dave Edwards", "Sam Stephen", "Alex Russell"]
    };
    // 在基础对象上绑定一个无参数方法
    addMethod(ninjas, "find", function() {
        return this.values;
    });
    // 在基础对象上绑定一个单参数的方法
    addMethod(ninjas, "find", function(name) {
        var ret = [];
        for (var i = 0; i < this.values.length; i++)
            if (this.values[i].indexOf(name) == 0)
                ret.push(this.values[i]);
        return ret;
    });
    // 在基础对象上绑定两个参数的方法
    addMethod(ninjas, "find", function(first, last) {
        var ret = [];
        for (var i = 0; i < this.values.length; i++)
            if (this.values[i] == (first + " " + last))
                ret.push(this.values[i]);
        return ret;
    });
    assert(ninjas.find().length == 3, "Found all ninjas");
    assert(ninjas.find("Sam").length == 1, "Found ninja by first name");
    assert(ninjas.find("Dave", "Edwards").length == 1, "Found ninja by first and last name");
    assert(ninjas.find("Alex", "Russell", "Jr") == null, "Found nothing");
    </script>
  • 函数判断 如何判断一个给定对象是一个函数的实例,并且是可调用的。通常typeof语句就可以满足要求。但也有跨浏览器的问题:Firefox--在html的object元素上使用typeof,会返回function而不是object;IE--IE会将DOM元素的方法报告成object,如typeof domNode.getAttribute=="object"等;Safari--Safari认为DOM的NodeList是一个function。所以typeof childNodes=="function"。

返回目录


4. 闭包

  • 不那么简单的闭包
<script type="text/javascript">
var outValue = 'ninja';
var later;
function outerFunction() {
    var innerValue = 'samurai';
    function innerFunction(paramValue) {
        assert(outerValue, "Inner can see the ninja.");
        assert(innerValue, "Inner can see the samurai.");
        assert(paramValue, "Inner can see the wakiz.");
        assert(tooLate, "Inner can see the ronin.");
    }
    later = innerFunction;
}
assert(!tooLate, "Outer can't see the ronin.");
var tooLate = 'ronin';
outerFunction();
later('wakiz');
</script>

内部闭包可以访问到tooLate,而外部闭包不能,因为:作用域之外的所有变量,即便是函数声明之后的那些声明,也都包含在闭包中;相同的作用域内,尚未声明的变量不能进行提前引用。

  • 使用闭包封装一些信息作为"私有变量"
<script type="text/javascript">
function Ninja() {
    // 在函数(构造器)内部声明一个私有变量
    var feints = 0;
    // 创建一个访问feints计数的方法,该变量在构造器内部无法被访问,只能读取
    this.getFeints = function() {
        return feints;
    };
    this.feint = function() {
        feints++;
    };
}
var ninja = new Ninja();
ninja.feint();
// 验证我们不能直接获取该变量值
assert(ninja.getFeints() == 1, "We're able to access the internal feint count.");
// 我们可以操作feints的值,因为即便构造器执行完且没有作用域了,feints变量还是会绑定在feint()方法声明创建的闭包上,并且可以在feint()方法内使用
assert(ninja.feints === undefined, "And the private data is inaccessible to us.");
</script>
  • 在ajax请求的callback里使用闭包
<div id="testSubject"></div>
<button type="button" id="testButton">Go!</button>
<script type="text/javascript">
  jQuery('#testButton').click(function(){
    var elem$ = jQuery("#testSubject");
    elem$.html("Loading...");
    jQuery.ajax({
      url("test.html"),
      success: function(html){
        // 定义一个匿名函数作为响应回调,在回调中通过闭包引用了elem$变量,使用该变量将响应文本填充到div元素中
        assert(elem$,"We can see elem$, via the closure for this callback.");
        elem$.html(html);
      }
    });
  });
</script>
  • 计时器中使用闭包
<div id="box">Some text</div>
<script type="text/javascript">
  function animateIt(elementId){
    var elem = document.getElementById(elementId);
    var tick = 0;
    var timer = setInterval(function(){
      if(tick < 100) {
        elem.style.left = elem.style.top = tick + "px";
        tick++;
      }
      else {
        clearInterval(timer);
        assert(tick == 100, "Tick accessed via a closure.");
        assert(elem, "Elment also accessed via a closure.");
        assert(timer, "Timer reference also obtained via a closure.");
      }
    },10);
  }
  animateIt('box');
</script>
  • 给事件处理程序绑定特定的上下文
<button id="test">Click it!</button>
<script type="text/javascript">
  // bind()方法创建并返回一个匿名函数,可以强制将上下文设置为想要的任何对象
  function bind(context,name){
    return function(){
        return context[name].apply(context,arguments);
    };
  }
  var button = {
    clicked: false;
    click: function(){
      this.clicked = true;
      assert(button.clicked,"The button has been clicked.");
      concole.log(this);
    }
  };
  var elem = document.getElementById("test");
  elem.addEventListener("click",bind(button,"click"),false);
</script>
  • 使用闭包实现缓存记忆
<script type="text/javascript">
Function.prototype.memorized = function(key) {
    this._values = this._values || {};
    return this._values[key] !== undefined ? this._values[key] : this._values[key] = this.apply(this, arguments);
};
Function.prototype.memorize = function() {
    var fn = this;
    // 通过变量赋值将上下文带到闭包中
    return function() {
        // 在缓存记忆函数中封装原始函数
        return fn.memorized.apply(fn, arguments);
    };
};
var isPrime = (function(num) {
    var prime = num != 1;
    for (var i = 2; i < num; i++) {
        if (num % i == 0) {
            prime = false;
            break;
        }
    }
    return prime;
}).memorize();
assert(isPrime(17), "17 is prime");
</script>
  • 利用即时函数处理迭代问题
<div>DIV 0</div>
<div>DIV 1</div>
<script type="text/javascript">
var div = document.getElementsByTagName("div");
// 通过for循环内加入即时函数,将正确的值传递给即时函数,进而让处理程序得到正确的值
for (var i = 0; i < div.length; i++)(function(n) {
    div[n].addEventListener("click", function() {
        assert("div #" + n + "was clicked.");
    }, false);
}
})(i);
</script>

返回目录


5. 原型与面向对象

  • 使用原型方法创建一个新实例
<script type="text/javascript">
function Ninja() {}
Ninja.prototype.swingSword = function() {
    return true;
};
// 将函数作为构造器进行调用,不仅新对象实例被创建,函数原型上的方法也可以调用了
var ninja = new Ninja();
// 判断一个实例的类型以及其构造器
assert(typeof ninja == "object", "The type of instance is object.");
assert(ninja instanceof Ninja, "instanceof identifies the constructor.");
assert(ninja.constructor == Ninja, "The ninja object was created by the Ninja function.");
</script>
  • 使用constructor实例化一个新对象
<script type="text/javascript">
  function Ninja(){}
  var ninja = new Ninja();
  var ninja2 = new ninja.constructor();
  assert(ninja2 instanceof Ninja,"It's a ninja");
  assert(ninja != ninja2,"But not the same Ninja");
</script>
  • 使用原型实现继承
<script type="text/javascript">
  function Person(){}
  Person.prototype.dance = function(){};
  function Ninja(){}
  Ninja.prototype = new Person();
  var ninja = new Ninja();
  assert(ninja instanceof Ninja, "ninja receives functionality from Ninja prototype.");
  assert(ninja instanceof Person, "...and Person prototype.");
  assert(ninja instanceof Object, "...and Object prototype.");
  assert(typeof ninja.dance == "function","...and can dance!");
</script>
  • forEach()兼容旧版本浏览器
<script type="text/javascript">
if (!Array.prototype.forEach) {
    Array.prototype.forEach = function(callback, context) {
        for (var i = 0; i < this.length; i++) {
            // 在每个数组条目上都调用callback方法 context||null表达式可防止将undefined传递给call()
            callback.call(context || null, this[i], i, this);
        }
    };
}
["a", "b", "c"].forEach(function(value, index, array) {
    assert(value, "Is in position" + index + " out of " + (array.length - 1));
});
</script>
  • 通过HTMLElement的原型给所有html元素添加方法
<div id="parent">
    <div id="a">To be removed</div>
    <div id="b">Me too!</div>
</div>
<script type="text/javascript">
HTMLElement.prototype.remove = function() {
    if (this.parentNode) {
        this.parentNode.removeChild(this);
    }
};
// 用原生方法删除元素a
var a = document.getElementById("a");
a.parentNode.removeChild(a);
// 用新方法删除元素b
document.getElementById("b").remove();
assert(!document.getElementById("a"), "a is gone");
assert(!document.getElementById("b"), "b is gone too");
</script>
  • 使用hasOwnProperty()方法解决原型对象拓展问题
<script type="text/javascript">
Object.prototype.keys = function() {
    var keys = [];
    for (var i in ths)
        // 忽略掉非实例对象的属性
        if (this.hasOwnProperty(i)) keys.push(i);
    return keys;
};
var obj = { a: 1, b: 2, c: 3 };
assert(obj.keys().length == 3, "There are three properties in this object.");
</script>
  • 模拟Array,而不是扩展成子类,可兼容所有浏览器
<script type="text/javascript">
// 创建一个含有原型属性length的新类
function MyArray() {}
MyArray.prototype.length = 0;
// 使用即时函数,用apply()将Array中选中方法复制到新类上
(function() {
    var methods = ['push', 'pop', 'shift', 'unshift', 'slice', 'splice', 'join'];
    for (var i = 0; i < methods.lenth; i++)(function name) {
        MyArray.prototype[name] = function() {
            return Array.prototype[name].apply(this, arguments);
        };
    }(methods[i]);
})();
var mine = new MyArray();
mine.push(1, 2, 3);
assert(mine.length == 3,"All the items are on our sub-classed array.");
assert(!(mine instanceof Array),"We aren't subclassing Array,though.");
</script>
  • 经典继承语法示例
<script type="text/javascript">
  var Person = Object.subClass() {
      init: function() { this.dancing = isDancing; },
      dance: function() { return this.dancing; }
  };
  var Ninja = Person.subClass() {
      init: function() { this._super(false); },
      dance: function() { return this._super(); },
      swingSword: function() { return true; }
  };
var person = new Person(true);
assert(person.dance(),"The person is dancing");
var ninja = new Ninja();
assert(ninja.swingSword(),"The sword is swinging");
assert(!ninja.dance(),"The ninja is not dancing");
assert(person instanceof Person,"person is a Person");
assert(ninja instanceof Ninja && ninja instanceof Person,"ninja is a Ninja and a Person");
</script>

返回目录


6. 正则表达式

  • 将横线字符串转换为驼峰拼写法
<script type="text/javascript">
  function upper(all,letter){
    return letter.toUpperCase();
  }
  assert("border-bottom-width".replace(/-(\w)/g,upper) == "borderBottomWidth","Camel cased a hyphenated string.");
</script>
  • 从字符串中剔除空格
<script type="text/javascript">
function trim(str){
  return (str || " ").replace(/^\s+|\s+$/g," ");
}
assert(trim(" #id div.class ") == "#id div.class","Extra whitespace trimmed from a selector string.");
</script>

在更长的文档字符串中,采用字符串的slice方法剔除字符串尾部空格,性能更好

<script type="text/javascript">
  function trim(str){
    var str=str.replace(/^\s\s*/, ' '),
    ws=/\s/,
    i=str.length;
    while(ws.test(str.charAt(--i)));
    return str.slice(0,i+1);
  }
  assert(trim(" #id div.class ") == "#id div.class","Extra whitespace trimmed from a selector string.");
</script>
  • 使用空白符匹配所有字符,包括换行符
<script type="text/javascript">
  var html = "<b>Hello</b>\n<i>word!</i>";
  assert(/[\S\s]*/.exec(html)[0]==="<b>Hello</b>\n<i>word!</i>","Matching everything with a character set.");
</script>

返回目录