ES5 没有块级作用域,只有全局作用域和函数作用域,由于这一点,变量的作用域甚广,所以一进入函数就要马上将它创建出来。这就造成了所谓的变量提升。
ES5 的 "变量提升" 这一特性往往一不小心就会造成一下错误:
- var tmp = new Date();
- function f() {
- console.log(tmp);
- if (false) { //执行则undefined
- var tmp = "hello world";
- }
- }
- var s = 'hello';
- for (var i = 0; i < s.length; i++) {
- console.log(s[i]);
- }
- console.log(i); // 5
往常我们往往是使用
来解决这一问题的(比如自执行函数)。现在,基于这一问题,ES6 增加了
- 闭包
, 所以不再需要自执行函数了。
- 块级作用域
ES6 是是向后兼容的,而保持向后兼容性意味着永不改变 JS 代码在 web 平台上的行为,所以
创建的变量其作用域依旧将会是全局作用域和函数作用域。这样以来,即使拥有了块级作用域,也无法解决 ES5 的 "变量提升" 问题。所以,这里 ES6 新增了俩个新关键词:
- var
和
- let
。
- const
也可以使用 Object.freeze 将对象冻结
- const a = [];
- a.push('Hello'); // 可执行
- a = ['Dave']; // 报错
- const foo = Object.freeze({});
- // 常规模式时,下面一行不起作用;
- // 严格模式时,该行会报错
- foo.prop = 123; //
使用 let 和 const:
- var a = 1;
- window.a // 1
- let b = 1;
- window.b // undefined
我们知道,ES5 函数中的 this 指向的是运行时所在的作用域。比如
- function foo() {
- setTimeout(function() {
- console.log('id:', this.id);
- },
- 100);
- }
- var id = 21;
- foo.call({
- id: 42
- }); //id: 21
在这里,我声明了一个函数 foo, 其内部为一个延迟函数 setTimeout,每隔 100ms 打印一个 this.id。我们通过
来调用它,并且为这个函数设定作用域。它真正执行要等到 100 毫秒后,由于 this 指向的是运行时所在的作用域,所以这里的 this 就指向了全局对象 window,而不是函数 foo。这里:
- foo.call({id:42})
超时调用的代码都是在全局作用域中执行的,因此函数中 this 的值在非严格模式下指向 window 对象,在严格模式下是 undefined --《javascript 高级程序设计》
为了解决这一问题,我们往常的做法往往是将 this 赋值给其他变量:
- function foo() {
- var that = this;
- setTimeout(function() {
- console.log('id:', that.id);
- },
- 100);
- }
- var id = 21;
- foo.call({
- id: 42
- }); //id: 42
而现在 ES6 推出了箭头函数解决了这一问题。
标识符 => 表达式
- var sum = (num1, num2) = >{
- return num1 + num2;
- }
- // 等同于
- var sum = function(num1, num2) {
- return num1 + num2;
- };
- 圆括号
和
- 大括号
- return
针对 this 关键字的问题,ES6 规定箭头函数中的 this 绑定定义时所在的作用域,而不是指向运行时所在的作用域。这一以来,this 指向固定化了,从而有利于封装回调函数。
- function foo() {
- var that = this;
- setTimeout(() = >{
- console.log('id:', that.id);
- },
- 100);
- }
- var id = 21;
- foo.call({
- id: 42
- }); //id: 42
注意:箭头函数 this 指向的固定化,并不是因为箭头函数内部有绑定 this 的机制,实际原因是箭头函数根本没有自己的 this。而箭头函数根本没有自己的 this,其内部的 this 也就是外层代码块的 this。这就导致了其:
传统 ECMAScript 没类的概念,它描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。而实现这一行为的传统方法便是通过构造函数:
- function Point(x, y) {
- this.x = x;
- this.y = y;
- }
- Point.prototype.toString = function() {
- return '(' + this.x + ', ' + this.y + ')';
- };
- var p = new Point(1, 2);
在这里,构造函数 Point 会有一个原型对象(prototype),这个原型对象包含一个指向 Point 的指针 (constructor),而实例 p 包含一个指向原型对象的内部指针 (prop)。所以整个的继承是通过原型链来实现的。详情可见我的这篇文章:
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过 class 关键字,可以定义类。但是类只是基于原型的面向对象模式的语法糖。对于
的引入,褒贬不一,很多人认为它反而是一大缺陷,但对我来说,这是一个好的语法糖,因为往常的原型链继承的方式往往能把我绕那么一会儿。
- class
- //定义类
- class Point {
- constructor(x, y) {
- this.x = x;
- this.y = y;
- }
- toString() {
- return '(' + this.x + ', ' + this.y + ')';
- }
- }
- var p = new Point(1, 2);
Class 之间可以通过 extends 关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
- class ColorPoint extends Point {
- constructor(x, y, color) {
- super(x, y); // 调用父类的constructor(x, y)
- this.color = color;
- }
- toString() {
- return this.color + ' ' + super.toString(); // 调用父类的toString()
- }
- }
历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来,这对开发大型的、复杂的项目形成了巨大障碍。为了适应大型模块的开发,社区制定了一些模块加载方案,比如 CMD 和 AMD。
ES6 的模块化写法:
- import {
- stat,
- exists,
- readFile
- }
- from 'fs';
上面代码的实质是从 fs 模块加载 3 个方法,其他方法不加载。这种加载称为 "编译时加载",即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。
模块功能主要由两个命令构成:
- // 写法一
- export
- var m = 1;
- //错误
- export 1;
- // 写法二
- var m = 1;
- export {
- m
- };
- //错误
- export m;
- // 写法三 重命名
- var n = 1;
- export {
- n as m
- };
在 javascript 的开发中,我们常常需要这样来输出模板:
- function sayHello(name) {
- return "hello,my name is " + name + " I am " + getAge(18);
- }
- function getAge(age) {
- return age;
- }
- sayHello("brand") //"hello,my name is brand I am 18"
我们需要使用 + 来连接字符串和变量(或者表达式)。例子比较简单,所以看上去无伤大雅,但是一旦在比较复杂的情况下,就会显得相当繁琐不方便,这一用法也让我们不厌其烦。对此,ES6 引入了
, 可以方便优雅地将 JS 的值插入到字符串中。
- 模板字符串
对于模板字符串,它:
- \
对于上面的例子,模板字符串的写法是:
- function sayHello(name) {
- return`hello,
- my name is $ {
- name
- }
- I am $ {
- getAge(18)
- }`;
- }
- function getAge(age) {
- return age;
- }
- sayHello("brand") //"hello,my name is brandI am 18"
严格模式的目标之一是允许更快地调试错误。帮助开发者调试的最佳途径是当确定的问题发生时抛出相应的错误 (throw errors when certain patterns occur),而不是悄无声息地失败或者表现出奇怪的行为 (非严格模式下经常发生)。严格模式下的代码会抛出更多的错误信息,能帮助开发者很快注意到一些必须立即解决的问题。在 ES5 中, 严格模式是可选项,但是在 ES6 中,许多特性要求必须使用严格模式,这个习惯有助于我们书写更好的 JavaScript。
来源: http://www.cnblogs.com/yzg1/p/5776171.html