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也存在块级做用域。以下面几种状况:app
with异步
var obj = {a: 2, b: 2, c: 2}; with (obj) { //均做用于obj上 a = 5; b = 5; c = 5; }
let函数
let是ES6新增的定义变量的方法,其定义的变量仅存在于最近的{}以内。以下this
var foo = true; if (foo) { let bar = foo * 2; bar = something( bar ); console.log( bar ); } console.log( bar ); // ReferenceError
constcode
与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!
了解这些了后,咱们来聊聊闭包。什么叫闭包?简单的说就是一个函数内嵌套另外一个函数,这就会造成一个闭包。这样提及来可能比较抽象,那么咱们就举例说明。可是在距离以前,咱们再复习下这句话,来,跟着大声读一遍,“不管函数是在哪里调用,也不管函数是如何调用的,其肯定的词法做用域永远都是在函数被声明的时候肯定下来的”。
来,下面咱们看一个经典的闭包的例子:ip
for (var i=1; i<=9; i++) { setTimeout( function timer(){ console.log( i ); },1000 ); }
运行的结果是啥捏?你可能期待每隔一秒出来一、二、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();