一、 作用域问题
(1)全局作用域
var x = 0;
var f = function() {
var x = 2;
}
f();
alert(x); // 0
这个函数运行时,外面alert(x)
拿到的是全局作用域中x的值,第一个声明的var x=0;
的值是全局作用域中的值,第二个在f函数中声明的var x=2
的值是局部作用域的值,因为用var重新声明了一个变量,两个指向的是不同的作用域,因此alert(x)
返回的是0
(2)局部作用域
如果我们把var x=2;中的var去掉,变成下面这样子
var x = 0;
var f = function() {
x = 2;
}
f();
alert(x); // 2
出现这种情况是因为,var x=0;
声明的是全局作用域的值,f()函数也是全局作用域,然后f函数中的值。x会覆盖掉外部的值,因此就会导致得到的值是2
-
再来看一个例子
var x = 2, y = 3; function f(i) { x = x + 1; y = y + 1; i = i + 10; } f(10); alert(x); // 3 alert(y); // 4 alert(i); // i is not defined
这个也是因为x,y有声明并且在全局作用域中,这时就能得到x,y的值,而i是在f函数中命名的,在外部的全局中就不能调用到。
如果将alert()语句放在f函数中,值为3,4,20
二、 函数覆盖问题
来看一些函数覆盖的问题:
var x = 0;
var f = function f(){
x = 1;
}
f();
alert(x); // 1
function f(){
x = 2;
}
f();
alert(x); // 1
这里f函数将x的值置为1,然后虽然两次调用f()函数,但是函数已经产生覆盖,第一个函数覆盖了第二个函数,因此两次调用都是1
如果将上面的匿名函数f注释掉,如下
var x = 0;
f();
alert(x); // 2
function f(){
x = 2;
}
f();
alert(x); // 2
这时,已经不发生函数覆盖,就一直是2,
如果将命名函数注释掉
var x = 0;
var f = function f(){
x = 1;
}
f();
alert(x); // 1
f();
alert(x); // 1
像这样,也不发生函数覆盖,也是只调用f函数、
要解决函数覆盖的方法,就先写命名函数,再写匿名函数
var x = 0;
function f(){
x = 2;
}
f();
alert(x); // 2
var f = function() {
x = 1;
}
f();
alert(x); // 1
这样的话,命名函数f()先调用,匿名函数f后调用
总结:以后写函数时,尽量写成匿名函数形式,var f = function(){}
三、变量和函数提升问题
-
函数未定义
console.log(x);
// x is not defined变量未定义,直接是undefined
-
函数已定义
console.log(x); // undefined
var x = '123';
console.log(x); // 123;
这里声明了变量,js解释器“前瞻性”
查找所有变量定义,把它们“提升”到函数顶部,就出现undefined,第一个未定义就出现undefined,第二个定义了就出现1223
- 内部变量重复定义
var name = 'Baggins';
(function() {
console.log('Orginal name was:', name); // Orginal name was: undefined
var name = 'underhill';
console.log('New name is:', name); // New name is: underhill
})();
这里使用了闭包函数,第一个name变量提升,就会出现undefined,本来可以访问到全局作用域的name,但是内部已经重新声明了,name变量提升,就会出现undefined。
这里典型的使用了函数声明提前,函数体内的块级作用域覆盖住了外部name的全局作用域,然后由于函数声明提升,相当于在函数体内,先var name
;然后console.log("Original name was"+name)
;然后name = "Underhill";console.log("New name is" + name);
最后的值是一样的。
因此,建议所有变量的定义,都放在头部,并使用var a=XX,b=xxx这样定义。
- 函数直接调用
isItHoisted();
function isItHoisted() {
console.log('Yes!'); // Yes
}
这里函数直接调用就没什么好说的
- 函数定义提升
函数定义提升仅仅作用域函数定义,而不作用于函数表达式
这里函数definitionHoisted()
,已经提升,而函数定义不作用于函数表达式,因此就抛出TypeError
- 命名函数表达式
funcName();
varName();
var varName = function funcName() {
console.log('Definition not hoisted!');
}
在这里,函数名字是funcName()
是函数的一部分,不会得到提升,而varName()也找不到这个方法
- 闭包函数与全局函数
function f1() {
var a = 999;
nAdd = function() {
a = a + 1;
}
function f2() {
console.log(a);
}
return f2;
}
var result = f1();
result(); // 999
nAdd(); // 1000
result(); // undefined
-
var result = f1();就调用了f1()函数,这时result即为f2,函数,result是全局变量
-
result() 调用完之后的值为999,这时就是调用了f2函数,并将值输出
-
nAdd(),调用完之后,这时nAdd()函数为全局函数,也是闭包函数,值为1000,
-
result(),这个调用f2函数,就输出1000.
-
闭包中的命名提升。
function(a) {
console.log(a); // 1000
a = 999;
function(a) {
console.log(a); // 999
}
}(1000);
这里输出的值是999,因为1000传入进去,a的值被覆盖了,a变量命名提升,输出就是999;
- 函数覆盖问题
var m = 1,j = k = 0;
function add(n) {
return n = n + 1;
}
y = add(m);
function add(n) {
return n = n + 3; // 4
}
在js函数中,是没有函数重载的,以前学java的时候,当函数名是相同的,主要传递的类型或者参数不同就会出现重载,两个都可以用,但是在js中
主要出现两个同名的函数,就会出现函数覆盖,第二个add函数,覆盖第一个add函数,然后,由于函数声明提前,就会直接将函数覆盖。
-
那么什么是函数声明提前呢?
- 函数声明提前就是变量在变量声明之前就是已经可用,一般用在函数体内,在一个函数体内是块级作用域,一般函数,内部同名函数会覆盖外部同名函数,然后又由于函数声明提前,就可以有值。
That's all,Thank you!