ES6新特性
本篇随笔涉及的知识点有
- let和const;
- 继承;
- Proxy;
- 模块化;
- 操作数组map, filter, reduce
- 数据结构set和map
- 数据类型Symbol
1.var,let以及const区别
不被挂载到window:在全局作用域下使用let,const声明变量,变量是不会被挂载到window上的;这一点跟var不同。
无变量提升:let,const声明的变量,不能在声明前使用,存在暂时性死区;var可以进行变量提升。
暂时性死区:虽然变量在编译的环节中被告知在这块作用域中可以访问,但是访问是受限制的。
函数提升:函数提升优先于变量提升,函数提升会把整个函数挪到作用域顶部,变量提升只会把声明挪到作用域顶部。
有块状作用域:let 声明的变量只能在当前块级作用域中使用。
禁止重复声明:var 声明的变量可以重复声明,而且不会有任何警告或者提示,覆盖了一个值;而 let 则会直接抛出一个语法错误。
const禁止重复赋值:let 和 const 作用基本一致,但是后者声明的变量不能再次赋值,所以在声明的时候必须赋初值。
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,该结果会在第二次执行回调函数时当做第一个参数传入 - 所以在第二次执行回调函数时,相加的值就分别是
1和2,以此类推,循环结束后得到结果 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结构实现。如果你需要“键值对”的数据结构,Map比Object更合适。
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)"
还有Promise,箭头函数在其他随笔记录。

浙公网安备 33010602011771号