@zhangzhen
2016-06-01T03:30:11.000000Z
字数 12495
阅读 3005
Douglas-Crockford Javascript 最佳实践
Function.prototype.method = function (name, func) {if (!this.prototype[name]) {this.prototype[name] = func;}return this;}
Number.method("integer", function () {return Math[this < 0 ? 'ceil' : 'floor'](this);});
String.method("trim", function () {return this.replace(/^\s+|\s+$/g, '');})
var myObject = (function () {var value = 0;return {increment: function (inc) {value += typeof inc === "number" ? inc : 1;},getValue: function () {return value;}};}());
// 定义var memoizer = function (memo, formula) {var recur = function (n) {var result = memo[n];if (typeof result !== 'number') {result = formula(recur, n);memo[n] = result;}return result;};return recur;};// 使用var fibonacci = memoizer([0, 1], function (recur, n) {return recur(n-1) + recur(n - 2);});for (var i = 0; i <= 10; i++) {console.log("%d : %d", i, fibonacci(i));}// 扩展用法 阶乘var factorial = memoizer([1, 1], function (recur, n) {return n * recur (n - 1)});for (var i = 1; i <= 10; i++) {console.log("%d : %d", i, factorial(i));}
var语句定义的普通变量
// 这里是一个函数化构造器的伪代码模板(加粗的文本表示强调):var **constructor** = function (spec, my) {// 代码快内部不知道怎么加粗,哪位大神指点一下var that, **其他私有变量**;my = my || {};// **把共享的变量和函数添加到my中**that = {name: "**一个新对象**"};// **给that添加特权方法**return that;}// spec 对象包含构造器需要构造一个新实例的所哟信息。spec 的内容可能会被复制到私有变量中,或者被其他函数改变,或者方法可以在需要的时候访问spec的信息。(一个简化的方式是替换spec为一个单一的值。当构造对象过程中并不需要整个spec对象的时候,这是有用的。)// my 对象是一个为继承链中的构造器提供秘密共享的容器。my对象可以选择性地使用。如果没有传入一个my对象,那么会创建一个my对象// demovar mammal = function (spec) {var that = {};that.get_name = function () {return spec.name;};that.says = function () {return spec.saying || "";}return that;};var cat = function (spec) {spec.saying = spec.saying || 'meow';var that = mammal(spec);that.purr = function (n) {var i, s = '';for (i = 0; i < n; i += 1) {if (s) {s += "-";}s += "r";}return s;};that.get_name = function () {return that.says() + " " + spec.name + " " + that.says();};return that;};var myCat = cat({name: "Henrietta"});
Object.method('superior', function (name) {var that = this,method = that[name];return function () {return method.apply(that, arguments);};});
让我们在coolcat上试验一下,coolcat就像cat一样,除了它有一个更酷的调用父类方法的get_name方法。它只需要一点点的准备工作。我们会声明一个super_get_name变量,并且把调用superior方法所返回的结果赋值给它。
var coolcat = function (spec) {var that = cat(spec),super_get_name = that.superior('get_name');that.get_name = function (n) {return 'like ' + super_get_name() + ' baby';};return that;};var myCoolCat = coolcat({name: 'Bix'});var name = myCoolCat.get_name();console.log(name); // "like meow Bix meow baby"
我们可以从一套部件中把对象组装出来。例如,我们可以构造一个给任何对象添加简单事件处理特性的函数。他会给对象添加一个on方法、一个fire方法和一个私有的事件注册表对象:
var eventuality = function (that) {var registry = {};that.fire = function (event) {// 在一个对象上触发一个事件。该事件可以是一个包含事件名称的字符串// 或者是一个拥有包含事件名称的 type 属性的对象。// 通过'on'方法注册的事件处理程序中匹配事件名称的函数将被调用。var array,func,handler,i,type = typeof event === 'string' ? event : event.type;if (registry.hasOwnProperty(type)) {array = registry[type];for (i = 0; i < array.length; i += 1) {handler = array[i];// 每个处理程序包含一个方法和一组可选的参数。// 如果该方法是一个字符串形式的名字,那么寻找到该函数。func = handler.method;if (typeof func === 'string') {func = this[func];}// 调用一个处理程序。如果该条目包含参数,那么传递它们过去。否则,传递该事件对象。func.apply(this, handler.parameters || [event]);}}return this;};that.on = function (type, method, parameters) {// 注册一个事件。构造一条处理程序条目。将它插入到处理程序数组中,// 如果这种类型的事件还不存在,就构造一个。var handler = {method: method,parameters: parameters};if (registry.hasOwnProperty(type)) {registry[type].push(handler);} else {registry[type] = [handler];}return this;};return that;};
我们可以在任何单独的对象上调用eventuality, 授予它事件处理方法。我们也可以赶在that被返回之前在一个构造器函数中调用它 eventuality(that);
eventuality(myCoolCat);myCoolCat.health = 100;myCoolCat.on('dead', function (event) {var date = event.date;var killer = event.killer;console.log("%s killed me at %s", killer, date);});myCoolCat.on('hurt', function () {this.health -= 50;if (this.health <= 0) {var event = {type: 'dead',killer: 'stone from sky',date: new Date()};this.fire(event);} else {console.log('nothing, who is the strong guy.');}});myCoolCat.fire("hurt"); //nothing, who is the strong guy.myCoolCat.fire("hurt"); //stone from sky killed me at Sat Mar 14 2015 15:47:29 GMT+0800 (CST)
javascript 编程中,一个常见的错误是在必须使用数组时使用了对象,或者在必须使用对象时使用了数组。其实规则很简单:当属性名是小而连续的整数时,你应该使用数组。否则,使用对象。
javascript 本身没有一个好的机制来区分数组和对象。我们可以通过定义自己的is_array函数来弥补这个缺陷:
var is_array = function (value) {return value&& typeof value === 'object'&& value.constructor === Array;};// 不过该方法在识别从不同的窗口(window)或者frame里构造的数组时会失败。下面是一个更好的方式// 上面这句话这里本人尚未理解,请高手指点var is_array = function (value) {return Object.prototype.toString.apply(value) === '[object Array]';};
Array.method('reduce', function (f, value) {var i;for (i = 0; i < this.length; i += 1) {value = f(this[i], value);}return value;});// DEMOvar data = [4, 8, 15, 16, 23, 42];var add = function (a, b) {return a + b;};var mult = function (a, b) {return a * b;};var sum = data.reduce(add, 0);console.log(sum);// 108var product = data.reduce(mult, 1);console.log(product); // 7418880// 因为数组其实就是对象,所以我们可以直接给一个单独的数组添加方法data.total = function () {return this.reduce(add, 0);};var total = data.total();console.log(total); // 108
Array.dim = function (dimension, initial) {var a = [], i;for (i = 0; i < dimension; i += 1) {a[i] = initial;}return a;};var myArray = Array.dim(10, 0); //[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]// 注意这里不能使用我们之前说的method方法,应为那个方法是给某个类型的每一个具体的对象增加方法的,而这里我们是给Array这个 类型 增加方法,类似于某些静态语言中的静态方法或者类方法
sort 方法会array中的内容进行排序。但是它不能正确第给一组数字排序:
var myNumbers = [4, 8, 15, 16, 23, 42];myNumbers.sort(); // [15, 16, 23, 4, 42, 8]
javascript 的默认比较函数把要被排序的元素都视为字符串。它尚未足够智能到在比较这些元素之前先检测他们的类型,所以当它比较这些数字的时候,会把它们转化为字符串,于是得到一个错的离谱的结果。
辛运的是,你可以使用自己的比较函数来替换默认的比较函数。
你的比较函数应该接受两个参数,并且如果这两个参数相等则返回0,如果第1个参数应该排列在前面,则返回一个负数,如果第2个参数应该排在前面,则返回一个正数。
myNumbers.sort(function (a, b) {return a - b;}); // [4, 8, 15, 16, 23, 42]
上面这个函数可以使数字正确排序,但它不能使字符串排序。如果我们想要给任何包含简单值的数组排序,必须要做更多的工作:
var myStrings = ['aa', 'bb', 'a', 4, 8, 15, 16, 23, 42];myStrings.sort(function (a, b) {if (a === b) {return 0;}if (typeof a === typeof b) {return a < b ? -1 : 1;}return typeof a < typeof b ? -1 : 1;}); //[4, 8, 15, 16, 23, 42, "a", "aa", "bb"]
// by 函数接受一个成员名字字符串做为参数,// 并返回一个可以用来对包含该成员的对象数组的进行排序的比较函数var by = function (name) {return function (o, p) {var a, b;if (typeof o === 'object' && typeof p === 'object' && o && p) {a = o[name];b = p[name];if (a === b) {return 0;}if (typeof a === typeof b) {return a < b ? -1 : 1;}return typeof a < typeof b ? -1 : 1;} else {throw {name: 'Error',message: 'Expected an object when sorting by ' + name};}};}var myPeople = [{first: 'Joe', last: 'Beasser'},{first: 'Moe', last: 'Howard'},{first: 'Joe', last: 'DeRita'},{first: 'Shemp', last: 'Howard'},{first: 'Larry', last: 'Fine'},{first: 'Curly', last: 'Howard'}];myPeople.sort(by('first'));/*[{first: "Curly", last: "Howard"},{first: "Joe", last: "Beasser"},{first: "Joe", last: "DeRita"},{first: "Larry", last: "Fine"},{first: "Moe", last: "Howard"},{tfirst: "Shemp", last: "Howard"}]*/
sort 方法是不稳定的
排序的稳定性是指排序后数组中的相等值的相对位置没有发生改变,而不稳定性排序则会改变相等值的相对位置。详细内容参见排序算法。JavaScript的
sort方法的稳定性根据不同浏览器的实现而不一致。可参见Array.prototype.sort中的介绍。
下面的调用:
myPeople.sort(by('last')).sort(by('first'));/*[{"first": "Curly", "last": "Howard"},{"first": "Joe", "last": "Beasser"},{"first": "Joe", "last": "DeRita"},{"first": "Larry", "last": "Fine"},{"first": "Moe", "last": "Howard"},{"first": "Shemp", "last": "Howard"}]*/
不能保证产生正确的序列。如果你想基于多个键值进行排序,你需要再次做更多的工作。我们可以修改by函数,让其可以接受第2个参数,当主要的键值产生一个匹配的时候,另一个compare方法将被调用以决出高下。
// by 函数接受一个成员名字字符串和一个可选的次要比较函数做为参数,// 并返回一个可以用来对包含该成员的对象数组进行排序的比较函数。// 当 o[name] 和 p[name] 相等时,次要比较函数被用来决出高下。var by = function (name, minor) {return function (o, p) {var a, b;if (o && p && typeof o === 'object' && typeof p === 'object') {a = o[name];b = p[name];if (a === b) {return typeof minor === 'function' ? minor(o, p) : 0;}if (typeof a === typeof b) {return a < b ? -1 : 1;}return typeof a < typeof b ? -1 : 1;} else {throw {name: 'Error',message: 'Expected an object when sorting by ' + name};}};}myPeople.sort(by('last', by('first')));[{"first": "Joe", "last": "Beasser"},{"first": "Joe", "last": "DeRita"},{"first": "Larry", "last": "Fine"},{"first": "Curly", "last": "Howard"},{"first": "Moe", "last": "Howard"},{"first": "Shemp", "last": "Howard"}]
var parse_url = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;var url = "http://www.ora.com:80/goodparts?q#fragment";var result = parse_url.exec(url);var names = ['url', 'scheme', 'slash', 'host', 'port', 'path', 'query', 'hash'];var blanks = ' ';var i;for (i = 0; i < names.length; i += 1) {console.log("%s:%s%s", names[i], blanks.substring(names[i].length), result[i]);}/*url: http://www.ora.com:80/goodparts?q#fragmentscheme:httpslash: //host: www.ora.comport: 80path: goodpartsquery: qhash: fragment*/
var parse_number = /^-?\d+(?:\.\d*)?(?:e[+\-]?\d+)?$/i;var isNumber = function (value) {var result = parse_number.test(value);console.log("%s %s a number", value, result ? "is" : "is not");}isNumber('1')// 1 is a numberisNumber('number')// number is not a numberisNumber('98.6')// 98.6 is a numberisNumber('132.21.86.100')// 132.21.86.100 is not a numberisNumber('123.45E-67')// 123.45E-67 is a numberisNumber('123.45D-67')//123.45D-67 is not a number// 数字可能由一个整数部分加上一个可选的负号、一个可选的小数部分和一个可选的指数部分构成
/*!"#$%&'()*+,-./:;<=>?@[\]^_`{\}~*/var ascii32 = /(?:!|"|#|\$|%|&|'|\(|\)|\*|\+|,|-|\/|:|;|<|=|>|\?|@|\[|\\|]|\^|_|`|\{|\||\}|~)/;// 这是其中一种简写方式,不过依旧难以阅读var ascii32_short1 = /[!-\/:-@\[-`{-~]/;// 正则表达式的一个方便之处就是类的求反。如果 [ 后的第一个字符是 ^ ,那么这个类会排除这些特殊字符。var not_ascii = /[^!-\/:-@\[-`{-~]/;
javascript 有两组相等运算符:=== 和 !==, 以及它们邪恶的孪生兄弟 == 和 !=。=== 和 !== 这一组运算符会按照你期望的方式工作。如果来两个运算数类型一致并且拥有相同的值,那么 === 返回 true,!== 返回 false。而它们邪恶的孪生兄弟只有在两个运算数类型一致时才会做出正确的判断,如果两个运算数是不同的类型,它们试图去强制转换值的类型。转换的规则复杂且难以记忆。这里有一些有趣的例子:
'' == '0' // false0 == '' // true0 == '0' // truefalse == 'false' // falsefalse == '0' // truefalse == undefined // falsefalse == null // falsenull == undefined // true' \t\r\n' == 0 // true
== 运算符对传递性的缺乏值得我们警惕。我的建议是永远不要使用那对邪恶的孪生兄弟。相反,请始终使用 === 和 !==。如果以上所有比较使用 === 运算符,结果都是false。
传递性是一种编程约定。可以这么理解:对于任意的引用值x、y和z,如果
x == y和y == z为true,那么x == z为true。而javascript中的 == 运算符在某些特例上违背了传递性。
// 这是一个用JavaScript编写JSON解析器的实现方案:var json_parse = function () {// 这是一个能把JSON文本解析成JavaScript数据结构的函数。// 它是一个简单的递归降序解析器。// 我们在另一个函数中定义此函数,以避免创建全局变量。var at, // 当前字符索引ch, // 当前字符escapee = {'"' : '"','\\' : '\\','/' : '/',b : 'b',f : '\f',n : '\n',r : '\r',t : '\t'},text,error = function (m) {// 当某处出错时,调用error。throw {name : 'SyntaxError',messsage: m,at : at,text : text};},next = function (c) {// 如果提供了参数 c, 那么检查它是否匹配当前字符。if (c && c !== ch) {error("Expected '" + c + "' insttead of '" + ch + "'");}// 获取下一个字符。当没有下一个字符时,返回一个空字符串。ch = text.charAt(at);at += 1;return ch;},number = function () {// 解析一个数字值。var number,string = '';if (ch === '-') {string = '-';next('-');}while (ch >= '0' && ch <= '9') {string += ch;next();}if (ch === '.') {string += ".";while (next() && ch >= '0' && ch <= '9') {string += ch;}}if (ch == 'e' || ch == 'E') {string += ch;next();if (ch === '-' || ch === '+') {string += ch;next();}while (ch >= '0' && ch <= '9') {string += ch;next();}}number = +string;if (isNaN(number)) {error("Bad number");} else {return number;}},string = function () {// 解析一个字符串值。var hex,i,string = '',uffff;// 当解析字符串值时,我们必须找到 " 和 \ 字符。if (ch === '"') {while (next()) {if (ch === '"') {next();return string;} else if (ch === '\\') {next();if (ch === 'u') {uffff = 0;for (i = 0; i < 4; i += 1) {hex = parseInt(next(), 16);if (!isFinite(hex)) {break;}uffff = uffff * 16 + hex;}string += String.fromCharCode(uffff);} else if (typeof escapee[ch] === 'string') {string += escapee[ch];} else {break;}} else {string += ch;}}}error("Bad string");},white = function () {// 跳过空白while (ch && ch <= ' ') {next();}},word = function () {// true, false 或者 nullswitch (ch) {case 't':next('t');next('r');next('u');next('e');return true;case 'f':next('f');next('a');next('l');next('s');next('e');return false;case 'n':next('n');next('u');next('l');next('l');return null;}error("Unexpected '" + ch + "'");},value, // 值函数的占位符。array = function () {// 解析一个数组值。var array = [];if (ch === '[') {white();if (ch === ']') {next(']');return array; // 空数组}while (ch) {array.push(value());white();if (ch === "]") {next(']');return array;}next(',');white();}}error("Bad array");},object = function () {// 解析一个对象值var key,object = {};if(ch === '{') {next('{');white();if (ch === "}") {next('}');return object; // 空对象}while (ch) {key = string();white();next(":");object[key] = value();white();if(ch === '}') {next('}');return object;}next(",");white();}}error("Bad object");};value = function () {// 解析一个JSON值。它可以是对象、数组、字符串、数字或一个词。white();switch (ch) {case '{':return object();case '[':return array();case '"':return string();case "-":return number();default:return ch >= '0' && ch <= '9' ? number() : word() ;}};// 返回json_parse 函数。它能访问上述所有的函数和变量。return function(source, reviver) {var result;text = source;at = 0;ch = ' ';result = value();white();if (ch) {error("Syntax error");}// 如果存在reviver 函数,我们就递归地对这个新结构调用walk函数,// 开始时先创建一个临时的启动对象,并以一个空字符串作为键名保存结果,// 然后传递每个键值对给reviver函数去处理可能存在的转换。// 如果没有reviver函数,我们就简单地返回这个结果。return typeof reviver === 'function' ?function walk(holder, key) {var k, v, value = holder[key];if (value && typeof value === 'object') {for (k in value) {if (Object.hasOwnProperty.call(value, k)) {v = walk(value, k);if (v !== undefined) {value[k] = v;} else {delete value[k];}}}}return reviver.call(holder, key, value);}({'': result}, ''): result;}}();
正文部分到此结束。
第一次写读书笔记,一是加深自己的印象,二来方便以后查阅,毕竟纸张没有搜索功能。
如果你也喜欢这篇文章欢迎转载,但本文代码版权属于《JavaScript 语言精粹》,小部分代码扩充是为了演示代码效果。
如果你想和我交流,Github: mrzzcn, QQ:759650941,新浪微博:I-am-a-programer