块级作用域
ES5没有块级作用域,只有全局作用域和函数作用域,由于这一点,变量的作用域甚广,所以一进入函数就要马上将它创建出来。这就造成了所谓的变量提升。
ES5的“变量提升”这一特性往往一不小心就会造成一下错误:
1.内层变量覆盖外层变量
var tmp = new Date(); function f() { console.log(tmp); if (false) { //执行则undefined var tmp = "hello world"; } }
2.变量泄露,成为全局变量
var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); // 5
往常我们往往是使用闭包来解决这一问题的(比如自执行函数)。现在,基于这一问题,ES6增加了块级作用域,所以不再需要自执行函数了。
let 和 const
ES6是是向后兼容的,而保持向后兼容性意味着永不改变JS代码在Web平台上的行为,所以var创建的变量其作用域依旧将会是全局作用域和函数作用域。这样以来,即使拥有了块级作用域,也无法解决ES5的“变量提升”问题。所以,这里ES6新增了俩个新关键词:let和const。
1.let
“let是更完美的var”,它有着更好的作用域规则。
2.const
const声明一个只读的常量。一旦声明,常量的值就不能改变,但const声明的对象可以有属性变化(对象冻结Object.freeze)
const a = []; a.push('Hello'); // 可执行 a = ['Dave']; // 报错
也可以使用Object.freeze将对象冻结
const foo = Object.freeze({}); // 常规模式时,下面一行不起作用; // 严格模式时,该行会报错 foo.prop = 123;//
使用let和const:
"htmlcode">
var a = 1; window.a // 1 let b = 1; window.b // undefined
this关键字
我们知道,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。我们通过foo.call({id:42})来调用它,并且为这个函数设定作用域。它真正执行要等到100毫秒后,由于this指向的是运行时所在的作用域,所以这里的this就指向了全局对象window,而不是函数foo。这里:
"htmlcode">
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; };
"htmlcode">
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。这就导致了其:
"color: #ff0000">类与继承
传统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)。所以整个的继承是通过原型链来实现的。详情可见我的这篇文章:javascript中的prototype和constructor
class
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);
"htmlcode">
class ColorPoint extends Point { constructor(x, y, color) { super(x, y); // 调用父类的constructor(x, y) this.color = color; } toString() { return this.color + ' ' + super.toString(); // 调用父类的toString() } }
"color: #ff0000">模块化
历史上,JavaScript一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来,这对开发大型的、复杂的项目形成了巨大障碍。为了适应大型模块的开发,社区制定了一些模块加载方案,比如CMD和AMD。
ES6的模块化写法:
import { stat, exists, readFile } from 'fs';
上面代码的实质是从fs模块加载3个方法,其他方法不加载。这种加载称为“编译时加载”,即ES6可以在编译时就完成模块加载,效率要比CommonJS模块的加载方式高。当然,这也导致了没法引用ES6模块本身,因为它不是对象。
模块功能主要由两个命令构成:
"htmlcode">
// 写法一 export var m = 1; //错误 export 1; // 写法二 var m = 1; export {m}; //错误 export m; // 写法三 重命名 var n = 1; export {n as m};
"color: #ff0000">字符串插值
在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 的值插入到字符串中。
模板字符串
对于模板字符串,它:
"htmlcode">
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。
以上所述是小编给大家介绍的ES6所改良的javascript“缺陷”问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!