原文引用地址:http://bonsaiden.github.io/JavaScript-Garden/zh/javascript
对象java
javascript中全部变量都是对象,除了两个例外null和undefined。git
false.toString(); // 'false' [1, 2, 3].toString(); // '1,2,3' function Foo(){} Foo.bar = 1; Foo.bar; // 1
一个常见的误解时数字的字面值不是对象。这是由于javascript解析器的一个错误,它试图将点操做符解析为浮点数字面值的一部分。程序员
2.toString();//SyntaxError
有不少变通方法可让数字的字面值看起来像对象。github
2..toString(); // 第二个点号能够正常解析 2 .toString(); // 注意点号前面的空格 (2).toString(); // 2先被计算
对象做为数据类型ajax
javascript的对象能够做为哈希表使用,主要用来保存命名的键值的对应关系。编程
使用对象的字面语法-{}-能够建立一个简单的对象。这个新建立的对象从object.prototype继承下来,没有任何自定义属性。数组
var foo = {}; // 一个空对象 // 一个新对象,拥有一个值为12的自定义属性'test' var bar = {test: 12};
访问属性浏览器
有两种方式来访问对象的属性,点操做符或者中括号操做符。缓存
var foo = {name: 'kitten'} foo.name; // kitten foo['name']; // kitten var get = 'name'; foo[get]; // kitten foo.1234; // SyntaxError foo['1234']; // works
两种语法是等价的,可是中括号操做符在下面两种状况下依然有效-动态设置属性-属性名不是一个有效的变量名(好比属性名中包含空格,或者属性名是js的关键字)
删除属性
删除属性的惟一办法是使用delete操做符;设置属性为undefined或者null并不能真正删除属性,而仅仅是移除了属性和值得关联。
var obj = { bar: 1, foo: 2, baz: 3 }; obj.bar = undefined; obj.foo = null; delete obj.baz; for(var i in obj) { if (obj.hasOwnProperty(i)) { console.log(i, '' + obj[i]); } }
上面的输出结果有bar undefined和foo null -只有 baz 被真正的删除了,因此从输出结果中消失。
属性名的语法
var test = { 'case': 'I am a keyword so I must be notated as a string', delete: 'I am a keyword too so me' // 出错:SyntaxError };
对象的属性名可使用字符串或者普通字符声明。可是因为javascript解析器的另外一个错误设计,上面的第二种声明方式在ECMAScript5以前会抛出SyntaxError的错误。
这个错误的缘由是delete是javascript语言的一个关键字;所以为了在更低版本的javascript也能正常运行,必须使用字符串字面值声明方式。
原型
javascript不包含传统的类继承模型,而是使用prototype原型模型。
虽然这常常被看成是javascript的缺点被说起,其实基于原型的继承模型比传统的类继承还要强大。实现传统的类继承模型是很简单,可是实现javascript多种的原型继承则要困难的多。
第一个不一样之处在于javascript使用原型链的继承方式。
function Foo() { this.value = 42; } Foo.prototype = { method: function() {} }; function Bar() {} // 设置Bar的prototype属性为Foo的实例对象 Bar.prototype = new Foo(); Bar.prototype.foo = 'Hello World'; // 修正Bar.prototype.constructor为Bar自己 Bar.prototype.constructor = Bar; var test = new Bar() // 建立Bar的一个新实例 // 原型链 test [Bar的实例] Bar.prototype [Foo的实例] { foo: 'Hello World' } Foo.prototype {method: ...}; Object.prototype {toString: ... /* etc. */};
上面的例子中,test 对象从 Bar.prototype 和 Foo.prototype 继承下来;所以, 它能访问 Foo 的原型方法 method。同时,它也可以访问那个定义在原型上的 Foo 实例属性 value。 须要注意的是 new Bar() 不会创造出一个新的 Foo 实例,而是 重复使用它原型上的那个实例;所以,全部的 Bar 实例都会共享相同的 value 属性。
属性查找
当查找一个对象的属性时,javascript会向上遍历原型链,知道找到给定名称的属性为止。
到查找到达原型链的顶部-也就是Object.prototype - 可是仍然没有找到指定的属性,就会返回undefined。
原型属性
当圆形属性用来建立原型链时,能够把任何类型的值赋给它(prototype)。
然而将原子类型赋给prototype的操做将会被忽略。
function Foo() {} Foo.prototype = 1; // 无效
而将对象赋值给prototype,正如上面的例子所示,将会动态的建立原型链。
性能
若是一个属性在原型链上端,则对于查找时间带来不利影响。特别的,试图获取一个不存在的属性将会遍历整个原型链。
而且,当使用for in遍历对象的属性时,原型链上的全部属性都将被访问。
扩展内置类型的原型
一个错误特性被常用,那就是扩展Object.prototype或者其余内置类型的原型对象。
这种技术被称为monkey patching而且会破坏封装。虽然它被普遍的应用到一些javascript类库中好比prototype,可是我仍然认为为内置类型添加一些非标准的函数不是个好主意。
扩展内置类型的惟一理由是为了和新的javascript保持一致,好比Array,forEach.
总结
上面的例子中,test 对象从 Bar.prototype 和 Foo.prototype 继承下来;所以, 它能访问 Foo 的原型方法 me在写复杂的 JavaScript 应用以前,充分理解原型链继承的工做方式是每一个 JavaScript 程序员必修的功课。 要提防原型链过长带来的性能问题,并知道如何经过缩短原型链来提升性能。 更进一步,绝对不要扩展内置类型的原型,除非是为了和新的 JavaScript 引擎兼容。
为了判断一个对象是否包含自定义属性而不是原型链上的属性,咱们须要使用继承自Object.prototype的hasOwnProperty方法。
hasOwnProperty是javascript中惟一一个处理属性可是不查找原型链的函数。
// 修改Object.prototype Object.prototype.bar = 1; var foo = {goo: undefined}; foo.bar; // 1 'bar' in foo; // true foo.hasOwnProperty('bar'); // false foo.hasOwnProperty('goo'); // true
只有hasOwnProperty能够给出正确和指望的结果,这在遍历对象的属性时会颇有用。没有其余方法能够用来排除原型链上的属性,而不是定义在对象自身上的属性。
hasOwnProperty做为属性
javascript不会保护hasOwnProperty被非法占用,所以结果一个对象碰巧存在这个属性,就须要外部的hasOwnProperty函数来获取正确的结果。
var foo = { hasOwnProperty: function() { return false; }, bar: 'Here be dragons' }; foo.hasOwnProperty('bar'); // 老是返回 false // 使用其它对象的 hasOwnProperty,并将其上下文设置为foo ({}).hasOwnProperty.call(foo, 'bar'); // true
结论
当检查对象上某个属性是否存在时,hasOwnProperty 是惟一可用的方法。 同时在使用 for in loop 遍历对象时,推荐老是使用 hasOwnProperty 方法, 这将会避免原型对象扩展带来的干扰。
for in循环
和in操做符同样,for in 循环一样在查找对象属性时遍历原型链上的全部属性。
// 修改 Object.prototype Object.prototype.bar = 1; var foo = {moo: 2}; for(var i in foo) { console.log(i); // 输出两个属性:bar 和 moo }
因为不可能改变for in自身的行为,所以有必要过滤出那些不但愿出如今循环体中的属性,这能够经过Object.prototype原型上的hasOwnProperty 函数来完成。
使用hasOwnProperty 过滤
// foo 变量是上例中的 for(var i in foo) { if (foo.hasOwnProperty(i)) { console.log(i); } }
这个版本的代码是惟一正确的写法。因为咱们使用了hasOwnProperty ,因此此次只输出moo。若是不使用hasOwnProperty ,则这段代码在原生对象原型被扩展时可能会出错。
一个普遍使用的类库prototype就扩展了原生的javascript对象。所以当这个类库被包含在页面中时,不使用hasOwnProperty 过滤的for in 循环不免会出问题。
总结
推荐老是使用 hasOwnProperty。不要对代码运行的环境作任何假设,不要假设原生对象是否已经被扩展了。
函数
函数式javascript中的一等对象,这意味着能够把函数像其余值同样传递。一个常见的用法是把匿名函数做为回调函数传递到异步函数中。
函数声明
function foo() {}
上面的方法会在执行前被解析,所以它存在于当前上下文的任意一个地方,即便在函数定义体的上面被调用也是对的。
foo(); // 正常运行,由于foo在代码运行前已经被建立 function foo() {}
函数赋值表达式
var foo = function() {};
这个例子把一个匿名函数赋值给变量foo。
foo; // 'undefined' foo(); // 出错:TypeError var foo = function() {};
因为var定义了一个声明语句,对变量foo的解析式在代码运行以前,所以foo变量在代码运行时已经被定义过了。
可是因为赋值语句只在运行时执行,所以在相应代码以前,foo的值缺省为undefined。
命名函数的赋值表达式
另一个特殊的状况是将命名函数赋值给一个变量。
var foo = function bar() { bar(); // 正常运行 } bar(); // 出错:ReferenceError
bar函数声明外事不可见的,这是由于咱们已经把函数赋值给了foo;然而在bar内部依然可见。这是因为javascript的命名处理所致,函数名在函数内老是可见的。
this的工做原理
javascript有一套彻底不一样于其余语言的对this的处理机制。在五种不一样的状况下,this指向的各不相同。
全局范围内
this
当在全局范围内使用this,它将会指向全局对象。
函数调用
foo();
这里this也会指向全局对象
方法调用
test.foo();
这个例子中,this指向test对象。
调用构造函数
new foo();
若是函数倾向于和new关键字一块使用,则咱们称这个函数式构造函数。在函数内部,this指向新建立的对象。
显示的设置this
function foo(a, b, c) {} var bar = {}; foo.apply(bar, [1, 2, 3]); // 数组将会被扩展,以下所示 foo.call(bar, 1, 2, 3); // 传递到foo的参数是:a = 1, b = 2, c = 3
当使用function.prototype上的call或者apply方法时,函数内的this将会被显示设置为函数调用的第一参数。
所以函数调用的规则在上例中已经不适用了,在foo函数内this被设置成了bar。
常见误解
尽管大部分的状况说的过去,不过第一个规则(这里指的是应该是第二个规则,也就是直接调用函数时,this指向全局对象)被认为是javascript语言另外一个错误设计的地方,由于它历来就没有实际用途。
Foo.method = function() { function test() { // this 将会被设置为全局对象(译者注:浏览器环境中也就是 window 对象) } test(); }
一个常见的误解是test中的this将会指向foo对象,实际上不是这个样子的。
为了在test中获取对foo对象的引用,咱们须要在method函数内部建立一个局部变量指向foo对象。
Foo.method = function() { var that = this; function test() { // 使用 that 来指向 Foo 对象 } test(); }
that只是咱们随意起的名字,不过这个名字被普遍的用来指向外部的this对象。在闭包一节,咱们能够看到that能够做为参数传递。
方法的赋值表达式
另外一个看起来奇怪的地方时函数别名,也就是将一个方法赋值给一个变量。
var test = someObject.methodTest; test();
上例中,test就像一个普通的函数被调用;所以,函数内的this将再也不被指向到someObject对象。
虽然this的晚绑定特性彷佛并不友好,可是这确实基于原型继承赖以生存的土壤。
function Foo() {} Foo.prototype.method = function() {}; function Bar() {} Bar.prototype = Foo.prototype; new Bar().method();
当method被调用时,this将会指向Bar的实例对象。
闭包和引用
闭包是javascript一个很是重要的特性,这意味着当前做用域老是可以访问外部做用域中的变量。由于函数式javascript中惟一拥有自身做用域的结构,所以闭包的建立依赖于函数。
模拟私有变量
function Counter(start) { var count = start; return { increment: function() { count++; }, get: function() { return count; } } } var foo = Counter(4); foo.increment(); foo.get(); // 5
这里,Counter函数返回两个闭包,函数increment和函数get。这两个函数都维持着对外部做用域count的引用,所以总能够访问到此做用域内定义的变量count。
为何不能够在外部访问私有变量
由于javascript不能对做用域进行引用或赋值,所以没有办法在外部访问count变量。惟一的途径就是经过那两个闭包。
var foo = new Counter(4); foo.hack = function() { count = 1337; };
上面的代码不会改变定义在counter做用域中的count变量的值,由于foo。hack没有定义在那个做用域内。它将会建立或者覆盖全局变量count。
循环中的闭包
一个常见的错误出如今循环中使用闭包,假设咱们须要在每次循环中调用循环序号
for(var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); }, 1000); }
上面的代码不会输出数字0-9,而是会输出数字10十次。
当console.log被调用时候,匿名函数保持对外部变量i的引用,此时for循环已经结束,i的值被修改为了10.
为了获得想要的结果,须要在每次循环中建立变量i的拷贝。
避免引用错误
为了正确的得到循环序号,最好使用匿名包裹器(自执行匿名函数)。
for(var i = 0; i < 10; i++) { (function(e) { setTimeout(function() { console.log(e); }, 1000); })(i); }
外部的匿名函数会当即执行,并把i做为它的参数,此时函数内e变量就拥有了i的一个拷贝。
当传递给setTimeount的匿名函数执行时,它就拥有了对e的引用,而这个值是不会被循环改变的。
有另外一个方法完成一样的工做;那就是从匿名包装器中返回一个函数。这和上面的代码效果同样。
for(var i = 0; i < 10; i++) { setTimeout((function(e) { return function() { console.log(e); } })(i), 1000) }
arguments对象
JavaScript 中每一个函数内都能访问一个特别变量 arguments。这个变量维护着全部传递到这个函数中的参数列表。
arguments 变量不是一个数组(Array)。 尽管在语法上它有数组相关的属性 length,但它不从 Array.prototype 继承,实际上它是一个对象(Object)。
所以,没法对 arguments 变量使用标准的数组方法,好比 push, pop 或者 slice。 虽然使用 for 循环遍历也是能够的,可是为了更好的使用数组方法,最好把它转化为一个真正的数组。
转化为数组
下面的代码将会建立一个新的数组,包含全部arguments 对象中的元素。
Array.prototype.slice.call(arguments);
这个转化比较慢,在性能很差的代码中不推荐这种作法。
传递参数
下面将参数从一个函数传递到另外一个函数,是推荐的作法。
function foo() { bar.apply(null, arguments); } function bar(a, b, c) { // do stuff here }
另外一个技巧是同时使用 call 和 apply,建立一个快速的解绑定包装器。
上面的 Foo.method 函数和下面代码的效果是同样的:
Foo.method = function() { var args = Array.prototype.slice.call(arguments); Foo.prototype.method.apply(args[0], args.slice(1)); };
自动更新
arguments 对象为其内部属性以及函数形式参数建立 getter 和 setter 方法。
所以,改变形参的值会影响到 arguments 对象的值,反之亦然。
function foo(a, b, c) { arguments[0] = 2; a; // 2 b = 4; arguments[1]; // 4 var d = c; d = 9; c; // 3 } foo(1, 2, 3);
性能真相
arguments 对象总会被建立,除了两个特殊状况 - 做为局部变量声明和做为形式参数。 而无论它是否有被使用。
arguments 的 getters 和 setters 方法总会被建立;所以使用 arguments 对性能不会有什么影响。 除非是须要对 arguments 对象的属性进行屡次访问。
在 MDC 中对 strict mode 模式下 arguments 的描述有助于咱们的理解,请看下面代码
// 阐述在 ES5 的严格模式下 `arguments` 的特性 function f(a) { "use strict"; a = 42; return [a, arguments[0]]; } var pair = f(17); assert(pair[0] === 42); assert(pair[1] === 17);
然而,的确有一种状况会显著的影响现代 JavaScript 引擎的性能。这就是使用 arguments.callee。
function foo() { arguments.callee; // do something with this function object arguments.callee.caller; // and the calling function object } function bigLoop() { for(var i = 0; i < 100000; i++) { foo(); // Would normally be inlined... } }
上面代码中,foo 再也不是一个单纯的内联函数 inlining(译者注:这里指的是解析器能够作内联处理), 由于它须要知道它本身和它的调用者。 这不只抵消了内联函数带来的性能提高,并且破坏了封装,所以如今函数可能要依赖于特定的上下文。
所以强烈建议你们不要使用 arguments.callee
和它的属性。
构造函数
JavaScript 中的构造函数和其它语言中的构造函数是不一样的。 经过 new 关键字方式调用的函数都被认为是构造函数。
在构造函数内部 - 也就是被调用的函数内 - this 指向新建立的对象 Object。 这个新建立的对象的 prototype 被指向到构造函数的 prototype。
若是被调用的函数没有显式的 return 表达式,则隐式的会返回 this 对象 - 也就是新建立的对象。
function Foo() { this.bla = 1; } Foo.prototype.test = function() { console.log(this.bla); }; var test = new Foo();
上面代码把 Foo 做为构造函数调用,并设置新建立对象的 prototype 为 Foo.prototype。
显式的 return 表达式将会影响返回结果,但仅限于返回的是一个对象。
function Bar() { return 2; } new Bar(); // 返回新建立的对象 function Test() { this.value = 2; return { foo: 1 }; } new Test(); // 返回的对象
new Bar() 返回的是新建立的对象,而不是数字的字面值 2。 所以 new Bar().constructor === Bar,可是若是返回的是数字对象,结果就不一样了,以下所示
function Bar() { return new Number(2); } new Bar().constructor === Number
这里获得的 new Test()是函数返回的对象,而不是经过new关键字新建立的对象,所以:
(new Test()).value === undefined (new Test()).foo === 1
若是 new 被遗漏了,则函数不会返回新建立的对象。
function Foo() { this.bla = 1; // 获取设置全局参数 } Foo(); // undefined
虽然上例在有些状况下也能正常运行,可是因为 JavaScript 中 this 的工做原理, 这里的 this 指向全局对象。
工厂模式
为了避免使用 new 关键字,构造函数必须显式的返回一个值。
function Bar() { var value = 1; return { method: function() { return value; } } } Bar.prototype = { foo: function() {} }; new Bar(); Bar();
上面两种对 Bar 函数的调用返回的值彻底相同,一个新建立的拥有 method 属性的对象被返回, 其实这里建立了一个闭包。
还须要注意, new Bar() 并不会改变返回对象的原型(译者注:也就是返回对象的原型不会指向 Bar.prototype)。 由于构造函数的原型会被指向到刚刚建立的新对象,而这里的 Bar 没有把这个新对象返回(译者注:而是返回了一个包含 method 属性的自定义对象)。
在上面的例子中,使用或者不使用 new
关键字没有功能性的区别。
上面两种方式建立的对象不能访问 Bar 原型链上的属性,以下所示:
var bar1 = new Bar(); typeof(bar1.method); // "function" typeof(bar1.foo); // "undefined" var bar2 = Bar(); typeof(bar2.method); // "function" typeof(bar2.foo); // "undefined"
经过工厂模式建立新对象
上面两种方式建立咱们常听到的一条忠告是不要使用 new 关键字来调用函数,由于若是忘记使用它就会致使错误。对象不能访问 Bar 原型链上的属性,以下所示:
为了建立新对象,咱们能够建立一个工厂方法,而且在方法内构造一个新对象。
function Foo() { var obj = {}; obj.value = 'blub'; var private = 2; obj.someMethod = function(value) { this.value = value; } obj.getPrivate = function() { return private; } return obj; }
虽然上面的方式比起 new 的调用方式不容易出错,而且能够充分利用私有变量带来的便利, 可是随之而来的是一些很差的地方。
1.会占用更多的内存,由于新建立的对象不能共享原型上的方法。
2.为了实现继承,工厂方法须要从另一个对象拷贝全部属性,或者把一个对象做为新建立对象的原型。
3.放弃原型链仅仅是由于防止遗漏 new 带来的问题,这彷佛和语言自己的思想相违背。
总结
虽然遗漏 new 关键字可能会致使问题,但这并非放弃使用原型链的借口。 最终使用哪一种方式取决于应用程序的需求,选择一种代码书写风格并坚持下去才是最重要的。
做用域与命名空间
尽管 JavaScript 支持一对花括号建立的代码段,可是并不支持块级做用域; 而仅仅支持 函数做用域。
function test() { // 一个做用域 for(var i = 0; i < 10; i++) { // 不是一个做用域 // count } console.log(i); // 10 }
若是 return 对象的左括号和 return 不在一行上就会出错。
// 译者注:下面输出 undefined function add(a, b) { return a + b; } console.log(add(1, 2));
JavaScript 中没有显式的命名空间定义,这就意味着全部对象都定义在一个全局共享的命名空间下面。
每次引用一个变量,JavaScript 会向上遍历整个做用域直到找到这个变量为止。 若是到达全局做用域可是这个变量仍未找到,则会抛出 ReferenceError 异常。
隐式的全局变量
// 脚本 A foo = '42'; // 脚本 B var foo = '42'
上面两段脚本效果不一样。脚本 A 在全局做用域内定义了变量 foo,而脚本 B 在当前做用域内定义变量 foo。
再次强调,上面的效果彻底不一样,不使用 var 声明变量将会致使隐式的全局变量产生。
// 全局做用域 var foo = 42; function test() { // 局部做用域 foo = 21; } test(); foo; // 21
在函数 test 内不使用 var 关键字声明 foo 变量将会覆盖外部的同名变量。 起初这看起来并非大问题,可是当有成千上万行代码时,不使用 var 声明变量将会带来难以跟踪的 BUG
// 全局做用域 var items = [/* 数组 */]; for(var i = 0; i < 10; i++) { subLoop(); } function subLoop() { // subLoop 函数做用域 for(i = 0; i < 10; i++) { // 没有使用 var 声明变量 // 干活 } }
外部循环在第一次调用 subLoop
以后就会终止,由于 subLoop
覆盖了全局变量i
。 在第二个 for
循环中使用 var
声明变量能够避免这种错误。 声明变量时绝对不要遗漏var
关键字,除非这就是指望的影响外部做用域的行为。
局部变量
JavaScript 中局部变量只可能经过两种方式声明,一个是做为函数参数,另外一个是经过var
关键字声明。
// 全局变量 var foo = 1; var bar = 2; var i = 2; function test(i) { // 函数 test 内的局部做用域 i = 5; var foo = 3; bar = 4; } test(10);
foo
和 i
是函数 test
内的局部变量,而对bar
的赋值将会覆盖全局做用域内的同名变量。
变量声明提高
JavaScript 会提高变量声明。这意味着 var
表达式和 function
声明都将会被提高到当前做用域的顶部。
bar(); var bar = function() {}; var someValue = 42; test(); function test(data) { if (false) { goo = 1; } else { var goo = 2; } for(var i = 0; i < 100; i++) { var e = data[i]; } }
上面代码在运行以前将会被转化。JavaScript 将会把 var
表达式和 function
声明提高到当前做用域的顶部。
// var 表达式被移动到这里 var bar, someValue; // 缺省值是 'undefined' // 函数声明也会提高 function test(data) { var goo, i, e; // 没有块级做用域,这些变量被移动到函数顶部 if (false) { goo = 1; } else { goo = 2; } for(i = 0; i < 100; i++) { e = data[i]; } } bar(); // 出错:TypeError,由于 bar 依然是 'undefined' someValue = 42; // 赋值语句不会被提高规则(hoisting)影响 bar = function() {}; test();
没有块级做用域不只致使 var
表达式被从循环内移到外部,并且使一些 if
表达式更难看懂。
在原来代码中,if
表达式看起来修改了所有变量 goo
,实际上在提高规则被应用后,倒是在修改局部变量。
若是没有提高规则(hoisting)的知识,下面的代码看起来会抛出异常 ReferenceError
。
// 检查 SomeImportantThing 是否已经被初始化 if (!SomeImportantThing) { var SomeImportantThing = {}; }
实际上,上面的代码正常运行,由于 var
表达式会被提高到全局做用域的顶部。
var SomeImportantThing; // 其它一些代码,可能会初始化 SomeImportantThing,也可能不会 // 检查是否已经被初始化 if (!SomeImportantThing) { SomeImportantThing = {}; }
在 Nettuts+ 网站有一篇介绍 hoisting 的文章,其中的代码颇有启发性。
// 译者注:来自 Nettuts+ 的一段代码,生动的阐述了 JavaScript 中变量声明提高规则 var myvar = 'my value'; (function() { alert(myvar); // undefined var myvar = 'local value'; })();
名称解析顺序
JavaScript 中的全部做用域,包括全局做用域,都有一个特别的名称 this
指向当前对象。
函数做用域内也有默认的变量 arguments
,其中包含了传递到函数中的参数。
好比,当访问函数内的 foo
变量时,JavaScript 会按照下面顺序查找:
var foo
的定义。foo
名称的。foo
。(function() { // 函数建立一个命名空间 window.foo = function() { // 对外公开的函数,建立了闭包 }; })(); // 当即执行此匿名函数
( // 小括号内的函数首先被执行 function() {} ) // 而且返回函数对象 () // 调用上面的执行结果,也就是函数对象
// 另外两种方式 +function(){}(); (function(){}());
for in
循环 遍历数组。 相反,有一些好的理由不去使用
for in
遍历数组。
for
循环。
var list = [1, 2, 3, 4, 5, ...... 100000000]; for(var i = 0, l = list.length; i < l; i++) { console.log(list[i]); }
l = list.length
来缓存数组的长度。
length
是数组的一个属性,可是在每次循环中访问它仍是有性能开销。 可能最新的 JavaScript 引擎在这点上作了优化,可是咱们无法保证本身的代码是否运行在这些最近的引擎之上。
length
属性的 getter 方式会简单的返回数组的长度,而 setter 方式会截断数组。
var foo = [1, 2, 3, 4, 5, 6]; foo.length = 3; foo; // [1, 2, 3] foo.length = 6; foo; // [1, 2, 3]
foo
的值是:
[1, 2, 3, undefined, undefined, undefined]
可是这个结果并不许确,若是你在 Chrome 的控制台查看
foo
的结果,你会发现是这样的:
[1, 2, 3]
由于在 JavaScript 中
undefined
是一个变量,注意是变量不是关键字,所以上面两个结果的意义是彻底不相同的。
// 译者注:为了验证,咱们来执行下面代码,看序号 5 是否存在于 foo 中。 5 in foo; // 无论在 Firebug 或者 Chrome 都返回 false foo[5] = undefined; 5 in foo; // 无论在 Firebug 或者 Chrome 都返回 true
length
设置一个更小的值会截断数组,可是增大
length
属性值不会对数组产生影响。
for
循环并缓存数组的
length
属性。 使用
for in
遍历数组被认为是很差的代码习惯并倾向于产生错误和致使性能问题。
Array
的构造函数在如何处理参数时有点模棱两可,所以老是推荐使用数组的字面语法 -
[]
- 来建立数组。
[1, 2, 3]; // 结果: [1, 2, 3] new Array(1, 2, 3); // 结果: [1, 2, 3] [3]; // 结果: [3] new Array(3); // 结果: [] new Array('3') // 结果: ['3'] // 译者注:所以下面的代码将会令人很迷惑 new Array(3, 4, 5); // 结果: [3, 4, 5] new Array(3) // 结果: [],此数组长度为 3
new Array(3);
这种调用方式),而且这个参数是数字,构造函数会返回一个
length
属性被设置为此参数的空数组。 须要特别注意的是,此时只有
length
属性被设置,真正的数组并无生成。
var arr = new Array(3); arr[1]; // undefined 1 in arr; // false, 数组尚未生成
for
循环的麻烦。
new Array(count + 1).join(stringToRepeat);
typeof
操做符(和
instanceof
一块儿)或许是 JavaScript 中最大的设计缺陷, 由于几乎不可能从它们那里获得想要的结果。
instanceof
还有一些极少数的应用场景,
typeof
只有一个实际的应用(
译者注:这个实际应用是用来检测一个对象是否已经定义或者是否已经赋值), 而这个应用却不是用来检查对象的类型。
Value Class Type ------------------------------------- "foo" String string new String("foo") String object 1.2 Number number new Number(1.2) Number object true Boolean boolean new Boolean(true) Boolean object new Date() Date object new Error() Error object [1,2,3] Array object new Array(1, 2, 3) Array object new Function("") Function function /abc/g RegExp object (function in Nitro/V8) new RegExp("meow") RegExp object (function in Nitro/V8) {} Object object new Object() Object object
typeof
操做符的运算结果。能够看到,这个值在大多数状况下都返回 "object"。
[[Class]]
的值。
[[Class]]
,咱们须要使用定义在
Object.prototype
上的方法
toString
。
[[Class]]
值的方法,那就是使用
Object.prototype.toString
。
function is(type, obj) { var clas = Object.prototype.toString.call(obj).slice(8, -1); return obj !== undefined && obj !== null && clas === type; } is('String', 'test'); // true is('String', new String('test')); // true
Object.prototype.toString
方法被调用,
this 被设置为了须要获取
[[Class]]
值的对象。
Object.prototype.toString
返回一种标准格式字符串,因此上例能够经过
slice
截取指定位置的字符串,以下所示:
Object.prototype.toString.call([]) // "[object Array]" Object.prototype.toString.call({}) // "[object Object]" Object.prototype.toString.call(2) // "[object Number]"
// IE8 Object.prototype.toString.call(null) // "[object Object]" Object.prototype.toString.call(undefined) // "[object Object]" // Firefox 4 Object.prototype.toString.call(null) // "[object Null]" Object.prototype.toString.call(undefined) // "[object Undefined]"
foo
是否已经定义;若是没有定义而直接使用会致使
ReferenceError
的异常。 这是
typeof
惟一有用的地方。
Object.prototype.toString
方法; 由于这是惟一一个可依赖的方式。正如上面表格所示,
typeof
的一些返回值在标准文档中并未定义, 所以不一样的引擎实现可能不一样。
typeof
操做符。
instanceof
操做符用来比较两个操做数的构造函数。只有在比较自定义的对象时才有意义。 若是用来比较内置类型,将会和
typeof
操做符 同样用处不大。
function Foo() {} function Bar() {} Bar.prototype = new Foo(); new Bar() instanceof Bar; // true new Bar() instanceof Foo; // true // 若是仅仅设置 Bar.prototype 为函数 Foo 自己,而不是 Foo 构造函数的一个实例 Bar.prototype = Foo; new Bar() instanceof Foo; // false instanceof比较内置类型 new String('foo') instanceof String; // true new String('foo') instanceof Object; // true 'foo' instanceof String; // false 'foo' instanceof Object; // false
instanceof
用来比较属于不一样 JavaScript 上下文的对象(好比,浏览器中不一样的文档结构)时将会出错, 由于它们的构造函数不会是同一个对象。
instanceof
操做符应该仅仅用来比较来自同一个 JavaScript 上下文的自定义对象。 正如
typeof
操做符同样,任何其它的用法都应该是避免的。
// 下面的比较结果是:true new Number(10) == 10; // Number.toString() 返回的字符串被再次转换为数字 10 == '10'; // 字符串被转换为数字 10 == '+10 '; // 同上 10 == '010'; // 同上 isNaN(null) == false; // null 被转换为数字 0 // 0 固然不是一个 NaN(译者注:否认之否认) // 下面的比较结果是:false 10 == 010; 10 == '-10';
Number
和
String
)的构造函数在被调用时,使用或者不使用
new
的结果彻底不一样。
new Number(10) === 10; // False, 对象与数字的比较 Number(10) === 10; // True, 数字与数字的比较
Number
做为构造函数将会建立一个新的
Number
对象, 而在不使用
new
关键字的
Number
函数更像是一个数字转换器。
'' + 10 === '10'; // true
+'10' === 10; // true
+'010' === 10 Number('010') === 10 parseInt('010', 10) === 10 // 用来转换为整数 +'010.2' === 10.2 Number('010.2') === 10.2 parseInt('010.2', 10) === 10
!!'foo'; // true !!''; // false !!'0'; // true !!'1'; // true !!'-1' // true !!{}; // true !!true; // true
eval
函数会在当前做用域中执行一段 JavaScript 代码字符串。
var foo = 1; function test() { var foo = 2; eval('foo = 3'); return foo; } test(); // 3 foo; // 1
eval
只在被直接调用而且调用函数就是
eval
自己时,才在当前做用域中执行。
var foo = 1; function test() { var foo = 2; var bar = eval; bar('foo = 3'); return foo; } test(); // 2 foo; // 3
eval
,和下面两种写法效果同样:
// 写法一:直接调用全局做用域下的 foo 变量 var foo = 1; function test() { var foo = 2; window.foo = 3; return foo; } test(); // 2 foo; // 3 // 写法二:使用 call 函数修改 eval 执行的上下文为全局做用域 var foo = 1; function test() { var foo = 2; eval.call(window, 'foo = 3'); return foo; } test(); // 2 foo; // 3
eval
函数。99.9% 使用
eval
的场景都有不使用
eval
的解决方案。
eval
也存在安全问题,由于它会执行任意传给它的代码, 在代码字符串未知或者是来自一个不信任的源时,绝对不要使用
eval
函数。
eval
,任何使用它的代码都会在它的工做方式,性能和安全性方面受到质疑。 若是一些状况必须使用到
eval
才能正常工做,首先它的设计会受到质疑,这不该该是首选的解决方案, 一个更好的不使用
eval
的解决方案应该获得充分考虑并优先采用。
undefined
。
undefined
是一个值为
undefined
的类型。
undefined
,这个变量也被称为
undefined
。 可是这个变量不是一个常量,也不是一个关键字。这意味着它的值能够轻易被覆盖。
undefined
值:
undefined
。return
表达式的函数隐式返回。return
表达式没有显式的返回任何内容。undefined
值的变量。undefined
只是保存了
undefined
类型实际值的副本, 所以对它赋新值不会改变类型
undefined
的值。
undefined
作比较,咱们须要事先获取类型
undefined
的值。
undefined
值的改变,一个经常使用的技巧是使用一个传递到
匿名包装器的额外参数。 在调用时,这个参数不会获取任何值。
var undefined = 123; (function(something, foo, undefined) { // 局部做用域里的 undefined 变量从新得到了 `undefined` 值 })('Hello World', 42); 另一种达到相同目的方法是在函数内使用变量声明。 var undefined = 123; (function(something, foo) { var undefined; ... })('Hello World', 42);
var
声明变量的状况下,这个版本的代码会多出 4 个字节的代码。
undefined
的使用场景相似于其它语言中的 null,实际上 JavaScript 中的
null
是另一种数据类型。
Foo.prototype = null
),可是大多数状况下均可以使用
undefined
来代替。
var foo = function() { } // 解析错误,分号丢失 test()
var foo = function() { }; // 没有错误,解析继续 test()
(function(window, undefined) { function test(options) { log('testing!') (options.list || []).forEach(function(i) { }) options.value.test( 'long string to pass here', 'and another long string to pass' ) return { foo: function() {} } } window.test = test })(window) (function(window) { window.someLibrary = {} })(window)
(function(window, undefined) { function test(options) { // 没有插入分号,两行被合并为一行 log('testing!')(options.list || []).forEach(function(i) { }); // <- 插入分号 options.value.test( 'long string to pass here', 'and another long string to pass' ); // <- 插入分号 return; // <- 插入分号, 改变了 return 表达式的行为 { // 做为一个代码段处理 foo: function() {} }; // <- 插入分号 } window.test = test; // <- 插入分号 // 两行又被合并了 })(window)(function(window) { window.someLibrary = {}; // <- 插入分号 })(window); //<- 插入分号
log('testing!') (options.list || []).forEach(function(i) {})
log('testing!')(options.list || []).forEach(function(i) {})
if
或者
else
表达式,也不该该省略花括号。 这些良好的编程习惯不只能够提到代码的一致性,并且能够防止解析器改变代码行为的错误处理。
setTimeout
和
setInterval
来计划执行函数。
function foo() {} var id = setTimeout(foo, 1000); // 返回一个大于零的数字
setTimeout
被调用时,它会返回一个 ID 标识而且计划在未来大约 1000 毫秒后调用
foo
函数。
foo
函数只会被执行一次。
setTimeout
指定的时刻被调用。
this
将会指向这个全局对象。
function Foo() { this.value = 42; this.method = function() { // this 指向全局对象 console.log(this.value); // 输出:undefined }; setTimeout(this.method, 500); } new Foo();
setTimeout
只会执行回调函数一次,不过
setInterval
- 正如名字建议的 - 会每隔
X
毫秒执行函数一次。 可是却不鼓励使用这个函数。
setInterval
仍然会发布更多的回调指令。在很小的定时间隔状况下,这会致使回调函数被堆积起来。
function foo(){ // 阻塞执行 1 秒 } setInterval(foo, 1000);
foo
会执行一次随后被阻塞了一分钟。
foo
被阻塞的时候,
setInterval
仍然在组织未来对回调函数的调用。 所以,当第一次
foo
函数调用结束时,已经有 10 次函数调用在等待执行。
setTimeout
函数。
function foo(){ // 阻塞执行 1 秒 setTimeout(foo, 1000); } foo();
setTimeout
回调函数,并且阻止了调用指令的堆积,能够有更多的控制。
foo
函数如今能够控制是否继续执行仍是终止执行。
clearTimeout
或者
clearInterval
函数来清除定时, 至于使用哪一个函数取决于调用的时候使用的是
setTimeout
仍是
setInterval
。
var id = setTimeout(foo, 1000); clearTimeout(id);
// 清空"全部"的定时器 for(var i = 1; i < 1000; i++) { clearTimeout(i); }
setTimeout
和
setInterval
也接受第一个参数为字符串的状况。 这个特性绝对不要使用,由于它在内部使用了
eval
。
function foo() { // 将会被调用 } function bar() { function foo() { // 不会被调用 } setTimeout('foo()', 1000); } bar();
eval
在这种状况下不是被
直接调用,所以传递到
setTimeout
的字符串会自全局做用域中执行; 所以,上面的回调函数使用的不是定义在
bar
做用域中的局部变量
foo
。
function foo(a, b, c) {} // 不要这样作 setTimeout('foo(1,2, 3)', 1000) // 可使用匿名函数完成相同功能 setTimeout(function() { foo(a, b, c); }, 1000)
setTimeout
或者
setInterval
的第一个参数, 这么写的代码明显质量不好。当须要向回调函数传递参数时,能够建立一个匿名函数,在函数内执行真实的回调函数。
setInterval
,由于它的定时执行不会被 JavaScript 阻塞。