前言
七月中旬,集中时间学习使用React框架的时候,接触到了很多ES6的语法。一开始看的时候,简直一脸蒙蔽,后面对看不懂的代码在google之后知道基本都是ES6的语法的使用。那么,我的学习领域肯定也得跟进扩展到ES6啦。现在,ES6也是前端工作者的基本标配了。
当然,我们可以按照阮一峰大大的教程来详细学习,http://es6.ruanyifeng.com/
但是考虑到时间成本,肯定先挑重点常用的来学习和记忆。
七月底的时候整理了ES6的几个比较常用和重要的语法,今天修整发布下,当然是配合栗子来记录的。
后面打算,对ES6的学习整理为一个系列,发布在博客记录,这是系列记录第一篇~嘻嘻。
目前ES6的支持情况:
使用gulp搭建一个ES6的开发转换环境,以作学习测试使用:
https://segmentfault.com/a/1190000004394726?utm_source=APP&utm_medium=iOS&utm_campaign=socialShare
let和const
对于var,我们通过例子来知晓var的变量影响的范围:
栗子1:
这个例子可以看到var a变量提升到全局,同名变量会被覆盖:
function aa(){
var a=1;
console.log(a); //1
if(true){
var a=2
};
console.log(a) //2
}
这是闭包中函数内的嵌套函数的使用情况下,嵌套函数内定义为var b的变量的变量提升程度的例子。可以看到,只提升到嵌套函数的顶部而已,没有再往上升一级。
function test() {
var b=1;
function a() {
var b = 2
console.log('闭包中的b:',b)
}
console.log('全局变量的b',b)
return a;
}
var result=test();
result();
配合这个例子看:
function test() {
var b=1;
function a() {
if (true) {
var b = 2
console.log(b)
}
return a;
}
console.log('b: ', b)
}
test() //b:1
let
let的用法和var类似,区别就是let所声明的变量,只在let命令所在的代码块内有效。也就是,let声明的变量不会提升,被锁在当前块。
看下面一系列的例子:
function test() {
if(true) {
console.log(a)//TDZ,俗称临时死区,用来描述变量不提升的现象
let a = 1
}
}
test() //a is not defined
function test() {
if(true) {
let a = 1
}
console.log(a)
}
test() //a is not defined
正确的使用就是,在let声明变量后,紧接着在同个作用域下使用:
function test() {
if(true) {
let a = 1
console.log(a)
}
}
test() // 1
const
const是声明常量用的。声明之后的常量,是read only,而不能被赋值的,否则会报错。
const a=233;
a=666;
console.log(a) //
而如果我们用const来声明一个对象Object ,那么这种情况下,反而可以修改对象内属性的值。记住const常量修改自身的值不可以,而修改test.a的值可以:
const test={
a:1
}
test.a=2
console.log(test)
let和const的异同点:
相同点:let和const都只在当前块的作用域内有效而已,不会变量提升。
不同点:let声明的变量是可以重复赋值的,而const声明的常量不能被赋值。
在这里延伸一道经典面试题:
正常我们使用的for循环:
for(var i = 0; i < 5; i++) {
console.log(i) //0,1,2,3,4
}
如果在其中加入回调:
for(var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i) //5, 5, 5, 5, 5
}, 0)
}
console.log(i) //5 i跳出循环体污染外部函数
将var改成let之后
for(let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i) // 0,1,2,3,4
}, 0)
}
console.log(i)//i is not defined i无法污染外部函数
应用:
当希望变量保证不被恶意修改,使用const。例如在react中,props传递的对象是不可更改的,所以使用const声明;
声明一个对象的时候,推荐使用const;
当需要修改声明的变量值时,使用let,var能用的场景都可以使用let替代。
解构赋值
函数的扩展
rest参数
rest参数就是“扩展运算符...”。
let a=[1,2,3];
let [c,...d]=a;
console.log(c) //1
console.log(d) //2,3
注意:rest参数只能作为最后一个参数,之后不能有其他参数。否则会报错:
let a=[1,2,3];
let [...d,c]=a;
console.log(d) //报错:SyntaxError:Rest element must be last element in array
函数的length属性,不包括rest参数
(function(a){}).length //1
(function(a,...b){}).length //1
一个我觉得很好的数组push的例子:
function push(array,...items){
items.forEach(function(item){
array.push(item);
console.log(item);
});
console.log(array);
}
var a=[];
push(a,1,2,3)
//1,2,3
//[1,2,3]
另一个使用res参数来取代arguments对象的例子:
const pa=(...args) =>{
console.log(args)
return args.reduce((pre,cur)=>{
return pre+cur;
},0)
};
pa.apply(this,[1,2,3,4]) //10
在这个例子中做一个知识点延伸就是reduce()方法的使用。
Array.reduce(callback,[initialValue])
就是先得是一个数组来使用这个reduce()方法,然后reduce方法会对数组中的每一个元素都调用一次callback函数。initialValue是可填项,如果填了的话,就传入给callback函数中,作为累加的初始项。
而callback函数就是比如:
function add(first,second){
return first+second;
}
学了这个reduce方法后,以后可以替代使用for循环来做累加的操作了。
所以这个例子就等同于:
const pa=function(...args){
console.log(args)
return args.reduce(function(pre,cur){
return pre+cur;
},0)
}
pa.apply(this,[1,2,3,4])
箭头函数
学习这个箭头函数,只需要谨记:
var f=v =>v;
等同于
var f=function(v){
return v;
};
如果函数没有参数或有多个参数时,那么就需要写一个圆括号()
var a=()=>5;
//等同于
var a=function(){
return 5;
}
var sum=(a,b) =>{
return a+b;
}
//等同于
var sum=function(a,b){
return a+b;
}
const s=function(){
return {
a:'hello world'
}
}
等价于
const s=()=>{
return {
a:'hello world'
}
}
就等于后面用{}括住的代码块,直接就是function()后面跟着这一块,不用用{}再括第二次。
箭头函数这块,我觉得阮一峰大大的教程里的例子就非常能让人理解了,直接看他的说明教程中的例子就行。具体摘抄来看下:
箭头函数使用起来很简洁对不对,当我们用在回调函数里的时候,就更觉得简洁了。
看这个例子:
[1,2,3].map(function(x){
return x*x;
})
如果是用箭头函数来写:
[1,2,3].map(x=>x*x)
哎呀,好简洁对不对~~~~
这个例子,我看了又可以延伸一个知识点了:map()方法的使用。
map()方法在MDN上的解释:对数组的每个元素调用定义的回调函数,并返回包含结果的数组。
这样写言简意赅,一目了然这个方法的用法。
就是把[1,2,3]这个数组中的每个元素,执行x*x,得到相应的结果,组成一个数组返回,结果就是[1,4,9]
箭头函数中this的指向
重点要说箭头函数的this指向。从此不用再使用var _this=this的用法来重新修改this的指向了。
所以有人会说箭头函数,是一种语法糖,因为使用箭头函数时,使用者就不用去确认不用情形使用下this的指向了。
具体也是看阮一峰教程的例子来理解:
函数体内的this的指向,是定义时所在的对象
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 });
// id: 42
之前学this篇的时候,我写的博客中专门说了,setTimeout()方法和 setInterval()中this的指向是全局对象window,尽管如果setTimeout()方法是在dom元素的事件下使用的,也不会指向dom元素对象本身。
所以这个例子中,按这个道理,setTimeout()中的this是指向全局对象window的,那this.id按理应该是等于全局的id变量21才对。但是使用箭头函数时,this的指向指向了foo这个函数对象。所以this.id对应的就是通过call()方法传入的id的值42,输出为id:42
看阮一峰教程里写的两个对比例子:
function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭头函数
setInterval(() => this.s1++, 1000);
// 普通函数
setInterval(function () {
this.s2++;
}, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0
一样道理, setInterval()方法是箭头函数的写法的话,就指向定义所在的Timer函数对象,那么this.s1就取this.s1=0的值。而setInterval()函数是普通写法的话,尽管是包裹在Timer函数下定义的,但this指向运行时所在的作用域(即全局对象)。但是我们看全局下没有timer.s2的定义值,所以就一次都不更新。所以,3100毫秒之后,timer.s1被更新了3次,而timer.s2一次都没更新。
再看这个DOM 事件的回调函数封装在一个对象里面的例子,对回调函数使用箭头函数的写法的好处:
正常使用ES5的写法,我们需要使用var _this=this,才能在document对象的点击事件的函数内调用doSomething这个属性。
var handler = {
id: '123456',
init: function() {
var _this=this
document.addEventListener('click',
function(event){
_this.doSomething(event.type), false);
}
},
doSomething: function(type) {
console.log('Handling ' + type + ' for ' + this.id);
}
};
现在换成箭头函数的写法,我们就可以摒弃_this的借位写法了:本来init()方法中的this.doSomething这一行的this是会指向document对象的,但使用箭头函数后是指向handler对象了。
var handler = {
id: '123456',
init: function() {
document.addEventListener('click',
event => this.doSomething(event.type), false);
},
doSomething: function(type) {
console.log('Handling ' + type + ' for ' + this.id);
}
};
class的使用:取代构造函数实例化得到对象的做法
基本使用
在JavaScript中,我们面向对象的写法,在ES5规范下,都是使用构造函数,然后实例化这个构造函数,然后来得到对象的。现在ES6引入了其他语言中的class概念来实现面向对象,来等价于我们以往习惯的构造函数的写法:
例如:下面这个构造函数:
funtion constr(){
this.a=1;
this.b=2;
}
constr.prototype.init=function(){
console.log(this.b)
};
var sum=new constr();
sum.init;
可以使用class语法,等价转化为:
class constr {
constructor() {
this.a=1;
this.b=2;
}
init() {
console.log(this.b);
} //注意有个括号
}
let sum = new constr ();
sum.m() //2
继承
Class通过extends关键字实现继承,和ES5中的原型链继承不一样,相对简洁很多。
例如:
class Point{
}
class ColorPoint extends Point{
}
子类ColorPoint 继承了父类Point的所有属性和方法,等于就是把父类Point中的所有属性方法都复制到子类ColorPoint 中
重点是要结合super()关键字使用。记住下面的话:子类必须在constructor方法中调用super方法,否则新建实例时会报错。
class Point{
}
class ColorPoint extends Point{
constructor(){
}
}
let cp=new ColorPoint (); //ReferenceError
因为子类没有自己的this对象,而是继承父类的this对象,然后对其加工的。如果不用super()方法,那么子类就得不到this对象。
再说一点,在子类的构造函数中,只有调用super之后才能使用this关键字,否则报错。
在下面这个例子中,子类的constructor方法没有调用super之前,就使用this关键字,结果报错,而放在super方法后的this.color就正确。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y);
this.color = color; // 正确
}
}
最后来说下,生成子类实例的写法,也是一样使用new的方式来生成实例,传入数值:
let cp=new ColorPoint (25,8,'green')
然后看例子可知,cp这个实例继承自子类,也继承自父类:
cp instanceof ColorPoint //true
cp instanceof Point //true
super关键字的使用
super关键字,当做函数使用和当做对象使用,两种情况下的用法完全不同。
这点待我补充。
Module
JavaScript是一直没有模块(module)体系的,在浏览器端没办法实现I/O。而前端工作者们为了适应大型复杂的前端项目所需的模块化需求,逐渐发展出前端模块化的规范:commonJS、AMD和CMD。(这一块的知识,我也专门写了一篇前端模块化规范发展的记录博客)。这些规范,无非都是使用import、require、export这一类的方法,来讲一个大程序拆分成相互依赖的小文件。
而到ES6,就终于实现了模块功能,等于JavaScript语法上就原生支持模块的实现方式了,而不必再依赖框架(例如requireJS框架)来转换过渡了。
ES6的Module有两个关键字import和export。export是将本身的属性方法给暴露出去,给其他模块使用。而一个模块想使用其他模块中的属性方法时,就使用import
具体看栗子:
在lean.js中将属性a的值传递出去:
export let a=1;
然后我们想在init.js模块中使用a的值:
import {a} from 'lean.js'; //或具体为lean.js所在的文件路径
console.log(a) //1
这其中实现的本质是,前端是无法实现I/O的,所以import和export的使用,就是需要借助webpack或gulp这样的配置环境,使用babel.js插件,将其转化为commonJS中的require,在node.js服务端运行的。
Promise
下一篇继续讲这个知识点。
Generator
下一篇继续讲这个知识点。
网友评论