一、函数声明和函数表达式有什么区别 (*)
主要区别函数声明会有声明提升,而函数表达式的规则跟变量一样。
例:
源码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<script>
foo1(4);
foo2(3);
var foo2 = function(a){console.log(a)}
function foo1(i){console.log(i)}
</script>
</body>
</html>
相当于:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<script>
var foo2;//----变量提升
function foo1(i){console.log(i)};//---函数声明提升
foo1(4);//----可执行该函数
foo2(3);//----会报错,因为是从上到下的顺序的
foo2 = function(a){console.log(a)}
</script>
</body>
</html>
二、什么是变量的声明前置?什么是函数的声明前置 (**)
具体例子可以看第一道题已经po出demo;
JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)。
函数的声明前置就是把整个函数提升到当前作用域的最前面(位于前置的变量声明后面)。
三、arguments 是什么 (*)
arguments 是一个类数组对象。代表传给一个function的参数列表。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<script>
function demo() {
for(var i=0; i<arguments.length; i++)
console.log(arguments[i]);
console.log(arguments.length);
}
demo("a",2,3,4,5);
</script>
</body>
</html>
四、函数的重载怎样实现 (**)
什么是重载函数允许在同一范围中声明几个功能类似的同名函数,但是这些同名函数的形式参数必须不同,这就是重载函数。
重载函数的作用重载函数常用来实现功能类似而所处理的数据类型不同的问题。
JS的函数重载的实现在面向对象语言里重载是很重要的一个特性,而JavaScript这个自称面向对象的语言是没有直接提供重载。那么JavaScript是怎么实现(准确地讲应该叫“模拟”)的呢?
利用arguments实现重载arguments是JavaScript里的一个内置对象,包含了调用者传递的实际参数,而调用时只它和数组一样有个length属性;我们暂且把它当“数组”来理解吧,我们根据该数组的长度以及其元素的类型来选择不同的实现,从而模拟了重载。
在JavaScript中没有函数重载的概念,函数通过名字确定唯一性,参数不同也被认为是相同的函数,后面的覆盖前面的。所以我们这里通过arguments实现重载:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<script>
function demo() {
for(var i=0; i<arguments.length; i++)
console.log(arguments[i]);
}
demo(2,3,4,5);
demo("a","b");
</script>
</body>
</html>
五、立即执行函数表达式是什么?有什么作用 (***)
- 立即执行函数表达式(IIFE——Immediately Invoked Function Expression),是将函数的定义放在一个圆括号里,让JavaScript引擎将其理解为一个表达式,再在函数的定义后面加一个(),以达到定义函数后立即调用该函数的效果。下面两种形式在功能上是一致的:
(function(){ /* code */ }()); //立即执行函数表达式
(function(){ /* code */ })(); //立即执行函数表达式
- 立即执行函数表达式的作用
- 可以创建匿名函数,避免污染全局变量
- 防止其他js文件出现与本文件的函数名重名情况,那么函数内部定义的 同一个变量a有可能被污染;
- 形成一个单独的作用域,可以封装一些外部无法读取的私有变量
// 写法一
var tmp = newData;
processData(tmp);
storeData(tmp);//
写法二(function (){
var tmp = newData;
processData(tmp);
storeData(tmp);
}());
上面代码中,写法二比写法一更好,因为完全避免了污染全局变量
六、什么是函数的作用域链 (****)
解释:
当 Javascript 代码需要查询一个变量 x 的时候,会有一个被称为「变量名解析」的过程。它会首先检查作用域链的第一个对象,如果这个对象包含名为 x 的属性,那么就采用这个属性的值,否则,会继续检查第二个对象,依此类推。当检查到最后一个对象的时候仍然没有相应的属性,则这个变量会被认定为是「未定义」的。
举例:
<script type="text/javascript">
var rain = 1;
function rainman(){
var man = 2;
function inner(){
var innerVar = 4;
alert(rain);
}
inner(); //调用inner函数
}
rainman(); //调用rainman函数
</script>
观察alert(rain);这句代码。JavaScript首先在inner函数中查找是否定义了变量rain,如果定义了则使用inner函数中的rain变量;如果inner函数中没有定义rain变量,JavaScript则会继续在rainman函数中查找是否定义了rain变量,在这段代码中rainman函数体内没有定义rain变量,则JavaScript引擎会继续向上(全局对象)查找是否定义了rain;在全局对象中我们定义了rain = 1,因此最终结果会弹出'1'。
上面的代码涉及到了三个作用域链对象,依次是:inner、rainman、window。
七、代码题
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('男');
2、写一个函数,返回参数的平方和?如 (难度**)
利用arguments类数组对象实现;
function sumOfSquares(){
var result=0;
for(var i=0; i<arguments.length; i++){
result += arguments[i]*arguments[i];
}
console.log(result);
}
sumOfSquares(2,3,4); // 29
sumOfSquares(1,3); // 10
3、如下代码的输出?为什么 (难度*)
源码:
console.log(a);
var a = 1;
console.log(b);
可看做:
var a; //--------变量提升
console.log(a);//----log.undefined
a = 1;//---------赋值a
console.log(b);//------log.报错
4、如下代码的输出?为什么 (难度*)
源码:
sayName('world');
sayAge(10);
function sayName(name){
console.log('hello ', name);
}
var sayAge = function(age){
console.log(age);
};
可看做:
var sayAge;//-------变量声明提升
function sayName(name){---------函数声明提升
console.log('hello ', name);//----log. "hello,world"
}
sayName('world');//---调用函数
sayAge(10);
sayAge = function(age){
console.log(age);//--------log.报错,无此函数;因为sayAge(10)无法调用;
};
5、 如下代码的输出?为什么 (难度**)
源码:
function fn(){}
var fn = 3;
console.log(fn);
可看做:
var fn;
function fn(){}
fn = 3;
console.log(fn);//-------log.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 = 10;//---传参数给fn2赋值10
var fn2;//----变量声明提升
function fn2(){ //----函数提升,覆盖变量fn2变为函数
console.log('fnnn2');//--无,没有调用
}
console.log(fn2);//-----log.fn2函数
fn2 = 3;
console.log(fn2);//-----3
console.log(fn);//------log.fn函数
}
fn(10)
console.log('fnnn2'):因为没有调用fn2函数所有没有执行;
第一个console.log(fn2);显示是fn2函数,由于传进去的参数fn2=10被声明为变量后又被函数fn2给覆盖了,故log出的就是fn2函数;
第一个console.log(fn2);显示的是fn函数,首先会检查作用域链的第一个调用对象,没有找到fn该属性则去找第二个对象即全局对象,找到fn的属性是一个函数,故log.出的是fn()函数;
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)); //--------报错
根据命名冲突,执行console.log(fn(fn))时,fn为变量类型,值为1,所以报错,fn不为函数。
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(j);//---log.undefined
console.log(i);//---log.undefined
for(i=0; i<10; i++){
j = 100;
}
console.log(i);//----log.10
console.log(j);//----log.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
function fn(){
var i;
function fn2(){
i = 100;
}
console.log(i);//----log.undefined
i = 99;
fn2();
console.log(i); //----log.100
}
fn();
i = 10;
fn = 20;
console.log(i);//---log.10
第一条console.log(i):由于变量声明前置,但是还没初始化,所以输出的是undefined;
第二条console.log(i):先将i赋值为99,然后调用fn2()函数,执行函数i = 100, 因在本层fn2()调用对象内没有检索到声明,故寻下一层,下一层将i声明为变量,故作用再了这层作用域内,也就是在fn()这个调用对象里面;所以i输出100;
第三条console.log(i):因fn()调用对象内i为局部变量,是不会影响到外界的,所以i检索全局变量的值,i输出10;
10、如下代码的输出?为什么 (难度*****)
var say = 0;
(function say(n){
console.log(n);
if(n<3) return;
say(n-1);
}( 10 ));
console.log(say);
输出: 10 9 8 7 6 5 4 3 2//到2这里就跳出; 0
在立即执行函数say(n)中,if,return和say(n-1)组成了一个循环,从n的传递参数10开始,计算结果分别为10,9,8,7,6,5,4,3,2,当n为2时,首先console.log,然后发现n<3,循环退出;
由于(function say(n){console.log(n);if(n<3) return;say(n-1); }( 10 ));这个是立即执行函数,故这可以看作是一个封闭的不受外界影响也不影响外界的局部作用域,所以全局console.log(say);输出0