你应该知道的 JavaScript

2,613 阅读6分钟
原文链接: youbookee.com

Code One


if (!(“a” in window)) {
var a = 1;
}
alert(a);

这个奇怪的代码似乎是这样: “如果window 没有属性a, 定义一个变量a并且赋值1”, 你可能会认为应该alert 1。 实际上,alert的是”undefined”。 为了理解发生了什么,你应该知道JavaScript的四件事。

 

首先, 所有的全局变量都是window的属性。 var a = 1 完全等价于window.a = 1
第二, 所有的变量声明都会被提升(hositing)至所在作用域的顶部, 这个是JavaScript一大特性。看看,下面的这个例子


alert(“a” in window);
var a;

上面的例子会alert出”true”即使变量声明在alert()之后。这是因为JavaScript引擎会首先扫描所有变量生命并把他们hositing至作用域顶部。上面那段代码相当于这样:


var a;
alert(“a” in window);

第三件事你应该理解只有变量声明被hositing而变量初始化没有


var a = 1;

你可以拆分看待上面的代码


var a;
a = 1;

实际上JavaScript引擎也是这么做的, 当有变量声明并初始化,JavaScript会自动拆分变量声明和变量初始化。那为什么没有提升(hositing)变量初始化? 因为这样会影响变量值在代码执行期间,导致不可预料的结果。

最后一点是JavaScript Scoping, 很多新手不能理解甚至很多有经验的JavaScript程序员也不能完全理解scoping。JavaScript的scoping如此复杂原因是他看起来像C系语言的成员, 看看下面的C程序:


#include 
int main() {
int x = 1;
printf(“%d”, x); // 1
if (1) {
int x = 2;
printf(“%d”, x); // 2
}
printf(“%d”, x); // 1
}

上面输出的是121这是因为C系语言有块级作用域(block-level scope),但JavaScript却不是这样。


var x = 1;
console.log(x); // 1
if (true) {
var x = 2;
console.log(x); // 2
}
console.log(x); // 2

上面console出122, 这是因为JavaScript是函数级作用域(function-level scope), 像if语句是不会创建新的作用域的。只有函数才会创建新的作用域。

如果你理解上面四点,那么上面的例子你肯定可以知道为什么alert的是”undefined”, 上述的代码实际上是这样执行的。


var a;
if (!(“a” in window)) {
a = 1;
}
alert(a);

不难看出alert的是”undefined”, 声明了变量a, 所有!("a" in window)的是结果是false; 变量声明了但是没有初始化, 所以alert “undefined”。

Code Two


var a = 1,
b = function a(x) {
x && a(–x);
};
alert(a);

这段代码看起来比实际上复杂得多。结果是alert “1”。 不用太困惑。同样的,这段代码需要你知道关于JavaScript的三件事。

首先, 变量声明提升。上一个例子已经讲过。第二个方面是函数声明提升。所有的函数声明同变量声明一样都会被提升至当前的作用域顶部。

很重要的一点是,一个函数声明如下:


function functionName(arg1, arg2) {
// function body
}

对应是函数表达式, 其实也就是变量初始化赋值。


var functionName = function(arg1, arg2) {
//function body
};

函数表达式并不会被提升。这其实也就是变量初始化。

第三点就是你必须知道和理解函数声明会覆盖了变量声明但没有覆盖变量初始化。为了理解这一点,看看下面的例子


function value() {
return 1;
}
var value;
alert(typeof value); // “function”

上面的value作为function而结束了,即使变量声明出现在函数声明之后。在这种情况下函数声明会获得更高的优先权。
但是下面的例子会有不同的结果。


function value() {
return 1;
}
var value = 1;
alert(typeof value); // “number”

现在value的值为1, 变量初始化覆盖的函数声明。

我们再看一个例子:


var a = 1;
function foo() {
a = 10;
return;
function a() {}
}
foo();
alert(a);

上面的例子会alert 1, 上面的这段代码实际执行是这样的,


var a;
function foo() {
function a() {}
a = 10;
return;
}
a = 1;
foo();
alert(a);

这样你应该就不难理解为什么会是1了吧。

回到Code Two, 那个函数实际上是函数表达式即使有函数名a, 有名称的函数表达式并不会被认为是函数声明因此不会被覆盖变量声明。不过,你可能会发现包含函数表达式的变量是b, 而函数表达式的名称是a。 不同浏览器会做不同的处理,IE会认为function a() {}是函数声明,因此它会被变量初始化覆盖,意味这当调用a(--x)是会抛出一个错误。其他浏览器允许在函数里调用a(--x)即使函数外部a的类型的number。 同时,在IE中调用b(2)会抛出一个error但是在其他浏览器中抛出undefined

Code Two 可以简化为更加正确并容易理解的代码,如下:


var a = 1,
b = function(x) {
x && b(–x);
}
alert(a);

Code Three


function b(x, y, a) {
arguments[2] = 10;
alert(a);
}
b(1, 2, 3);

这段代码相对简单, 你只需回答是3, 或是 10。 ECMA-262, 3rd Edition, 10。1。8节解释关于arguments对象:

For each non-negative integer, arg, less than the value of the length property, a property is created with name ToString(arg) and property attributes { DontEnum }。 The initial value of this property is the value of the corresponding actual parameter supplied by the caller。 The first actual parameter value corresponds to arg = 0, the second to arg = 1, and so on。 In the case when arg is less than the number of formal parameters for the Function object, this property shares its value with the corresponding property of the activation object。 This means that changing this property changes the corresponding property of the activation object and vice versa。

简而言之, 每一个参数在arguments对象中都是一个同名的副本。也就是
arguments[0]指向x, arguments[1]指向y, 而arguments[2]指向a。 当传入的参数是b(1, 2, 3, 4)时, arguments[3]没有指向那个变量,但是它的值为4。

显而易见,alert 的是10。

Code Four


var b = 5;
function a() {
var b = 10;
alert(this.b);
}
a.call(null);

你应该理解关于JavaScript的两个方面。

首先, 你应该理解this对象是什么。 看看下面的例子:


var object = {
method: function() {
alert(this === object);
}
}
object.method(); // true

在上面的代码中, this指向object对象, 所以结果是true
在全局作用域中, this指向window,因此下面的this指向的则是window


function method() {
alert(this === window); //true
}
method();

理解了第一点你才能理解第二点, call()是干嘛的。 通过call()可以重新定义函数的执行环境,即this的指向, 第一个参数将成为this的指向对象, 随后的参数将作为参数传入函数。


function method() {
alert(this === window);
}
method(); // true
method.call(document); // false

当执行method.call(document)时, this指向的将是document

当传入函数为nullundefined时会怎样呢?

If thisArg is null or undefined, the called function is passed the global object as the this value。 Otherwise, the called function is passed ToObject(thisArg) as the this value。

所以此时传入的是window

PS: 还有一个apply()call()类似,不过只有两个参数,第二参数应该是数组。

理解上面讲的,很容易就可以知道答案是 5