问答:
1. 函数声明和函数表达式有什么区别 (*)
在日常的任务中,JavaScript主要使用下面两种方式创建函数:
- 函数声明:
function sayName(){
console.log('hunger');
}
sayName();
- 函数表达式
var sayName = function(){
console.log('hunger');
};
区别:
a. 函数声明必须以function开头,否则,则为表达式。
b. 函数表达式在语句的结尾要加上分号,表示语句结束,而函数声明则不需要在结尾加上分号。
c. 函数声明在JS解析的时候会进行函数提升,即在同一个作用域内,不管函数声明在哪里创建,它会提升至作用域顶部。
但是函数表达式不会被提升,它的表现和声明变量提升的规则相同,也就是说,函数名(这里指var后面定义的变量名)会声明前置,但是函数表达式的赋值没有被提前,操作还是在原来的位置。
2. 什么是变量的声明前置?什么是函数的声明前置 (**)
- 变量的声明前置:JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升。
如:
上面运行的代码会报错,是因为我们没有声明变量xxx。
接下来我们将声明变量xxx
我们发现,变量是在语句调用之后定义的,但是结果并没有报错。这就是变量声明前置的作用。JS解析器会把当前作用域内声明的所有变量和函数都放到作用域的开始。但是对于变量来说,它只是把变量的声明提到了作用域的开始,而变量的赋值仍然按照顺序执行。那么没有被赋值的变量会自动赋值为undefined,所以此处运行的结果为初始值undefined。它等同于如下代码:
var xxx;
console.log(xxx);
xxx = 2;
再来看看如下的列子:
我们先声明一个全局变量xxx,然后再函数内部定义一个局部变量。我们希望是第一次打印出来的结果是全局范围内定义的xxx变量,第二次打印出来的是局部变量xxx的值。但是输出结果并没有跟我们设想的一样,原因就是定义的局部变量在其作用域内声明提前了。故第一次打印出来的是没有赋值的undefined,第二次打印出12。它等同于如下代码:
xxx = 2;
(function(){
var xxx;
console.log(xxx);
xxx = 12;
console.log(xxx);
})();
- 函数的声明前置:和变量的声明会前置一样,函数声明同样会前置,如果我们使用函数表达式那么规则和变量一样;如果我们使用函数声明的方式,那么即使函数写在最后也可以在前面语句调用,前提是函数声明部分已经被下载到本地。
- 函数表达式:
在上面代码中,变量sayAge被前置了,但是它的赋值并没有被提前,这样就印证了函数表达式的提前和变量提前是一回事了。上面的代码等同于:
var sayAge;
sayAge(10);
sayAge = function(age){
console.log(age);
}
-
函数声明:
函数声明并不仅仅是函数名被提前了,整个函数的定义都被提前了。它等同于如下代码:
function fn(){
console.log('1');
}
fn();
总结:
- 变量声明会提前到作用域的顶部,而赋值会被保留在原地,仍然按照次序执行。
- 函数表达式是变量前置了,但是赋值没有提前。和变量提升一样。
- 函数声明整个会被前置到变量声明的后面,即使函数写在最后面也可以在前面语句调用。
那么,我们在分析代码的时候,可以把变量和函数声明放在作用域的顶部,这样分析出来的结果一般不容易出错。
3. arguments 是什么 (*)
arguments是JS里的一个内置对象,它只在函数内部有效,可以在函数内部通过使用arguments对象来获取函数的所有参数。这个对象为传递给函数的每个参数建立一个条目,条目的索引从0开始。参数也可以重新被赋值。
arguments对象就像数组(array),但是它却不是真正的数组,除了length,它没有数组所特有的属性和方法。
当一个函数的参数数量比它显示声明参数数量更多时候,我们就可以使用arguments对象获取到该函数的所有传入参数。
如:
4. 函数的重载怎样实现 (**)
重载是很多面向对象语言实现多态的手段之一,在静态语言中确定一个函数的手段是靠方法签名——函数名+参数列表,也就是说相同名字的函数参数个数不同或者顺序不同都被认为是不同的函数,称为函数重载。
在JavaScript中没有函数重载的概念,函数通过名字确定唯一性,参数不同也被认为是相同的函数,后面的覆盖前面的。
在JavaScript中可以通过arguments来实现函数的重载,如:
5. 立即执行函数表达式是什么?有什么作用 (***)
立即执行函数表达式(Immediately-Invoked Function Expression),是将函数定义放在一个圆括号里,让JavaScript引擎将其理解为一个表达式,再在函数的定义后面加一个()
,以达到定义函数后立即调用该函数的效果。有下面两种写法:
(function(){ /code/ }());
(function(){ /code/ })();
如:
作用:
- 封装大量的工作而不会在背后遗留任何全局变量。
- 定义的所有变量都会成为立即执行函数的局部变量,所以不用担心这些临时变量会污染全局空间。
- 可以将独立的功能封装在自包含模块中。
注意:立即执行函数通常作为一个单独模块使用。一般没有问题,但是,建议在自己写的立即执行函数前加分号,这样可以有效地与前面代码进行隔离。否则,可能出现意想不到的错误。
6. 什么是函数的作用域链 (****)
- 作用域:作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在JavaScript中,变量的作用域有全局作用域和局部作用域两种。
- 全局作用域:在代码中任何地方都能访问到的对象拥有全局作用域,一般来说以下几种情形拥有全局作用域:
a. 最外层函数和在最外面定义的变量拥有全局作用域
var a = 2;
function fn(){
var b;
var c;
function fn2(){
console.log(a);
console.log(b);
}
b = 3;
fn2();
c = 4;
}
fn();
//在这段代码中,变量a 和函数fn时拥有全局作用域,而在函数里面的变量和函数并不拥有全局作用域
b. 所有未定义直接赋值的变量自动声明为拥有全局作用域
function fn(){
var a = 2;
b = 3;
console.log(a);
}//变量b用于全局作用域,而变量a则不是。
c. 所有window对象的属性拥有全局作用域
- 局部作用域:和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,最常见的如函数内部,所以在一些地方也会看到有人把这种作用域称为函数作用域。
function fn(){
var a = 2;
function fn2(){
console.log(a);
}
fn2();
} //在此代码中变量a和函数fn2用于局部作用域
- 作用域链:当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。
参考:
JavaScript 开发进阶:理解 JavaScript 作用域和作用域链
代码:
1. 以下代码输出什么? (难度**)
function getInfo(name, age, sex){
console.log('name:',name);
console.log('age:', age);
console.log('sex:', sex);
console.log(arguments);
arguments[0] = 'valley';
console.log('name', name);
}
getInfo('hunger', 28, '男');
getInfo('hunger', 28);
getInfo('男');
输出结果:
//getInfo('hunger', 28, '男');
name:hunger
age:28
sex:男
["hunger", 28, 男]
name valley
//getInfo('hunger', 28);
name:hunger
age:28
sex:undefined
["hunger", 28]
name valley
//getInfo('男');
name:男
age:undefined
sex:undefined
["男"]
name valley
2. 写一个函数,返回参数的平方和?如 (难度**)
function sumOfSquares(){
var s = 0;
for(var i = 0;i < arguments.length;i++){
s += arguments[i] * arguments[i];
}
console.log(s);
}
sumOfSquares(2,3,4);//29
sumOfSquares(1,3);//10`
3. 如下代码的输出?为什么 (难度*)
console.log(a); //输出undefined,因为变量提升了,但是没有赋值
var a = 1;
console.log(b);//输出b is not defined,因为变量b没有被申明`
4. 如下代码的输出?为什么 (难度*)
sayName('world'); //输出结果为hello world
sayAge(10); //输出结果为Uncaught TypeError: sayAge is not a function
function sayName(name){
console.log('hello ', name);
}
var sayAge = function(age){
console.log(age);
};
第一行代码由于函数声明提升,整个sayName函数提到代码头部,则在执行sayName(“world”)时输出正常结果;第二航代码由于sayAge是函数表达式,则JS引擎在解析代码时,只是把var sayAge提到代码的头部,并未把整个函数部分提到代码头部,所以在执行时会报错。
5. 如下代码的输出?为什么 (难度**)
function fn(){}
var fn = 3;
console.log(fn);//输出3,在同一个作用域内定义了名字相同的变量和方法的话,无论其顺序如何,变量的赋值会覆盖方法的赋值。
6. 如下代码的输出?为什么 (难度***)
function fn(fn2){
console.log(fn2);
var fn2 = 3;
console.log(fn2);
console.log(fn);
function fn2(){
console.log('fnnn2');
}
}
fn(10);
上面代码相当于:
function fn(fn2){
var fn2;//变量声明提升
function fn2(){
console.log('fnnn2');
}//函数声明提升
console.log(fn2);//当函数执行命令有冲突时,函数载入的顺序是变量>函数>参数,此时肯定是输出函数。
fn2=3;
console.log(fn2);//此时fn2被赋值3,因为在同一作用域内,定义了同一个名字的变量和方法时,无论顺序如何,变量的赋值会覆盖方法的赋值。
console.log(fn);//当函数执行命令有冲突时,函数载入的顺序是变量>函数>参数,此时肯定是输出函数。
}
fn(10);
输出结果为:
function fn2(){
console.log('fnnn2');
}
3
function fn(fn2){
console.log(fn2);
var fn2 = 3;
console.log(fn2);
console.log(fn);
function fn2(){
console.log('fnnn2');
}
}
7. 如下代码的输出?为什么 (难度***)
var fn = 1;
function fn(fn){
console.log(fn);
}
console.log(fn(fn));
上述代码相当于:
var fn;
function fn(fn){
console.log(fn);
}
fn = 1;
console.log(fn(fn));//输出结果:Uncaught TypeError: fn is not a function(…)。在执行时,正确代码应该是函数(参数),在这个习题中,由于变量命名提升,代码形式为变量(变量),那么交给浏览器去执行时会输出:fn is not a function(…)。
8. 如下代码的输出?为什么 (难度**)
//作用域
console.log(j);
console.log(i);
for(var i=0; i<10; i++){
var j = 100;
}
console.log(i);
console.log(j);
上述代码相当于:
var i;
var j;
//变量提升,将其 提到代码头部
console.log(i);//undefined,此时变量i还未被赋值
console.log(j);//undefined,此时变量j还未被赋值
for(var i = 0; i<10; i++){
var j = 100;
console.log(i);//10 在for循环执行后,i为10,for循环语句不会前置,其定义的变量自然就是全局变量,所以能够被解析,正常顺序执行并显示。
console.log(j);`//100 在for循环执行后,j为100。
9. 如下代码的输出?为什么 (难度****)
fn();
var i = 10;
var fn = 20;
console.log(i);
function fn(){
console.log(i);
var i = 99;
fn2();
console.log(i);
function fn2(){
i = 100;
}
}
上述代码相当于:
var i;
var fn;
//变量提升,将var i和var fn提升到代码头部
function fn(){
var i;
function fn2(){
i = 100;
}
console.log(i);//输出undefined,因为变量i声明了但是没有赋值
i = 99;
fn2();//执行后i为100,覆盖了i = 99
console.log(i);//输出100
}
fn();
i = 10;
fn = 20;
console.log(i);//输出为10.
10. 如下代码的输出?为什么 (难度*****)
var say = 0;
(function say(n){
console.log(n);
if(n<3) return;
say(n-1);
}( 10 )); //输出10,9,8,7,6,5,4,3,2
/*function前后加了圆括号,表示该函数为立即执行函数,因此会马上执行,尾部赋值n=10,得到n-1=9,然后n=9,继续循环,直到n=2时,满足if条件,return终止,不执行n-1;所以最终结果为2*/
console.log(say);`//输出0
/*因为在该作用域中,变量say已经被赋值了0,在同一个作用域中,变量和方法同名时,无论顺序如何,变量的赋值会覆盖方法的赋值*/
本文版权归本人及饥人谷所有,转载请注明出处