经历语言奇怪特性的旅程
在这篇文章中我想总结一下我们在1月YYCJS聚会讨论的一些事情。这都是关于JavaScript的怪异的部分。你可以在Youtube上找到这个视频, 在yycjs.com/the-weird-parts 找到一些幻灯片,在 JSBin 找到在线编码的部分。
非常感谢@alexisabril的完整性检查, Assembly提供图像。另外,感谢审查这个Minko Gechev的博客(同名)谈论了一些其他的怪异的地方。
热身
进入我们所谈论的内容。我们可以使用(点)或[](方括号)操作符访问对象和对象属性,点操作符只接受有效的JavaScript变量名而方括号可以采用任何字符串:
var person = {
name: 'david',
'&weird property': 'YYCJS'
}
var prop = 'name';
person.name // -> David
person['name'] // -> David
person["name"] // -> David
person[prop] // -> David
person['&weird property'] // -> YYCJS
// ERROR
person.&weird property
给字符串使用单引号或双引号没有真正区别。保持一致是有意义,大家似乎普遍偏好单引号(在大多数键盘上少按一些键)。
函数实参
在一个函数中, arguments是一个特殊的类数组变量,它包含所有传递给它的参数:
function sum() {
var sum = 0;
for(var i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
sum(1, 2, 3, 4); // -> 10
一个函数通过不同数量的参数调用时,arguments通常用于模拟函数重载。
arguments不是一个真正的数组,它只是一个类数组对象拥有长度属性和索引。如果你想使用数组方法如push或pop,您可以使用jQuery.toArray()或这样的来转换他:
var args = Array.prototype.slice.call(arguments, 0);
真值和假值
在JavaScript中, 在一个逻辑表达式中除了实际的布尔值可以评估为真(真值)或假(falsy)。下列值将被视为falsy:
false
null
undefined
0
'' // empty string
person.undefinedPropertys
例如, 他们可以在一个if语句中判断
var person = { name: 'David' }
if(person.name) {
// Do stuff if property exists and is not falsy
}
注意:这并不适用于未声明的变量名。如果你想看看一个变量是否存在(而不是没有定义),使用typeof运算符:
if(typeof myvar !== 'undefined') {}
一个逻辑表达式的结果并不总是真实的布尔值。使用| |(或)将返回第一个真值或表达式的最后一个值。这通常用于将默认值分配给一个对象或一个函数的默认参数:
person.meetup = person.meetup || defaults.meetup || 'YYCJS';
function sum(first, second) {
first = first || 0;
second = second || 0;
return first + second;
}
sum() // -> 0 instead of NaN
sum(1) // -> 1 instead of NaN
注:我建议重新分配函数参数变量设置默认值, 如果它在任何其他方式将改变请使用局部变量。
等式
真值和假值是等式比较。基本的= =(等于)和!=(不等于)操作符只有比较值(不比较类型),例如:
1 == 1 // -> true 1 == ‘1' // -> true 1 == 2 // -> false // 下面的就奇怪了 '' == false // -> true [] == false // -> true, [] not falsy though null == undefined // -> true
= = =(三重等于)和!= =(三重不等于?)操作符比较值和类型:
1 === 1 // -> true对象总是比较他们的引用,所以如果它实际上是相同的对象,这些表达式只会是真。
1 === '1' // -> false
1 === parseInt('1') // -> true
[] === false // -> false
null === undefined // -> false
var person = { name: 'David' }; var otherPerson = person; person === { name: 'David' } // -> false, created new object person === otherPerson // -> true, same reference为达到一致性和避免遇到很麻烦的错误总是使用= = =和! = =。有时这可能意味着编写更多的代码 (如转换字符串如果你期望得到一个数字),但它会让事情更容易和更可预测。
范围
JavaScript只知道函数范围。这意味着块或for和while循环不引入一个新的变量范围,有效地让它和在这个函数开始的地方声明每个变量一样。
函数里的其他函数可以访问他们父范围(关闭)的变量而其他的方式不能访问。
var x = 'outer';有时你会看到所有的变量被声明在函数的开始部分。这主要是一个风格的问题,不是必须的,如果变量名只是描述性的或者函数很小。
function count() {
for(var i = 0; i < 10; i++) {
var x = 'testing';
var inner = function() {
var i = 42;
// -> x === 'testing'
// -> i === 42
}
inner();
// -> i === current count } } // -> x === 'outer'
闭包
让我们看看下面的jQuery代码片段,这段代码创建十个按钮,添加一个单击处理器并将它们附加到body。
for(var i = 0; i < 10; i++) {
var button = $('<button>Button #' + i + '</button>');
button.click(function() {
alert(i);
});
$('body').append(button);
}
然而点击任何按钮不会按你期望的弹出这个按钮的数字,而是每个按钮都弹出数字10。
原因仍然是变量作用域。单击处理函数从他的父作用域访问相同的变量,因为它总是在循环完成后执行,它不管在哪都将具有相同的值。
这里的窍门是通过包装器函数引入一个新的范围,用当前计数调用它。它将被复制到一个新的变量计数器:
for(var i = 0; i < 10; i++) { var button = $('<button>Button #' + i + '</button>'); var wrapper = function(counter) { button.click(function() { alert(counter); }); }只有原始值(字符串、布尔值、数字)将被复制。如果你传递一个对象,你会得到同样的对象引用,重要的是要知道你是否打算修改该对象。
wrapper(i); $('body').append(button); }
全局变量
在浏览器环境中,没有var声明的变量将自动成为全局性的,在Node中将成为模块的全局变量:
function test() { var local = 42; global = 'global'; }这可能是总是无意的。在Node和新生浏览器中避免意外的全局变量泄漏的好方法是使ECMAScript 5严格模式可用,将:
test();
'use strict';
用在一个函数或JavaScript文件的开头。一个全局变量泄漏将抛出一个ReferenceError引用错误。
如果你需要全局变量, 可以明确地通过这个全局对象(在浏览器中是Window,在Node中是global)添加和访问它们:
function test() { var local = 42; window.global = 'global'; } test();
this是什么?
在任何函数,this是一个保留字,指的是函数的所有者。例如当调用一个定义在一个对象中的函数,它将是对象本身:
var person = { name: 'David', sayHi: function() { return 'Hello ' + this.name; } }
person.sayHi(); // -> 'Hello David'
this也用在事件处理程序函数中,它指触发这个事件的一个或多个元素:
document.getElementById('mybutton').addEventListener('click',
function() {
this.innerHTML = 'Button clicked';
});
三位一体的this
能够改变一个函数的所有者可能会导致一些混淆,this当前指的到底是什么,但只有三个规则可以说明this指什么:
1. 对象,当函数在这个对象里被调用(使用“.”或[ ]操作符)
2. 新创建的对象,当一个函数被new操作符创建的对象调用
3. 被调用者设置为call,apply或bind
var person = { name: 'David', sayHi: function() { return 'Hello ' + this.name; } }; function Dog(name) { this.name = name; } var otherPerson = { name: 'Eric' }; // 1) The object, when the function is called on an object person.sayHi(); // -> 'Hello David' person['sayHi'](); // -> 'Hello David' // 2) The new object when a function is called with new var goofy = new Dog('Goofy'); goofy.name // -> 'Goofy' // 3) The owner has been set with call, apply or bind person.sayHi.call(otherPerson); // -> 'Hello Eric' var hi = person.sayHi.bind(otherPerson); hi(); // Hello Eric否则this在严格模式下将是全局或未定义的对象:
var ownerless = person.sayHi;
ownerless() // "Hello " (or window.name or ReferenceError)
this和callback
回调用于任何类型的异步操作。一个常见的陷阱是忘记在任何新的回调过程中你可能会失去原来的this。保持引用您将需要将其存储在一个新的变量里,这样它就可以被引用(通过闭包):
$('button').click(function() { // Store the old this reference (the clicked button) var self = this; $.getJSON('someFile.json', function(data) { // Set the button content $(self).html(data.text); }); });
总结
这绝对是一个不完整的JavaScript缺陷列表,尤其是刚开始使用JavaScript你可能会被很多事情绊倒。在后续的帖子我想谈谈JavaScript中的继承 (这也会发布在meetup上)。
还不错,支持一下
在这里输入代码