首页 > 技术文章 > ES6新特性

lora404 2020-03-09 16:08 原文

本篇随笔涉及的知识点有

  1. let和const;
  2. 继承;
  3. Proxy;
  4. 模块化;
  5. 操作数组map, filter, reduce
  6. 数据结构set和map
  7. 数据类型Symbol

 

1.var,let以及const区别

不被挂载到window:在全局作用域下使用let,const声明变量,变量是不会被挂载到window上的;这一点跟var不同。

无变量提升:let,const声明的变量,不能在声明前使用,存在暂时性死区;var可以进行变量提升。

  暂时性死区:虽然变量在编译的环节中被告知在这块作用域中可以访问,但是访问是受限制的。

  函数提升:函数提升优先于变量提升,函数提升会把整个函数挪到作用域顶部,变量提升只会把声明挪到作用域顶部。

有块状作用域let 声明的变量只能在当前块级作用域中使用。

禁止重复声明var 声明的变量可以重复声明,而且不会有任何警告或者提示,覆盖了一个值;而 let 则会直接抛出一个语法错误。

const禁止重复赋值:letconst 作用基本一致,但是后者声明的变量不能再次赋值,所以在声明的时候必须赋初值。

 

2.原型继承和class继承

class继承:js中并不存在类,class是语法糖,本质上还是函数。

class 实现继承的核心在于使用 extends 表明继承自哪个父类,并且在子类构造函数中必须调用 super,因为这段代码可以看成 Parent.call(this, value)

class Parent {
  constructor(value) {
    this.val = value
  }
  getValue() {
    console.log(this.val)
  }
}
class Child extends Parent {
  constructor(value) {
    super(value)
  }
}
let child = new Child(1)
child.getValue() // 1
child instanceof Parent // true

 

 

组合继承:最常见的继承方式,核心是在子类的构造函数中通过Parent.call(this)来继承父类的属性,改变子类的原型为new Parent()来继承父类函数。

function Parent(value) {
  this.val = value
}
Parent.prototype.getValue = function() {
  console.log(this.val)
}
function Child(value) {
  Parent.call(this, value)
}
Child.prototype = new Parent()

const child = new Child(1)

child.getValue() // 1
child instanceof Parent // true

优点在于构造函数可以传参,不会与父类的属性共享;而可以复用父类的函数。缺点是继承父类函数的时候调用了父类构造函数,导致子类的原型上多了不需要的父类属性,存在内存上的浪费。

 

寄生组合继承:核心就是将父类的原型赋值给了子类,并且将构造函数设置为子类,这样既解决了无用的父类属性问题,还能正确的找到子类的构造函数。

function Parent(value) {
  this.val = value
}
Parent.prototype.getValue = function() {
  console.log(this.val)
}

function Child(value) {
  Parent.call(this, value)
}
Child.prototype = Object.create(Parent.prototype, {
  constructor: {
    value: Child,
    enumerable: false,
    writable: true,
    configurable: true
  }
})

const child = new Child(1)

child.getValue() // 1
child instanceof Parent // true

 

 

3.模块化

实现模块化的目的:代码复用;提高代码可维护性;解决命名冲突

模块化的几种方式:

1)立刻执行函数

使用立即执行函数实现模块化是常见的手段,通过函数作用域解决了命名冲突、污染全局作用域的问题。

(function(globalVariable){
   globalVariable.test = function() {}
   // ... 声明各种变量、函数都不会污染全局作用域
})(globalVariable)

 

2)AMD和CMD:目前这两种实现方式已经很少见到

异步模块定义(AMD)是Asynchronous Module Definition的缩写,是 RequireJS 在推广过程中对模块定义的规范化产出。

  它采用异步方式加载模块,模块的加载不影响它后面语句的运行。
所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:
require([module], callback);
第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。


通用模块定义(CMD)是Common Module Definition的缩写,是SeaJS 在推广过程中对模块定义的规范化产出。

  CMD 推崇依赖就近,AMD 推崇依赖前置。

// CMD 
define(function(require, exports, module) {
       var a = require('./a')
       a.doSomething() // 此处略去 100 行
       var b = require('./b') // 依赖可以就近书写
       b.doSomething() // ... 
})
// AMD 默认推荐的是 
define(['./a', './b'], function(a, b) { 
    // 依赖必须一开始就写好 
    a.doSomething() // 此处略去 100 行 
    b.doSomething() ... 
})

3)CommonJS 最早是 Node 在使用,目前也仍然广泛使用,比如在 Webpack 中你就能见到它。

“在 CommonJs 的模块化规范中,每一个文件就是一个模块,拥有自己独立的作用域、变量、以及方法等,对其他的模块都不可见。
CommonJS规范规定,每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的 exports 属性(module.exports)是对外的接口。
加载某个模块,其实是加载该模块的 module.exports 属性。require 方法用于加载模块。”

链接:http://www.imooc.com/article/285854

 

 

//moudle-a.js
moudle.exports = {
    a: 1};
    
//moudle-b.js
var ma = require('./moudle-a');
var b = ma.a + 2;module.exports ={
        b: b
};

  

4)ES模块

  • CommonJS 支持动态导入,也就是 require(${path}/xx.js),后者目前不支持,但是已有提案
  • CommonJS 是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响
  • CommonJS 在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次。但是 ES Module 采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化
  • ES Module 会编译成 require/exports 来执行的
// 引入模块 API
import XXX from './a.js'
import { XXX } from './a.js'
// 导出模块 API
export function a() {}
export default function() {}

  

4.Proxy

概述

Proxy可以用来改变对象的默认操作,比如自行定义set和get等。

Proxy中需要理解的三个属性:

  • target: an Object which the proxy virtualizes.(目标对象)
  • handler: a Placeholder Object which contains traps.(包含重写方法的对象)
  • trap: the Methods that provide property access of the target object.(重写的方法,比如get和set)

比如get方法取不到值时不返回undefined,

let handler = {
    get: function(target, fieldName) {        

        if(fieldName === 'fullName' ) {
            return `${target.firstName} ${target.lastName}`;
        }

        return fieldName in target ?
            target[fieldName] :
                `No such property as, '${fieldName}'!`

    }
};


let p = new Proxy(employee, handler);

console.group('proxy');
console.log(p.firstName);
console.log(p.lastName);
console.log(p.org);
console.log(p.fullName);
console.groupEnd()

proxy
  Tapas
  Adhikary
  No such property as, 'org'!
  Tapas Adhikary

 

 

以及在set方法上加验证及数据绑定等等

const validator = {
    set: function(obj, prop, value) {
        if (prop === 'age') {
            if(!Number.isInteger(value)) {
                throw new TypeError('Age is always an Integer, Please Correct it!');
            }
            if(value < 0) {
                throw new TypeError('This is insane, a negative age?');
            }
        }
    }
};

let pr = new Proxy(employee, validator);

pr.age = "test";
Uncaught TypeError: Age is always an Integer, Please Correct it!
    at Object.set (<anonymous>:5:23)
    at <anonymous>:1:7
set @ VM2381:5
(anonymous) @ VM2434:1


pr.age = -1;
Uncaught TypeError: This is insane, a negative age?
    at Object.set (<anonymous>:8:23)
    at <anonymous>:1:7
set @ VM2381:8
(anonymous) @ VM2531:1

 

 

实例方法

1、get(target, propKey, receiver) 用于拦截某个属性的读取(read)操作,就是在读取目标对象的属性之前,搞点事情。
参数:
target:目标对象
property:属性名
receiver:操作行为所针对的对象,一般指proxy实例本身

复制代码
let exam={};
let proxy = new Proxy(exam, { get(target, propKey, receiver) { console.log('Getting ' + propKey); return target[propKey]; } })
console.log(proxy.name);//undefined
复制代码

2、set(target, propKey, value, receiver) 用来拦截目标对象的赋值(write)操作
参数:
target:目标对象
propertyName:属性名
propertyValue:属性值
receiver:Proxy实例本身

复制代码
let validator = {
    set: function(obj, prop, value) {
        if (prop === 'age') {
            if (!Number.isInteger(value)) {
                throw new TypeError('The age is not an integer');
                return false;    
            }
            if (value > 200) {
                throw new RangeError('The age seems invalid');
                return false;
            }
        }
        // 对于满足条件的 age 属性以及其他属性,直接保存
        obj[prop] = value;
        return true;
    }
};
let proxy= new Proxy({}, validator)
proxy.age = 100;
proxy.age           // 100
proxy.age = 'oppps' // 报错
proxy.age = 300     // 报错   
复制代码

3、apply(target, ctx, args)  用于拦截函数的调用、call 和 reply 操作。

  target 表示目标对象,

  ctx 表示目标对象上下文,

  args 表示目标对象的参数数组。

复制代码
function sub(a, b){
    return a - b;
}
let handler = {
    apply: function(target, ctx, args){
        console.log('handle apply');
        return Reflect.apply(...arguments);
    }
}
let proxy = new Proxy(sub, handler)
proxy(2, 1) 
// 1
复制代码

4、has(target,propkey)   拦截 propKey in proxy 的操作,返回一个布尔值。即在判断 target 对象是否存在 propKey 属性时,会被这个方法拦截。

let proxy = new Proxy(exam, handler)
'name' in proxy  

5、deleteProperty(target, propKey) 拦截delete proxy[propKey]的操作,返回一个布尔值。用于拦截 delete 操作。

6、construct(target, args) 用于拦截 new 命令。返回值必须为对象。

7、ownKeys(target) 用于拦截对象自身属性的读取操作。

8、getPrototypeOf(target) 拦截对象原型操作

9、isExtensible(target) 用于拦截 Object.isExtensible 操作。

 

5.操作数组map, filter, reduce

涉及面试题:map, filter, reduce 各自有什么作用?

forEach()

var arr = [1,2,3,4];

arr.forEach((item,index,arr)=>{

    console.log(item);  //结果为1,2,3,4

});

//forEach遍历数组,无返回值,不改变原数组,仅仅只是遍历,常用于注册组件、指令等等。

map 作用是生成一个新数组,遍历原数组,将每个元素拿出来做一些变换然后放入到新的数组中。

[1, 2, 3].map(v => v + 1) // -> [2, 3, 4]

另外 map 的回调函数接受三个参数,分别是当前索引元素,索引,原数组

['1','2','3'].map(parseInt)
  • 第一轮遍历 parseInt('1', 0) -> 1
  • 第二轮遍历 parseInt('2', 1) -> NaN
  • 第三轮遍历 parseInt('3', 2) -> NaN

filter 的作用也是生成一个新数组,在遍历数组的时候将返回值为 true 的元素放入新数组,我们可以利用这个函数删除一些不需要的元素

let array = [1, 2, 4, 6]
let newArray = array.filter(item => item !== 6)
console.log(newArray) // [1, 2, 4]

map 一样,filter 的回调函数也接受三个参数,用处也相同。

最后我们来讲解 reduce 这块的内容,同时也是最难理解的一块内容。reduce 可以将数组中的元素通过回调函数最终转换为一个值。

如果我们想实现一个功能将函数里的元素全部相加得到一个值,可能会这样写代码

const arr = [1, 2, 3]
let total = 0
for (let i = 0; i < arr.length; i++) {
  total += arr[i]
}
console.log(total) //6 

但是如果我们使用 reduce 的话就可以将遍历部分的代码优化为一行代码

const arr = [1, 2, 3]
const sum = arr.reduce((acc, current) => acc + current, 0)
console.log(sum)

对于 reduce 来说,它接受两个参数,分别是回调函数和初始值,接下来我们来分解上述代码中 reduce 的过程

  • 首先初始值为 0,该值会在执行第一次回调函数时作为第一个参数传入
  • 回调函数接受四个参数,分别为累计值、当前元素、当前索引、原数组,后三者想必大家都可以明白作用,这里着重分析第一个参数
  • 在一次执行回调函数时,当前值和初始值相加得出结果 1,该结果会在第二次执行回调函数时当做第一个参数传入
  • 所以在第二次执行回调函数时,相加的值就分别是 12,以此类推,循环结束后得到结果 6

some()

var arr = [1,2,3,4];

arr.some((item,index,arr)=>{

   return item > 2; //返回true

});

//遍历数组每一项,有一项返回true,则停止遍历,结果返回true。不改变原数组。

every()

var arr = [1,2,3,4];

arr.every((item,index,arr)=>{

   return item >1; //返回false

});

//遍历数组每一项,每一项返回true,最终结果为true.有一项返回false,停止遍历,结果返回为false。不改变原数组

 

6.set和map

ES6提供了新的数据结构Set,类似于数组,但成员的值都是唯一的。它本身是一个构造函数。

const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
  console.log(i); // 2 3 5 4
}

上面代码通过add方法向Set结构加入成员,结果表明Set结构不会添加重复的值。
Set函数可以接受一个数组(或者具有iterable接口的其他数据结构)作为参数,用来初始化。

Array.from方法可以将Set结构转为数组。

const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);

 

ES6提供了Map,类似于对象,也是键值对的集合,但键的范围不限于字符串,可以是各种类型的值。

也就是说,Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。如果你需要“键值对”的数据结构,MapObject更合适。

const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
上面代码使用Map结构的set方法,将对象o当作m的一个键,然后又使用get方法读取这个键,接着使用delete方法删除了这个键。
上面的例子展示了如何向Map添加成员。作为构造函数,Map也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。
const map = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"

 

7.Symbol

ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型。Symbol值通过Symbol函数生成。

let s = Symbol();
typeof s // "symbol"注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。也就是说,由于Symbol值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
let s1 = Symbol('foo');
let s2 = Symbol('bar');
s1 // Symbol(foo)
s2 // Symbol(bar)
s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"

 

链接:https://www.jianshu.com/p/ae331e3352f4
 

还有Promise,箭头函数在其他随笔记录。

 

推荐阅读