先上几道面试题练练手
var bb = 1;
function aa(bb) {
bb = 2;
alert(bb);
}
aa(bb);
alert(bb);
var a="undefined";
var b="false";
var c="";
function assert(aVar){
if(aVar)
alert(true);
else
alert(false);
}
assert(a);
assert(b);
assert(c);
function Foo() {
var i = 0;
return function() {
console.log(i++);
};
}
Foo();
var f1 = Foo(), f2 = Foo();
f1();
f1();
f2();
var foo = true;
if (foo) {
let bar = foo * 2;
bar = something( bar );
console.log( bar );
}
console.log( bar );
var foo = true;
if (foo) {
var a = 2;
const b = 3; //仅存在于if的{}内
a = 3;
b = 4; // 出错,值不能修改
}
console.log( a ); // 3
console.log( b ); // ReferenceError!
闭包的深度递进
在JavaScript中,作用域是基于函数来界定的。也就是说属于一个函数内部的代码,函数内部以及内部嵌套的代码都可以访问函数的变量。
顺便讲讲常见的两种error,ReferenceError和TypeError。如上图,如果在bar里使用了d,那么经过查询都没查到,那么就会报一个ReferenceError;如果bar里使用了b,但是没有正确引用,如b.abc(),这会导致TypeError。
严格的说,在JavaScript也存在块级作用域。如下面几种情况:
with
var obj = {a: 2, b: 2, c: 2};
with (obj) { //均作用于obj上
a = 5;
b = 5;
c = 5;
}
let
let是ES6新增的定义变量的方法,其定义的变量仅存在于最近的{}之内。如下
var foo = true;
if (foo) {
let bar = foo * 2;
bar = something( bar );
console.log( bar );
}
console.log( bar ); // ReferenceError
const
与let一样,唯一不同的是const定义的变量值不能修改。如下:
var foo = true;
if (foo) {
var a = 2;
const b = 3; //仅存在于if的{}内
a = 3;
b = 4; // 出错,值不能修改
}
console.log( a ); // 3
console.log( b ); // ReferenceError!
了解这些了后,我们来聊聊闭包。什么叫闭包?简单的说就是一个函数内嵌套另一个函数,这就会形成一个闭包。这样说起来可能比较抽象,那么我们就举例说明。但是在距离之前,我们再复习下这句话,来,跟着大声读一遍,“无论函数是在哪里调用,也无论函数是如何调用的,其确定的词法作用域永远都是在函数被声明的时候确定下来的”。
来,下面我们看一个经典的闭包的例子:
for (var i=1; i<=9; i++) {
setTimeout( function timer(){
console.log( i );
},1000 );
}
运行的结果是啥捏?你可能期待每隔一秒出来1、2、3...10。那么试一下,按F12,打开console,将代码粘贴,回车!咦???等一下,擦擦眼睛,怎么会运行了10次10捏?这是肿么回事呢?咋眼睛还不好使了呢?不要着急,等我给你忽悠!
现在,再看看上面的代码,由于setTimeout是异步的,那么在真正的1000ms结束前,其实10次循环都已经结束了。我们可以将代码分成两部分分成两部分,一部分处理i++,另一部分处理setTimeout函数。那么上面的代码等同于下面的:
// 第一个部分
i++;
i++; // 总共做10次
// 第二个部分
setTimeout(function() {
console.log(i);
}, 1000);
setTimeout(function() {
console.log(i);
}, 1000); // 总共做10次
看到这里,相信你已经明白了为什么是上面的运行结果了吧。那么,我们来找找如何解决这个问题,让它运行如我们所料!
因为setTimeout中的匿名函数没有将i作为参数传入来固定这个变量的值,让其保留下来, 而是直接引用了外部作用域中的i, 因此i变化时,也影响到了匿名函数。其实要让它运行的跟我们料想的一样很简单,只需要将setTimeout函数定义在一个单独的作用域里并将i传进来即可。如下:
for (var i=1; i<=9; i++) {
(function(){
var j = i;
setTimeout( function timer(){
console.log( j );
}, 1000 );
})();
}
不要激动,勇敢的去试一下,结果肯定如你所料。那么再看一个实现方案:
for (var i=1; i<=9; i++) {
(function(j){
setTimeout( function timer(){
console.log( j );
}, 1000 );
})( i );
}
啊,居然这么简单啊,你肯定在这么想了!那么,看一个更优雅的实现方案:
for (let i=1; i<=9; i++) {
setTimeout( function timer(){
console.log( i );
}, 1000 );
}
咦?!肿么回事呢?是不是出错了,不着急,我这里也出错了。这是因为let需要在strict mode中执行。具体如何使用strict mode模式,自行谷歌吧
再整理一些面试题吧
var x = 1;
var y = 0;
var z = 0;
function add(n){n=n+1;}
y = add(x);
function add(n){n=n+3;}
z = add(x);
console.log(x,y,z);
//两个函数没有返回值,打印1 undefined undefined
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25, 正常结果
getAge(); // NaN
单独调用函数getAge怎么返回了NaN?请注意,我们已经进入到了JavaScript的一个大坑里。JavaScript的函数内部如果调用了this,那么这个this到底指向谁?
答案是,视情况而定!如果以对象的方法形式调用,比如xiaoming.age(),该函数的this指向被调用的对象,也就是xiaoming,这是符合我们预期的。
如果单独调用函数,比如getAge(),此时,该函数的this指向全局对象,也就是window。
坑爹啊!
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var that = this; // 在方法内部一开始就捕获this
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - that.birth; // 用that而不是this
}
return getAgeFromBirth();
}
};
xiaoming.age(); // 25
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
另一个与apply()类似的方法是call(),唯一区别是:
apply()把参数打包成Array再传入;
call()把参数按顺序传入。
function foo() {
var x = 'Hello, ' + y;
alert(x);//hello,undefined
var y = 'Bob';
}
foo();
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。