最新的Javascrip迭代即将来临。截至2015年六月 es2015 ES6 /规格已获得批准,因此,你的浏览器将会支持大量的新特性和语法。
让我们从新版本中拆分一些小菜一碟的知识点.当我说小菜一碟时,这意味着那些知识不需要大量的研究就可以轻松上手。例如我不会在这里讲生成器和展开运算符,我认为(解释)这些需要一个更深入的文章。
我们会在这篇文章中讲到下面的这些新特性:
let
const
Template Strings
Classes
Arrow Functions
Promises
let
在目前的JS中,我们有函数作用域。这意味着如果变量在一个函数中通过var
关键字创建,那么它和这个函数是绑定的。在这个函数外面,它是不可被访问的。它可以在创建该变量的函数内部,以及此函数内部创建的函数内部被访问。
在一些其他语言中也存在块级作用域的理念。在{}
(或者称之为块)中的声明的任何东西,其作用域都只会和这个块绑定,所以我们可以有不依赖于函数的作用域。
像下面这样的for
循环在JS中比比皆是:
for(var i = 0; i < 10; i++) {
console.log(i);
}
console.log('After:', i); //10
你最初的想法大概会是这样的,var i=0
创建了一个i
变量,这个变量的作用域仅仅是在这个语句中。然而这并不正确,如果你在for
循环后面打印console.log(i)
,你会发现你在这里竟然可以访问这个变量!这可能不是你期待的行为。
ES6中有一个let
的新关键字,它可以让你创建一个只和创建它的语句块绑定的变量。
for(let i = 0; i < 15; i++) {
console.log(i);
}
// i undefined
console.log('After:', i);
使用let
关键字来代替var
,并尝试在for
循环后面打印console.log(i)
,你会发现现在返回了undefined
。这个规则对于任何语句块都好使,也就是{}
中的所有代码。这也包括内部的闭包。
function myFunction() {
let a = 'Hey';
return (function() {
return a;
})();
}
调用myFunction()
会返回"Hey"
.又或者是像下面这样在if
语句内部使用let
关键字声明变量。
if(true) {
let name = 'Ryan';
}
console.log(name);
在这种情况下,变量name
仅仅和if
语句的作用域绑定。
在目前的JS版本中我们不能创建不能被改变的变量。我们有这样一个约定,一个变量是在所有引用的地方应该是一个不应该改变的值。那么这应该是一个常数变量。
ES6提供给我们一个新的const
关键字,它允许我们创建一个只读的常数值。
const CONFIG = 'Configure'
CONFIG = 'New String' //Silent error
console.log(CONFIG); //Configure
在创建一个const
值后,你不能修改它。如果你试图修改常量的值,这个操作会失败且不会提供任何错误提示。const
声明的变量和let
声明的变量的一样也是块级作用域。
例如:
const API_KEY = 'h879u3iu1789123';
function apiCall() {
//Do some ajax call/whatever with API_KEY
return data;
}
这是一个相当小的例子,但也用到了我们刚刚谈到的const
关键字。
不喜欢字符串拼接?在ES6中我们现在可以使用模板字符串。模板字符串的语法非常直接,你可以直接使用 `` 字符,也就是我们所知道的反单引号,键入`Hey there`就创建了一个内容是“Hey there”的字符串。
你或许在想,“这很酷...但是 + 号部分又是怎么样的呢”。别急,我们有一个像${variable}
这样的语法可以用来替换冗长的字符串拼接语法。鉴于此,假设我们有一个变量let name = "Ryan"
,我们想把它拼进一个字符串,它看起来可能像下面这样:
var name = "Ryan";
var string = "Hi my name is " + name + " and I live in Toronto";
通过Es6模板字符串语法,我们可以使用反单引号和${}
表达式来拼接字符串和变量name
var name = "Ryan";
var string = `Hi my name is ${name} and I live in Toronto`;
当你有一个对象或者很多信息需要和字符串拼接在一起的时候,模板字符串可以让你事半功倍。
var data = {
name: "Ryan Christiani",
age: 29,
location: 'Toronto, ON'
}
var string = 'Hi my name is ' + data.name + ' and I am ' + data.age + ' and I live in ' + data.location;
console.log(string);
//Hi my name is Ryan Christiani and I am 29 and I live in Toronto, ON
使用模板字符患:
var data = {
name: "Ryan Christiani",
age: 29,
location: 'Toronto, ON'
}
var string = `Hi my name is ${data.name} and I am ${data.age} and I live in ${data.location}`;
console.log(string);
// Hi my name is Ryan Christiani and I am 29 and I live in Toronto, ON
在大多数编程语言中都有类的概念,类是用来创建面向对象结构的抽象的数据结构。
Before classes in ES6 there were a couple ways to create this structure. One way would be using a function as a constructor. 在ES6类的概念出现之前也有一些方法来创建这样的数据结构。一种途径是使用函数来作为构造器。
var Warrior = function() {
this.hp = 100;
this.str = 5;
this.attack = function(target) {
target.hp = target.hp -= this.str;
}
};
var character = new Warrior();
character.attack(enemy);
这里我们使用了一个函数和this
关键字将属性添加到Warrior
上。然而,在这个方法内部this
关键字默认指向的是window
对象,为了让它指向我们的Warrior
,在创建对象的时候我们需要用new
关键字。这样就我们实例化了一个这个对象的新副本。
如果你想使用Warrior
作为基本对象来创建更多的对象,我们需要使用prototype
属性。
var Fighter = function() {
this.weapons = ['sword','shield'];
};
Fighter.prototype = new Warrior();
var myFighter = new Fighter();
这里我们在Fighter
上用了prototype
属性来使它继承了Warrior
对象的属性。所以Fighter
可以使用Warrios
的.attack()
方法以及其他属性,它还有自己的特性,例如weapons
.
现在让我们来看下ES6中的新类型,一个已经介绍了的新关键字,class
.
class Warrior {
constructor() {
this.hp = 100;
this.str = 5;
}
attack(target) {
target.hp = target.hp -= this.str;
}
};
var character = new Warrior();
character.attack(enemy);
这个类中有一个constructor()
的方法,它会在你实例化类的时候运行。在这个方法内部你可以运行你需要的任何形式的初始化代码。如果你希望class
接受参数,他们将在constructor()
中被赋值。
class Warrior {
constructor(name) {
this.name = name;
this.hp = 100;
this.str = 5;
}
}
当你实例化这个类的时候,你需要按需传入对应的值。
var myWarrior = new Warrior('Ryan');
console.log(myWarrior.name); //"Ryan";
如果要从另一个类中继承,我们可以使用extends
关键字。
class Fighter extends Warrior {
constructor() {
this.weapons = ['sword','sheild'];
}
}
这和下面是等价的
``Fighter.prototype = new Warrior();``
你可能会想,既然我们有了class
,那有没有私有属性呢?Es6中有一个新的symbol
的数据类型,我们可以像下面那样去创建一个symbol。
``var mySymbol = Symbol();``
注意上面缺少new
关键字。这样可以创建一个供我们使用的symbol. 一个Symbol是一个唯一的标识符。鉴于此我们可以用它来在类中设置我们的私有属性。没有两个Symbol是一样的。
var privateProp = Symbol();
class Warrior {
constructor() {
this[privateProp] = ['some', 'data'];
}
}
现在这个属性可以理解为是私有的,如果你需要访问它,你可以用this[ privateprop ]
选择器。
ES6提供了一个新的函数语法,箭头函数!语法如下:
() => {
//statements
}
我们不再需要function
关键字,我们在{}
前面使用=>
来定义函数。这只对匿名函数有效,你不可以用它来创建一个带有名字的箭头函数。
如果你的函数只接受一个参数,那么你实际上可以省略参数所在的圆括号。
``x => { return x * 2 }``
对于简单的回调,这非常有用。如果你想的话,你也可以将简单语句外面的{}
去掉。
``var add = (a,b) => a + b;``
注意上面缺少return
语句,省略{}
将会创建一个隐式的return。
在这里你可以找到更多关于箭头函数的使用语法.我认为MDN可以更好地解释一些细微的差别。
我确实想复习this
的词法作用域,然而当我(真正来)处理函数中的this关键字时,我感觉this的当前上下文有时候还是挺迷惑人的。
var teacher = {
name: "Ryan",
location: "Toronto, ON",
courses: ['Front End Bootcamp', 'Intro Web Development', 'Advanced Web Development'],
print: function(){
this.courses.forEach(function(course,index) {
console.log(`${this.name} teaches ${course}`)
});
}
}
teacher.print();
//teaches Front End Bootcamp
//teaches Intro Web Development
//teaches Advanced Web Development
在上面这个例子中,this
关键字的作用域在teacher对象中,通过箭头函数我们可以改变他的上下文。值得注意的是forEach
的回调函数改变了this的值,它指向的是window
对象。使用箭头函数可以得到我们想要的结果。
var teacher = {
name: "Ryan",
location: "Toronto, ON",
courses: ['Front End Bootcamp', 'Intro Web Development', 'Advanced Web Development'],
print: function() {
this.courses.forEach((course,index) => {
console.log(`${this.name} teaches ${course}`)
});
}
}
teacher.print();
//Ryan teaches Front End Bootcamp
//Ryan teaches Intro Web Development
//Ryan teaches Advanced Web Development
现在在forEach
回调中,this
的值指向创建它的对象,也就是teacher
对象。
让我们再看一个例子,这样我们就可以更好的理解它。
class Europe {
constructor() {
this.count = 1000;
}
finalCountdown() {
var countInterval = setInterval(function() {
this.count--;
console.log(this.count);
if(this.count === 0) {
clearInterval(countInterval);
}
});
}
}
var gob = new Europe();
gob.finalCountdown();
或许你有一个Europe
的类,我们要为它添加一个自我介绍的方法或者其他的东西。这里唯一的问题是当你写setInterval(function(){...})
的时候,this
关键字指向了window(全局作用域)。因此上面的结果会打印NaN
.
然而如果我们把SetInterval
中的回调函数改成箭头函数, 那么这个箭头函数的作用域就指向了创建它的函数,也就是Europe
类。
class Europe {
constructor() {
this.count = 1000;
}
finalCountdown() {
var countInterval = setInterval(() => {
this.count--;
console.log(this.count);
if(this.count === 0) {
clearInterval(countInterval);
}
});
}
}
现在会打印1000到0!
Es6中我们有了原生的Promises!一个Promises是某些动作的最终的成功或者失败。他们通常用在Ajax请求或者动画中,Promise的概念并不新鲜,它已经存在了有一段时间。在jQuery中,我们使用$.Deferred()
对象来模拟Promise的行为。其他流行的库像Q也实现了Promises。
一个Promise有penging,fulfilled和rejected三种状态。我们会关注fulfilled和rejected这两个状态。
我们可以用new
关键字和Promise
构造器来创建一个Promise.构造器接受一个带resolve
和reject
两个参数的回调函数。
var myPromise = new Promise(function(resolve,reject) {
//...
});
这些将被用来确定我们的Promises是fulfilled(resolved) 还是rejected(reject)。resolve
和reject
实际上是我们可以用来传递一些数据的函数。
var myPromise = new Promise(function(resolve,reject) {
//do a bunch of stuff
if(something is true) {
resolve('Everything worked');
}
else {
reject('Something went wrong');
}
});
既然我们打算将我们的promise置为完成或者拒绝状态,我们就要在最终动作上做一些事情。输入then()
方法,这是我们创建的Promise
对象的一个方法,它接受两个回调函数,第一个回调函数在promise完成的时候被调用,第二个回调函数在promise拒绝的时候调用。
myPromise.then(
//resolved
function(message) {
//Will only be called if it is fulfilled
console.log(message)
},
//rejected
function(message) {
//Will only be called if it is rejected
console.log(message)
}
);
值得注意的是一旦设置了promises的状态,它就不能被改变。
假设在这样一个情况下,你需要等待多个promises的执行结果.all()
方法可以用来解决这个问题,它会一直处于等待状态直到一定数量promises的状态变为解决。例如:
var promise1 = new Promise(...);
var promise2 = new Promise(...);
Promise.all([promise1,promise2]).then(data => {
data.forEach((item) => {
console.log(item);
});
});
.all()
方法接受一个包含promises的数组,当数组中的promises全部变为解决状态时才会调用then()
方法。如果数组的一个promises被拒绝了,那么.all()
方法会拒绝数组中所有的promises.
Es6这么强大,你现在怎么可以不用?现在在项目中使用ES6最简单的方法就是用Babel和Traceur之类的工具来构建它。这些转码器可以将你写的ES6代码转制成ES5.这让我们编写符合未来语法的代码,并使它们可以运行在那些目前不支持所有新特性的浏览器上。
一个简单的设置就可以将gulp
和gulp-babel
包.结合使用
var gulp = require('gulp');
var babel = require('gulp-babel');
var concat = require('gulp-concat');
gulp.task('js', function() {
gulp.src('*.esnext.js')
.pipe(babel({
presets: ['es2015']
}))
.pipe(concat('script.js'))
.pipe(gulp.dest('.'));
});
gulp.task('default', function() {
gulp.watch('*.esnext.js',['js']);
});
注意:截至2015十月底,gulp-babel
需要插件才可以转码你的ES6代码。确保已经运行npm install --save-dev babel-preset-es2015
安装了 ES6/ECMAScript 2015插件。
想在浏览器里面玩ES6吗?那我推荐来自Rich Gilbank at Shopify的ScratchJS。这是一个可以让你在浏览器开发者工具中编写并执行Es6代码的chrome插件。
Babel和其他转码器并不能转码即将到来的所有新特性,如果你需要确定一个新特性是否支持可以看下兼容性表.
扫码关注w3ctech微信公众号
共收到0条回复