1. var let const的区别
随着ES6规范的到来,Js中定义变量的方法已经由单一的 var 方式发展到了 var、let、const 三种之多。
下文来讨论下这三种方式的区别
1.1 Js没有块级作用域
请看这样一条规则:在JS函数中的var声明,其是函数体的全部。
for(var i=0;i<10;i++){ var a = 'a';}console.log(a);
跳出了循环的变量a和i,在循环外能被访问到。
首先解释一下变量提升:
JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)。
这就导致了var声明的变量没有块级作用域。
1.2 循环内变量过度共享
for (var i = 0; i < 3; i++) { setTimeout(function () { console.log(i) }, 1000);}
输出结果为3,3,3而不是0,1,2。循环本身及三次 timeout 回调均共享唯一的变量 i 。当循环结束执行时,i 的值为3。所以当第一个 timeout 执行时,调用的 i 当让也为 3 了。
1.3 let与const
ES6中let解决了上面var的问题,其特点概括如下:
- let声明的变量拥有块级作用域。 也就是说用let声明的变量的作用域只是外层块,而不是整个外层函数。let 声明仍然保留了提升特性,但不会盲目提升,在示例一中,通过将var替换为let可以快速修复问题,如果你处处使用let进行声明,就不会遇到类似的bug。
- let声明的全局变量不是全局对象的属性。这就意味着,你不可以通过window.变量名的方式访问这些变量。它们只存在于一个不可见的块的作用域中,这个块理论上是Web页面中运行的所有JS代码的外层块。
- 形如for (let x...)的循环在每次迭代时都为x创建新的绑定。 这是一个非常微妙的区别,拿示例二来说,如果一个for (let...)循环执行多次并且循环保持了一个闭包,那么每个闭包将捕捉一个循环变量的不同值作为副本,而不是所有闭包都捕捉循环变量的同一个值。 所以示例二中,也以通过将var替换为let修复bug。 这种情况适用于现有的三种循环方式:for-of、for-in、以及传统的用分号分隔的类C循环。
- 用let重定义变量会抛出一个语法错误(SyntaxError)。这个很好理解,用代码说话
let a = 'a';let a = 'b';
上述写法是不允许的,浏览器会报错,因为重复定义了。
const更好理解,可以想象成Java中的final(不过,const 关键字并不会限制对 object 的修改),这里不多做阐述了。
经验阐述:能用 const 尽量用 const,不要轻易的 mutate object,代码结构复杂后会有好处。
2. 箭头函数
ES6标准新增了一种新的函数:Arrow Function(箭头函数)。
x => x * x
上面的箭头函数相当于:
function (x) { return x * x;}
箭头函数相当于匿名函数(
(function(){...})();
),并且简化了函数定义。箭头函数有两种格式,一种像上面的,只包含一个表达式,连{ ... }
和return
都省略掉了。还有一种可以包含多条语句,这时候就不能省略{ ... }
和return
:
x => { if (x > 0) { return x * x; } else { return - x * x; }}
如果参数不是一个,就需要用括号()括起来:
// 两个参数:(x, y) => x * x + y * y// 无参数:() => 3.14// 可变参数:(x, y, ...rest) => { var i, sum = x + y; for (i=0; i
如果要返回一个对象,就要注意,如果是单表达式,这么写的话会报错:
// SyntaxError:x => { foo: x }
因为和函数体的{ ... }
有语法冲突,所以要改为:
// ok:x => ({ foo: x })
2.1 this
箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this
是词法作用域,由上下文确定。
回顾前面的例子,由于JavaScript函数对this
绑定的错误处理,下面的例子无法得到预期结果:
var obj = { birth: 1990, getAge: function () { var b = this.birth; // 1990 var fn = function () { return new Date().getFullYear() - this.birth; // this指向window或undefined }; return fn(); }};
现在,箭头函数完全修复了this
的指向,this
总是指向词法作用域,也就是外层调用者obj
:
var obj = { birth: 1990, getAge: function () { var b = this.birth; // 1990 var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象 return fn(); }};obj.getAge(); // 25
如果使用箭头函数,以前的那种hack写法:
var that = this;
就不再需要了。
由于this
在箭头函数中已经按照词法作用域绑定了,所以,用call()
或者apply()
调用箭头函数时,无法对this
进行绑定,即传入的第一个参数被忽略:
var obj = { birth: 1990, getAge: function (year) { var b = this.birth; // 1990 var fn = (y) => y - this.birth; // this.birth仍是1990 return fn.call({birth:2000}, year); }};obj.getAge(2015); // 25
3. call & apply
call和apply作用很相似,函数执行时,在某个指定作用域下调用。只是参数的写法不同,
call(this,arg1,arg2..)apply(this.argunments)
比如
obj1.method1.call(obj2,argument1,argument2)
call的作用就是把obj1的方法放到obj2上使用,后面的argument1..这些做为参数传入。
function add(a,b) { alert(a+b); } function sub(a,b) { alert(a-b); } add.call(sub,3,1);
上述代码的意思就是用add来替换sub,add.call(sub,3,1) == add(3,1) ,所以运行结果为:alert(4); // 注意:js 中的函数其实是对象,函数名是对 Function 对象的引用。
Reference:
1. http://www.jianshu.com/p/4e9cd99ecbf5
2. http://www.cnblogs.com/52fhy/p/5117267.html
3. https://www.zhihu.com/question/34294629?sort=created
4. http://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001438565969057627e5435793645b7acaee3b6869d1374000
5. http://www.cnblogs.com/sweting/archive/2009/12/21/1629204.html