ES6基础特性

    |     2020年3月25日   |   html/css/js, web前端技术   |     0 条评论   |    1107

ES6 日常最常用的一批语法,核心就几件事:let/const、箭头函数、模板字符串、解构、默认参数、展开运算符、对象字面量简写和 class。它们不替代 JavaScript 原理,只是让写法更短,也更少踩 var 的坑。

编译到 ES5 之后,变量提升等老行为仍可能出现。真正要记住的是:块级作用域、箭头函数没有自己的 this、const 锁的是绑定而不是对象内部。Ant Design 源码里大量用计算属性名拼 className,官网见 https://ant.design/index-cn。

特性 要点 注意
let / const 块级作用域,无变量提升 const 引用类型仍可改内部属性
箭头函数 没有自己的 this,不能当函数声明 call/apply/bind 改不了指向
解构 / 展开 对象按属性名,数组按顺序 剩余参数必须放最后

一、新的变量声明方式 let/const

和 var 相比,最重要的两点是:提供块级作用域,以及不再具备变量提升。先看一个会报 a is not defined 的例子:

{
    let a = 20;
}
console.log(a);  // a is not defined

这个简单例子会被编译成类似下面的形态(编译后的 ES5 仍可能出现提升,实际开发里不要依赖提升):

{
    let _a = 20;
}

console.log(a);  // a is not defined
// ES5
console.log(a);   // undefined
var a = 20;

// ES6
console.log(a); // a is not defined
let a = 20;

变量提升与 let / const 怎么选

代码编译成 ES5 之后仍然可能存在变量提升,记住即可,日常尽量避免靠提升来写逻辑。面试题才会把提升用到极端。

全面用 let/const 替换 var 时:值会被改用 let,值不会被改用 const(常量)。基础类型锁的是值本身;引用类型锁的是指向该对象的引用,引用不变就可以改对象内部。试图改 const 绑定会报错。

几个例子可以对照揣摩:

let a = null;
a = 20;
const obDev = {
    a: 20,
    b: 30
}

obDev.a = 30;

console.log(obDev); // Object {a: 30, b: 30}
const fn = function() {}
const a = obDev.a;
... ...

抓住上面这些特性,let/const 就够用了。经验上 const 的场景往往比 let 多。

二、箭头函数的使用

ES6 改变编码习惯,很大一部分来自箭头函数。先看写法差异:当函数体直接 return 时,可以省略花括号。

// es5
var fn = function(a, b) {
    return a + b;
}

// es6 箭头函数写法,当函数直接被return时,可以省略函数体的括号
const fn = (a, b) => a + b;

// es5
var foo = function() {
    var a = 20;
    var b = 30;
    return a + b;
}

// es6
const foo = () => {
   const a = 20;
   const b = 30;
   return a + b;
}

箭头函数可以替换函数表达式,但不能替换函数声明。

箭头函数中没有 this

更关键的一点:箭头函数没有自己的 this。你在里面写的 this 一定是外层的 this。正因为如此,也就谈不上用 call/apply/bind 去改指向。React 组件之间传值时,记住这点能少走很多弯路。

var person = {
    name: 'tom',
    getName: function() {
        return this.name;
    }
}

// 我们试图用ES6的写法来重构上面的对象
const person = {
    name: 'tom',
    getName: () => this.name
}

// 但是编译结果却是
var person = {
    name: 'tom',
    getName: function getName() {
        return undefined.name;
    }
};

ES6 默认严格模式,this 不会自动指向 window。箭头函数本身又没有 this,于是只能是 undefined。这种情况如果还想用 this,就不要写成箭头函数。

// 可以稍做改动
const person = {
    name: 'tom',
    getName: function() {
        return setTimeout(() => this.name, 1000);
    }
}

// 编译之后变成
var person = {
    name: 'tom',
    getName: function getName() {
        var _this = this;  // 使用了我们在es5时常用的方式保存this引用

        return setTimeout(function () {
            return _this.name;
        }, 1000);
    }
};

先记住写法和 this 的特殊性,封装 React 组件时再慢慢体会。

三、模板字符串

模板字符串是为了解决用 + 拼接字符串的不便。功能很强,日常用法却很简单:

// es6
const a = 20;
const b = 30;
const string = `${a}+${b}=${a+b}`;

// es5
var a = 20;
var b = 30;
var string = a + "+" + b + "=" + (a + b);

用反引号包住整段字符串,用 ${} 包变量或表达式。它还支持换行等能力,需要时再查资料即可。

四、解构赋值

解构是一种按结构取值的写法。先有这样一个对象:

// 首先有这么一个对象
const props = {
    className: 'tiger-button',
    loading: false,
    clicked: true,
    disabled: 'disabled'
}

只想取 loading 与 clicked 时:

// es5
var loading = props.loading;
var clicked = props.clicked;

// es6
const { loading, clicked } = props;

// 给一个默认值,当props对象中找不到loading时,loading就等于该默认值
const { loading = false, clicked } = props;

代码量明显变少,所以到处都能见到它:

// 比如
// section1 
import React, { Component } from 'react';

// section2
export { default } from './Button';

// section3
const { click, loading } = this.props;
const { isCheck } = this.state;

// more  任何获取对象属性值的场景都可以使用解析结构来减少我们的代码量

数组也有自己的解构:

// es6
const arr = [1, 2, 3];
const [a, b, c] = arr;

// es5
var arr = [1, 2, 3];
var a = arr[0];
var b = arr[1];
var c = arr[2];

数组按序号一一对应(有序);对象按属性名一一对应(无序)。从对象里取属性时,解构更常用。

五、函数默认参数

以前不能直接给函数指定默认参数,常见写法是:

function add(x, y) {
    var x = x || 20;
    var y = y || 30;
    return x + y;
}

console.log(add()); // 50

缺点是:传入 false 这类假值时仍会落到默认值,未必是本意。ES6 的默认值写法:

function add(x = 20, y = 30) {
    return x + y;
}

console.log(add());

实际开发里给参数加合适的默认值,也能让人一眼看出参数形态:

const ButtonGroupProps = {
    size: 'normal',
    className: 'xxxx-button-group',
    borderColor: '#333'
}

export default function ButtonGroup(props = ButtonGroupProps) {
    ... ...
}

六、展开运算符

ES6 用 ... 表示展开,可以把数组或对象摊开:

const arr1 = [1, 2, 3];
const arr2 = [...arr1, 10, 20, 30];

// 这样,arr2 就变成了[1, 2, 3, 10, 20, 30];

展开对象也能得到类似结果(类似 Object.assign):

const obj1 = {
  a: 1,
  b: 2, 
  c: 3
}

const obj2 = {
  ...obj1,
  d: 4,
  e: 5,
  f: 6
}

// 结果类似于 const obj2 = Object.assign({}, obj1, {d: 4})

它还常和解构一起用。React 封装组件时,不确定 props 还会进来多少字段,就用展开处理剩余数据:

// 这种方式在react中十分常用
const props = {
  size: 1,
  src: 'xxxx',
  mode: 'si'
}


const { size, ...others } = props;

console.log(others)

// 然后再利用暂开运算符传递给下一个元素,再以后封装react组件时会大量使用到这种方式,正在学习react的同学一定要搞懂这种使用方式
<button {...others} size={size} />

放在函数参数里表示不定参,而且必须放在最后,否则报错:

// 所有参数之和
const add = (a, b, ...more) => {
    return more.reduce((m, n) => m + n) + a + b
}

console.log(add(1, 23, 1, 2, 3, 4, 5)) // 39

刚开始会绕脑,场景记熟之后刻意多用几次就顺了。

七、对象字面量与 class

属性名和变量同名时可以简写:

const name = 'Jane';
const age = 20

// es6
const person = {
  name,
  age
}

// es5
var person = {
  name: name,
  age: age
};

模块对外提供接口时同样适用:

const getName = () => person.name;
const getAge = () => person.age;

// commonJS的方式
module.exports = { getName, getAge }

// ES6 modules的方式
export default { getName, getAge  }

方法也可以简写(不要用箭头函数的话,this 仍是熟悉的那套):

// es6
const person = {
  name,
  age,
  getName() { // 只要不使用箭头函数,this就还是我们熟悉的this
    return this.name
  }
}

// es5
var person = {
  name: name,
  age: age,
  getName: function getName() {
    return this.name;
  }
};

中括号可以把变量当成属性名:

const name = 'Jane';
const age = 20

const person = {
[name]: true,
[age]: true
}

Ant Design 源码里大量用这种方式拼接当前元素的 className:

let alertCls = classNames(prefixCls, {
[`${prefixCls}-${type}`]: true,
[`${prefixCls}-close`]: !this.state.closing,
[`${prefixCls}-with-description`]: !!description,
[`${prefixCls}-no-icon`]: !showIcon,
[`${prefixCls}-banner`]: !!banner,
 }, className);

Ant Design 是认可度很高的 UI 组件库,官方用 React 实现,也有 Vue 版本。官网:https://ant.design/index-cn。

class

Class 是创建对象的语法糖。熟悉 ES5 面向对象的话,除了写法不同,并没有难理解的新概念:

// ES5
// 构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 原型方法
Person.prototype.getName = function() {
  return this.name
}

// ES6
class Person {
  constructor(name, age) {  // 构造函数
    this.name = name;
    this.age = age;
  }

  getName() {  // 原型方法
    return this.name
  }
}

Babel 会把 ES6 编译成 Object.defineProperty 那一套,get/set 等可参考《JavaScript 高级程序设计》。实际使用时几种写法对应的 ES5 含义如下:

class Person {
  constructor(name, age) {  // 构造函数
    this.name = name;
    this.age = age;
  }

  getName() {   // 这种写法表示将方法添加到原型中
    return this.name
  }

  static a = 20;  // 等同于 Person.a = 20

  c = 20;   // 表示在构造函数中添加属性 在构造函数中等同于 this.c = 20

// 箭头函数的写法表示在构造函数中添加方法,在构造函数中等同于this.getAge = function() {}
  getAge = () => this.age   

}

箭头函数仍然要注意 this:指向不能被改变,React 组件传值时常利用这一点。

继承 extends

相比 ES5,ES6 继承简单很多:

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  getName() {
    return this.name
  }
}

// Student类继承Person类
class Student extends Person {
  constructor(name, age, gender, classes) {
    super(name, age);
    this.gender = gender;
    this.classes = classes;
  }

  getGender() {
    return this.gender;
  }
}

一个 extends 即可,不必再纠结构造函数继承和原型继承。子类构造函数里必须调用一次 super,作用等同于 ES5 里用 call/apply 继承构造函数:

// 构造函数中
// es6 
super(name, age);

// es5
Person.call(this);

super 也可以直接调父级原型方法(如 super.getName),实际里很少这样写。React 里大量组件用继承创建:

import React, { Component } from 'react';

class App extends Component {

  defaultProps = {}
  state = {}
  componentWillMount() {}
  componentDidMount() {}

  btnClick = e => {}

  render() {}
}

分清哪些属性方法放在构造函数、哪些放在原型上,自己写 React 组件会清晰很多。难点不在这些语法糖,而在 ES5 原理,重点还是要回到基础上。

一句话总结:用 let/const 取代 var,用解构和展开少写取值代码;箭头函数没有自己的 this,class 只是面向对象的语法糖。

转载请注明来源:ES6基础特性
本文链接地址:https://ai.zhousir.top/?p=2499
回复 取消