前言:这篇文章主要是对于ES6特性的学习和总结,作为一个前端初级开发者,刚开始我是跟着菜鸟教程中自学的,只是学了那个基础的 JavaScript。这次花了两天时间了解了下 ES6的特性,收获还是挺多的。最起码之前看前端同事的代码所遇到的一些问题,在这里都能找到答案。
ES6特性 | 描述 | 备注 |
---|---|---|
let | 用于声明变量,作用域相比于 var 更严格 | |
const | 用于声明常量 | |
Symbol | 新的原始数据类型,创建的变量都是独一无二的 | |
Map | 容器类,存储键值对,FIFO | |
Set | 容器类,存储的值或引用都是唯一的 | |
Proxy | 用于对目标对象的读取、函数调用等操作进行拦截,实现观察者模式 | 除了常见的set、get方法,还包括原型操作、遍历、方法调用等操作 |
Reflect | 和Object类似,用于获取目标对象的默认行为,将Object操作都变成函数行为 | Proxy提供的方法一一对应,可以和Proxy结合使用,在Proxy修改对象行为的时候,可以先用Reflect先获取对象的默认行为 |
字符串 | 增加了判断子串包含的能力,并提供了模板字符串,内部可嵌入变量或者调用方法 | |
数值 | 主要是针对 Math 对象新增了很多方法,可以在用的时候再去搜索 | |
对象 | 提供了构建对象时属性和方法的简写,以及支持拓展运算符... | |
数组 | 数组提供了一些新的方法,感觉比较有用的是判断是否包含了某个元素 | |
函数 | 支持默认参数和不定参数,更重要的是提供了箭头函数,可以在回调函数中很好的保持 this 指向 | |
类 Class | Class 本质上是 function,更加面向对象了 | |
模块 | JavaScript 模块化就很方便了,一个文件就是一个模块 | |
Promise | 异步编程的一种解决方案 |
1.简介
1.1 ECMAScript和JavaScript的关系
ECMAScript:是一种标准化的脚本语言规范,定义了 JavaScript 的核心语法和行为。这个规范规定了 JavaScript 应该如何工作,应该包含哪些特性,但是它不是一种编程语言。
JavaScript:则是一种根据 ECMAScript 规范开发的编程语言,是 ECMAScript 的具体实现。
ECMAScript 是 JavaScript 的基石,它提供了 JavaScript 的语法基础和行为准则。而 JavaScript 则是在这个基础上进行扩展和实现。由于 ECMAScript 的版本不断更新,每次更新都会引入新的特性和语法,因此 JavaScript 也会随之发展和完善,以适应新的编程需求和技术趋势。
简单来说,ECMAScript 是 JavaScript 的语法规范,而 JavaScript 则是 ECMAScript 的具体实现。
1.2 ECMAScript的历史
- 1997 年 ESMAScript 1.0 诞生;
- 1998 年 6 月 ECMAAScript 2.0 诞生,包含一些小的更改,用于同步独立的 ISO 国际标准;
- 1999 年 12 月 ECMAScript3.0 诞生,它是一个巨大的成功,奠定了 JS 的基本语法;
- 2000 年 ECMAScript4.0 是 ESMAScript6 的前身,但这个版本改动过于激进,被放弃了;
- 2009 年 12 月,ECMAScript5.0 正式发布,在3.0 的基础上进行了许多改进和扩展,进一步提高了 JavaScript 的可用性和安全性;
- 2015 年,ECMAScript 6.0 发布,引入了很多新的语言特性和语法糖;
- ECMAScript 7(2016年)、ECMAScript 8(2017年)等版本..
因为作者懒,所以下面都会用 ES JS 分别代替ECMAScript和JavaScript。
既然 ES 有这么多版本,为什么在前端同事的口中听到最多的是 ES6 呢?
这主要是因为 ES6 (ES2015)引入了大量新的特性和语法,这些改动对 JS 开发者产生了深远的影响,至今仍然是日常开发中广泛使用的,因此,前端开发者谈论的最多的是 ES6,后续的版本虽然也带来了一些改动,但是改动通常较小,所以没有像 ES6 那样引起广泛的关注。
2.ES6特性
2.1 let 和 const
ES6 新增了两个重要的关键字 let 和 const 用来声明变量和常量。
2.1.1 let
1.声明的变量只在代码块有效
相比于之前的 var 关键字声明的变量全局有效,let 声明的变量只在所在的代码块有效,这会更符合当前开发语言的习惯。
2. 不存在变量提升
let 不存在变量提升,var 会变量提升:
console.log(b); var b = 10;
VM203:1 undefined
console.log(a); let a = 10;
VM227:1 Uncaught ReferenceError: a is not defined
at <anonymous>:1:13
变量 b 用 var 声明存在变量提升,所以当脚本运行的时候,b 已经存在了,只是没有赋值,所以会输出 undefined;
变量 a 用 let 声明不存在变量提升,在声明变量 a 之前,a 不存在,所以会报错;
3.不能重复声明
let 只能声明一次,var 可以声明多次:
let a = 1;
let a = 2;
var b = 3;
var b = 4;
VM231:2 Uncaught SyntaxError: Identifier 'a' has already been declared
2.1.2 const
const 就很好理解了,就是声明一个常量。
不过需要注意的是,const 只是保证变量指向的内存地址所保存的数据不允许变动。
- 对于简单类型,值就保存在变量指向的那个内存地址,因此 const 声明的简单类型等同于常量;
- 对于引用类型,变量指向的内存地址其实是保存了一个指向实际数据的指针,所以 const 只能保证指针是固定的。至于指向的数据结构变不变就无法控制了;
2.2 解构赋值
解构复制是对赋值运算符的扩展,针对于数组或者对象进行模式匹配,然后对其中的变量进行赋值。方便了复杂对象中数据字段获取。在解构中,有两部分参与:
解构的目标 和 解构的源
2.2.1 数组模型结构
// 基本
let [a,b,c] = [1,2,3] // a=1 b=2 c=3
// 嵌套
let [a,[[b], c]] = [1,[[2], 3]]; // a=1 b=2 c=3
// 可忽略
let [a, , b] = [1, 2, 3] // a=1 c=3
// 不完全解构
let [a = 1, b] = [] // a = 1, b = undefined
// 剩余运算符
let [a, ...b] = [1,2,3] // a = 1, b = [2,3]
2.2.2 对象模型的解构
// 基本使用
let { foo, bar } = { foo: 'aaa', bar: 'bbb' }; // foo = 'aaa' // bar = 'bbb'
let { baz : foo } = { baz : 'ddd' }; // foo = 'ddd'
// 解构默认值
let {a = 10, b = 5} = {a: 3}; // a = 3; b = 5;
let {a: aa = 10, b: bb = 5} = {a: 3}; // aa = 3; bb = 5;
2.3 Symbol
Symbol 是一种原始数据类型,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。
2.3.1 基本用法
Symbol 是原始数据类型,所以不能使用 new 命令。Symbol()函数接收一个字符串参数,主要是用于描述 Symbol 变量,打印时容易区分:
let sy = Symbol("test")
console.log(sy); // Symbol(test)
typeof(sy); // "symbol"
// 即使字符串参数相等,这两个变量也不相等
let sy1 = Symbol("test")
sy === sy1 // false
2.3.2 作为属性名
由于每一个 Symbol 的值都不相等,所以 Symbol 作为对象的属性名,可以保证属性不重名。
let symbol = Symbol("key1")
let object = {};
object[symbol] = 'value1'
console.log(object) // {Symbol(key1): 'value1'}
需要注意,Symbol 作为对象属性名时不能用.运算符,要用方括号。
Symbol 值作为属性名时,该属性是公有属性,可以在类的外部访问。但是不会出现在 for...in/of 的循环中,也不会被 Object.keys() Object.getOwnPropertyNames()返回。可以通过Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到:
let syObject = {};
syObject[sy] = "kk";
console.log(syObject);
for (let i in syObject) {
console.log(i);
} // 无输出
Object.keys(syObject); // []
Object.getOwnPropertySymbols(syObject); // [Symbol(key1)]
Reflect.ownKeys(syObject); // [Symbol(key1)]
2.3.3 Symbol.for()
Symbol.for()用于根据给定的 key 在全局的 Symbol 注册表中搜索对应的 Symbol:
- 如果找到了匹配的 Symbol,则返回它;
- 如果没有找到,则创建一个新的与该键关联的 Symbol,并将其添加到全局注册表中;
通过 Symbol.for() 和 Symbol()创建的变量是不同的,即使使用相同的 key 值,因为 Symbol()始终会创建一个新的、唯一的 Symbol:
let yellow = Symbol("Yellow");
let yellow1 = Symbol.for("Yellow");
yellow === yellow1; // false
let yellow2 = Symbol.for("Yellow");
yellow1 === yellow2; // true
Symbol.keyFor()会返回一个已经在全局注册表中登记的 Symbol 类型值的 key,用来检测该字符串参数作为名称的 Symbol 值是否已经在全局注册表中登记:
let yellow1 = Symbol.for("Yellow");
Symbol.keyFor(yellow1); // "Yellow"
2.4 Map 和 Set
我称之为容器类。
2.4.1 Map
Map对象用来保存键值对,和Object功能相同,但有所区别:
- Map的键可以是任意值,Object的键只能是字符串或者Symbol
- Map中的键值是有序的(FIFO),但Object则不是
- Map的键值对个数可以从size属性获取,Object的键值对个数只能手动计算
- Object有自己的原型,原型链上的键名有可能和自己在对象上添加的键名重复
2.4.1.1 基础使用
Map对象基础的语法就是set和get,一个存值,一个取值:
let map = new Map()
map.set("name", "zhangsan") // Map(1) {'name' => 'zhangsan'}
map.get("name") // 'zhangsan'
2.4.1.2 遍历Map
for...of遍历Map:
// 获取key value
let map = new Map()
map.set("name", "zhangsan")
map.set("age", 18)
for (var [key, value] of map) {
console.log(key + "=" + value)
}
// name=zhangsan
// age=18
// 获取key
for (var key of map.keys()) {
console.log(key);
}
// name
// age
// 获取value
for (var value of map.values()) {
console.log(value);
}
// zhangsan
// 18
forEach遍历:
map.forEach(function(value, key) {
console.log(key + " = " + value);
}, map)
// name = zhangsan
// age = 18
2.4.1.3 Map对象操作
Map对象的合并:
var first = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]);
var second = new Map([[1, 'uno'], [2, 'dos']]);
// 合并两个 Map 对象时,如果有重复的键值,则后面的会覆盖前面的,对应值即 uno,dos, three
var merged = new Map([...first, ...second]);
2.4.2 Set
Set对象允许存储任何类型的唯一值,无论是原始值或者是对象引用。
2.4.2.1 基础使用
let set = new Set()
set.add(1)
set.add(2)
set.add(3) // Set(3) {1, 2, 3}
set.add(1) // Set(3) {1, 2, 3}
可以看到,当第二次添加1后,set中仍然只有一个1。不过需要注意几个特殊情况:
- +0和-0在存储判断唯一性的时候是恒等的,不重复;
- undefined和undefined恒等,不重复;
- NaN和NaN不重复,但是在Set中只能存一个,不重复;
2.4.2.2 其它使用
类型转换:
// Array 转 Set
let set = new Set(["1","2","3"])
// Set 转 Array
let array = [...set]
// String 转 Set
let set = new Set("hello") // Set(4) {h,e,l,o}
需要注意,Set中的toString方法不能将Set转成String
对象的作用:
// 数组去重
var set = new Set([1, 2, 3, 4, 4]);
[...set]; // [1, 2, 3, 4]
// 并集
var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var union = new Set([...a, ...b]); // {1, 2, 3, 4}
// 交集
var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var intersect = new Set([...a].filter(x => b.has(x))); // {2, 3}
// 差集
var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var difference = new Set([...a].filter(x => !b.has(x))); // {1}
2.5 Proxy
Proxy可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。顾名思义,它是个代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些额外操作。
2.5.1 基本用法
一个Proxy对象由两部分组成:
- target:目标对象
- handler:用于声明代理target的指定行为
let target = { name: 'Tom', age: 24 }
let handler = {
get: function(target, key) {
console.log('getting '+key);
return target[key]; // 不是target.key
},
set: function(target, key, value) {
console.log('setting '+key);
target[key] = value;
}
}
let proxy = new Proxy(target, handler)
proxy.name // 实际执行 handler.get
proxy.age = 25 // 实际执行 handler.set
// getting name
// setting age
// 25
target可以为空对象:
let target = { }
let proxy = new Proxy(target, handler)
proxy.name // 调用get方法,此时目标对象为空,没有name属性
proxy.name = "Tom" // 调用set方法,向目标对象中添加了name属性
target.name = "Tom" // 通过构造函数新建Proxy实例时其实是对目标对象进行了浅拷贝,因此目标对象与代理对象会互相影响
handler对象也可以为空,相当于不设置拦截操作,直接访问目标对象:
let target = {}
let proxy = new Proxy(target, {})
proxy.name = "Tom"
target.name // "Tom"
2.5.2 实例方法
Proxy对象提供了以下方法,用于拦截对target对象的操作:
- get(target, propKey, receiver)
- set(target, propKey, value, receiver)
- apply(target, ctx, args)
- has(target, propKey)
- construct(target, args)
- has(target, propKey)
- construct(target, args)
- deleteProperty(target, propKey)
- defineProperty(target, propKey, propDesc)
- getOwnPropertyDescriptor(target, propKey)
- getPrototypeOf(target)
- isExtensible(target)
- ownKeys(target)
- preventExtensions(target)
- setPrototypeOf
2.5.2.1 get
get(target, propKey, receiver)
用于target对象上propKey的读取操作:
let target = { name: "Tom", age: 18}
let proxy = new Proxy(target, {
get(target, propKey, receiver) {
console.log("getting " + propKey)
return target[propKey]
}
})
proxy.name
// getting name
// Tom
get()方法可以继承:
let proxy = new Proxy(target, {
get(target, propKey, receiver) {
// 通常私有属性前会有下划线_,这里可以实现对私有属性的读取保护
if(propKey[0] === "_") {
throw new Erro(`Invalid attempt to get private "${propKey}"`);
}
console.log("getting " + propKey)
return target[propKey]
}
})
// Object.create()用于创建一个新对象obj,新对象obj的原型为传入的proxy对象,这样obj可以集成proxy的get方法。
let obj = Object.create(proxy)
obj.name
// getting name
2.5.2.2 set
set(target, propKey, value, receiver)
用于拦截target对象上的propKey的赋值操作,如果目标对象自身的某个属性,不可写也不可配置,那么set方法将不起作用:
let validator = {
set: function(target, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
// 对于满足条件的 age 属性以及其他属性,直接保存
target[prop] = value;
}
};
let proxy= new Proxy({}, validator)
proxy.age = 100;
proxy.age // 100
proxy.age = 'oppps' // 报错
proxy.age = 300 // 报错
第四个参数receiver表示原始操作行为所在的对象,一般是Proxy实例本身:
const handler = {
set: function(target, prop, value, receiver) {
target[prop] = receiver;
}
};
const proxy = new Proxy({}, handler);
proxy.name = 'Tom';
proxy.name === proxy // true
const exam = {}
// Object.setPrototypeOf方法会设置exam的原型为proxy
Object.setPrototypeOf(exam, proxy)
exam.name = "Tom"
exam.name === exam // true
2.5.2.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)
// handle apply
// 1
2.5.2.4 has(target, propKey)
用于拦截hasProperty操作,在判断 target 对象是否存在 propKey 属性时,会被这个方法拦截。此方法不判断一个属性是对象自身的属性,还是继承的属性:
let handler = {
has: function(target, propKey){
console.log("handle has");
return propKey in target;
}
}
let exam = {name: "Tom"}
let proxy = new Proxy(exam, handler)
'name' in proxy
// handle has
// true
这个方法不会拦截for...in循环。
2.5.3.4 construct(target, args)
用于拦截new命令,返回值必须为对象:
let handler = {
construct: function (target, args, newTarget) {
console.log('handle construct')
return Reflect.construct(target, args, newTarget)
}
}
class Exam {
constructor (name) {
this.name = name
}
}
let ExamProxy = new Proxy(Exam, handler)
let proxyObj = new ExamProxy('Tom')
console.log(proxyObj)
// handle construct
// exam {name: "Tom"}
2.5.3.5 其它拦截操作
拦截delete操作:
deleteProperty(target, propKey)
拦截Object.defineProperty方法:
let handler = {
defineProperty: function(target, propKey, propDesc){
console.log("handle defineProperty");
return true;
}
}
let target = {}
let proxy = new Proxy(target, handler)
proxy.name = "Tom"
// handle defineProperty
target
// {name: "Tom"}
// defineProperty 返回值为false,添加属性操作无效
let handler1 = {
defineProperty: function(target, propKey, propDesc){
console.log("handle defineProperty");
return false;
}
}
let target1 = {}
let proxy1 = new Proxy(target1, handler1)
proxy1.name = "Jerry"
target1
// {}
erty操作,用于拦截 Object.getOwnPropertyDescriptor() 返回值为属性描述对象或者 undefined 。
let handler = {
getOwnPropertyDescriptor: function(target, propKey){
return Object.getOwnPropertyDescriptor(target, propKey);
}
}
let target = {name: "Tom"}
let proxy = new Proxy(target, handler)
Object.getOwnPropertyDescriptor(proxy, 'name')
// {value: "Tom", writable: true, enumerable: true, configurable:
// true}
ptor属性:
getPrototypeOf(target)
主要用于拦截获取对象原型的操作,包括以下操作:
- Object.prototype._proto_
- Object.prototype.isPrototypeOf()
- Object.getPrototypeOf()
- Reflect.getPrototypeOf()
- instanceof
// 示例:
let exam = {}
let proxy = new Proxy({},{
getPrototypeOf: function(target){
return exam;
}
})
Object.getPrototypeOf(proxy) // {}
拦截Object.isExtensible操作:
let proxy = new Proxy({},{
isExtensible:function(target){
return true;
}
})
// Object.isExtensible() 用于检测指定对象是否可扩展(是否可以添加新的属性)
Object.isExtensible(proxy) // true
拦截自身属性的读取操作:
// 比如:
- Object.getOwnPropertyNames()
- Object.getOwnPropertySymbols()
- Object.keys()
- or...in
let proxy = new Proxy( {
name: "Tom",
age: 24
}, {
ownKeys(target) {
return ['name'];
}
});
Object.keys(proxy)
// [ 'name' ] 返回结果中,三类属性会被过滤:
// - 目标对象上没有的属性
// - 属性名为 Symbol 值的属性
// - 不可遍历的属性
// 针对于特殊属性
let target = {
name: "Tom",
[Symbol.for('age')]: 24,
};
// 添加不可遍历属性 'gender'
Object.defineProperty(target, 'gender', {
enumerable: false,
configurable: true,
writable: true,
value: 'male'
});
let handler = {
ownKeys(target) {
return ['name', 'parent', Symbol.for('age'), 'gender'];
}
};
let proxy = new Proxy(target, handler);
Object.keys(proxy)
// ['name']
拦截 Object.preventExtensions 操作:
// 只有目标对象不可扩展时(即 Object.isExtensible(proxy) 为 false ),
// proxy.preventExtensions 才能返回 true ,否则会报错
// Object.preventExtensions用于禁止对象扩展(比如添加新的属性)
var proxy = new Proxy({}, {
preventExtensions: function(target) {
return true;
}
});
// 由于 proxy.preventExtensions 返回 true,此处也会返回 true,因此会报错
Object.preventExtensions(proxy) 被// TypeError: 'preventExtensions' on proxy: trap returned truish but // the proxy target is extensible
// 解决方案
var proxy = new Proxy({}, {
preventExtensions: function(target) {
// 返回前先调用 Object.preventExtensions
Object.preventExtensions(target);
return true;
}
});
Object.preventExtensions(proxy)
// Proxy {}
拦截 Object.setPrototypeOf 方法:
// 如果目标对象不可扩展,setPrototypeOf方法不得改变目标对象的原型:
let proto = {}
let proxy = new Proxy(function () {}, {
setPrototypeOf: function(target, proto) {
console.log("setPrototypeOf");
return true;
}
}
);
Object.setPrototypeOf(proxy, proto);
// setPrototypeOf
返回一个可取消的Proxy实例:
Proxy.revocable()
let {proxy, revoke} = Proxy.revocable({}, {});
proxy.name = "Tom";
revoke();
proxy.name
// TypeError: Cannot perform 'get' on a proxy that has been revoked
2.6 Reflect
Reflect对象提供了一系列方法,允许开发者通过调用这些方法访问一些JS底层功能。使用Reflect可以实现诸如属性的赋值与取值、调用普通函数、调用构造函数、判断属性是否存在于对象中等功能。
主要功能包括:
- 将Object的一些明显属于语言内部的方法放到Reflect对象上,比如Object.defineProperty
- 修改某些Object方法的返回结果
- 让Object操作都变成函数行为,比如将对象的操作符操作封装为函数
简单来说,就是将Object的方法移植到了Reflect对象上(当前某些方法会同时存在于Object和Reflect对象上)。
另外,Reflect对象的设计也考虑到了和proxy对象的对应关系,Proxy对象主要用于修改某些操作的默认行为。而Reflect对象上可以找到与proxy对象方法一一对应的方法,不管Proxy怎么修改默认行为,都可以在Reflect上获取默认行为。
2.6.1 静态方法
查找并返回 target 对象的属性:
Reflect.get(target, name, receiver)
let exam = {
name: "Tom",
age: 24,
get info(){
return this.name + this.age;
}
}
Reflect.get(exam, 'name'); // "Tom"
// 当 target 对象中存在 name 属性的 getter 方法, getter 方法的 this 会绑定 // receiver
let receiver = {
name: "Jerry",
age: 20
}
Reflect.get(exam, 'info', receiver); // Jerry20
// 当 name 为不存在于 target 对象的属性时,返回 undefined
Reflect.get(exam, 'birth'); // undefined
// 当 target 不是对象时,会报错
Reflect.get(1, 'name'); // TypeError
给对象的属性赋值:
// 将target的name属性设置为value,返回值为boolean,true表示修改成功,false表示修改失败。
Reflect.set(target, name, value, receiver)
let exam = {
name: "Tom",
age: 24,
set info(value){
return this.age = value;
}
}
exam.age; // 24
Reflect.set(exam, 'age', 25); // true
exam.age; // 25
// value 为空时会将 name 属性清除
Reflect.set(exam, 'age', ); // true
exam.age; // undefined
// 当 target 对象中存在 name 属性 setter 方法时,setter 方法中的 this 会绑定 // receiver , 所以修改的实际上是 receiver 的属性,
let receiver = {
age: 18
}
Reflect.set(exam, 'info', 1, receiver); // true
receiver.age; // 1
let receiver1 = {
name: 'oppps'
}
Reflect.set(exam, 'info', 1, receiver1);
receiver1.age; // 1
判断对象中是否存在某个属性:
// 它是name in obj指令的函数化,用于查找name属性在obj对象中是否存在
Reflect.has(obj, name)
let exam = {
name: "Tom",
age: 24
}
Reflect.has(exam, 'name'); // true
删除对象的某个属性:
// 是delete obj[property]的函数化,用于删除obj对象的property属性
Reflect.deleteProperty(obj, property)
let exam = {
name: "Tom",
age: 24
}
Reflect.deleteProperty(exam , 'name'); // true
exam // {age: 24}
// property 不存在时,也会返回 true
Reflect.deleteProperty(exam , 'name'); // true
创建对象:
// 等同于 new target(...args)
Reflect.construct(obj, args)
function exam(name){
this.name = name;
}
Reflect.construct(exam, ['Tom']); // exam {name: "Tom"}
读取对象的proto属性:
// 在obj不是对象时,会报错
Reflect.getPrototypeOf(obj)
class Exam{}
let obj = new Exam()
Reflect.getPrototypeOf(obj) === Exam.prototype // true
设置对象的prototype:
Reflect.setPrototypeOf(obj, newProto)
let obj = {}
Reflect.setPrototypeOf(obj, Array.prototype); // true
apply.call:
等同于 Function.prototype.apply.call(func, thisArg, args) 。
- func 表示目标函数;
- thisArg 表示目标函数绑定的 this 对象;
- args 表示目标函数调用时传入的参数列表,可以是数组或类似数组的对象。若目标函数无法调用,会抛出 TypeError 。
Reflect.apply(Math.max, Math, [1, 3, 5, 3, 1]); // 5
为目标对象定义属性:
let myDate= {}
Reflect.defineProperty(MyDate, 'now', {
value: () => Date.now()
}); // true
const student = {};
Reflect.defineProperty(student, "name", {value: "Mike"}); // true
student.name; // "Mike"
得到target对象的propertyKey属性的描述对象:
Reflect.getOwnPropertyDescriptor(target, propertyKey)
var exam = {}
Reflect.defineProperty(exam, 'name', {
value: true,
enumerable: false,
})
Reflect.getOwnPropertyDescriptor(exam, 'name')
// { configurable: false, enumerable: false, value: true, writable:
// false}
// propertyKey 属性在 target 对象中不存在时,返回 undefined
Reflect.getOwnPropertyDescriptor(exam, 'age') // undefined
判断对象是否可以扩展:
Reflect.isExtensible(target)
让target对象不可扩展:
let exam = {}
Reflect.preventExtensions(exam) // true
返回target对象的所有属性:
等同于 Object.getOwnPropertyNames 与Object.getOwnPropertySymbols 之和
var exam = {
name: 1,
[Symbol.for('age')]: 4
}
Reflect.ownKeys(exam) // ["name", Symbol(age)]
2.6.2 组合使用
Reflect对象的方法和Proxy对象的方法是一一对应的。所以Proxy对象的方法可以通过调用Reflect对象的方法获取默认行为,然后进行额外操作:
let exam = {
name: "Tom",
age: 24
}
let handler = {
get: function(target, key){
console.log("getting "+key);
return Reflect.get(target,key);
},
set: function(target, key, value){
console.log("setting "+key+" to "+value)
Reflect.set(target, key, value);
}
}
let proxy = new Proxy(exam, handler)
proxy.name = "Jerry"
proxy.name
// setting name to Jerry
// getting name
// "Jerry"
2.6.3 实现观察者模式
// 定义 Set 集合,防止添加重复的观察者
const queuedObservers = new Set();
// 把观察者函数都放入 Set 集合中
const observe = fn => queuedObservers.add(fn);
// observable 返回原始对象的代理,拦截赋值操作
const observable = obj => new Proxy(obj, {set});
// 拦截原始对象obj的set方法,回调给各个观察者
function set(target, key, value, receiver) {
// 获取对象的赋值操作
const result = Reflect.set(target, key, value, receiver);
// 执行所有观察者
queuedObservers.forEach(observer => observer());
// 执行赋值操作
return result;
}
2.7 ES6字符串
判断是否包含字符串:
- includes():判断是否找到参数字符串。
- startsWith():判断参数字符串是否在原字符串的头部
- endsWith():判断参数字符串是否在原字符串的尾部
let string = "apple,banana,orange";
string.includes("banana"); // true
string.startsWith("apple"); // true
string.endsWith("apple"); // false
string.startsWith("banana",6) // true
字符串重复:
repeat():返回新的字符串,表示将字符串重复指定次数返回。
console.log("Hello,".repeat(2)); // "Hello,Hello,"
字符串补全:
- padStart:返回新的字符串,表示用参数字符串从头部(左侧)补全原字符串
- padEnd:返回新的字符串,表示用参数字符串从尾部(右侧)补全原字符串。
以上两个方法接受两个参数,第一个参数是指定生成的字符串的最小长度,第二个参数是用来补全的字符串。如果没有指定第二个参数,默认用空格填充:
console.log("h".padStart(5,"o")); // "ooooh"
console.log("h".padEnd(5,"o")); // "hoooo"
console.log("h".padStart(5)); // " h"
模板字符串:
模板字符串相当于加强版的字符串,用反引号 ...
包裹字符串,并且可以在字符串中嵌入变量和表达式:
// 字符串中插入变量和表达式:
let name = "Mike";
let age = 27;
let info = `My Name is ${name},I am ${age+1} years old next year.`
console.log(info);
// My Name is Mike,I am 28 years old next year.
// 字符串中调用函数:
function f(){
return "have fun!";
}
let string2= `Game start,${f()}`;
console.log(string2); // Game start,have fun!
标签模板:
如果参数是一个模板字符串,可以将函数调用写成标签模板:
alert`Hello world!`;
// 等价于
alert('Hello world!');
如果模板字符串中带有变量,会将模板字符串参数处理成多个参数:
function f(stringArr,...values){
let result = "";
for(let i=0;i<stringArr.length;i++){
result += stringArr[i];
if(values[i]){
result += values[i];
}
}
return result;
}
let name = 'Mike';
let age = 27;
f`My Name is ${name},I am ${age+1} years old next year.`;
// "My Name is Mike,I am 28 years old next year."
f`My Name is ${name},I am ${age+1} years old next year.`;
// 等价于
f(['My Name is',',I am ',' years old next year.'],'Mike',28);
2.8 数值
二进制表示法:前缀0b 或者 0B
console.log(0b11 === 3); // true
console.log(0B11 === 3); // true
八进制表示法:前缀0o 或者 0O
console.log(0o11 === 9); // true
console.log(0O11 === 9); // true
需要了解下安全整数表示在 JavaScript 中能够精确表示的整数,安全整数的范围在 2 的 -53 次方到 2 的 53 次方之间(不包括两个端点),超过这个范围的整数无法精确表示。
Number.parseInt:
// 将给定字符串转化为指定进制的整数,默认是 10 进制;
Number.parseInt('12.34'); // 12
Number.parseInt(12.34); // 12
// 指定进制
Number.parseInt('0011',2); // 3
除了这些,还对于 Math 对象做了很多扩展,增加了很多方法,可以在使用的时候再去搜索。
2.9 对象
2.9.1 关于对象的一些简写
属性的简洁表示法:ES6允许对象的属性直接写变量,这时候属性名是变量名,属性值是变量值。
const age = 12;
const name = "zhangsan";
const person = {age, name};
// 等同于
const person = {age: age, name: name}
方法名简写:
const person = {
// 方法简写
say() {
console.log("hello");
}
// 等同于
say: function() {
console.log("hello");
}
}
person.say();
属性名表达式,使用表达式作为属性名,需要用方括号包裹:
const hello = "Hello";
const obj = { [hello+"2"]:"world" };
obj //{Hello2: "world"}
2.9.2 对象的拓展运算符
拓展运算符 ... 用于取出参数对下昂所有可遍历属性然后拷贝到当前对象。
基本用法:
let person = {name: "Amy", age: 15};
let someone = { ...person };
someone; //{name: "Amy", age: 15}
合并两个对象:
let age = {age: 15};
let name = {name: "Amy"};
let person = {...age, ...name};
person; //{age: 15, name: "Amy"}
有可能会出现有相同属性的对象,后面的会覆盖前面的。
2.9.3 对象的新方法
Object.assign(target, source_1, ···):
将源对象的所有可枚举属性复制到目标对象中(浅拷贝):
let target = {a: 1};
let object2 = {b: 2};
let object3 = {c: 3};
Object.assign(target,object2,object3);
// 第一个参数是目标对象,后面的参数是源对象
target; // {a: 1, b: 2, c: 3
Object.is(value 1,value2):
用来比较两个值是否严格相等,和===类似:
Object.is("q","q"); // true
Object.is(1,1); // true
Object.is([1],[1]); // false
Object.is({q:1},{q:1}); // false
和===的区别:
//一是+0不等于-0
Object.is(+0,-0); //false
+0 === -0 //true
//二是NaN等于本身
Object.is(NaN,NaN); //true
NaN === NaN //false
2.10 数组
2.10.1 数组创建
Array.of(),将参数中所有值作为元素形成数组:
console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]
// 参数值可为不同类型
console.log(Array.of(1, '2', true)); // [1, '2', true]
// 参数为空时返回空数组
console.log(Array.of()); // []
Array.from(),将类数组对象或可迭代对象转化为数组:
Array.from(arrayLike[, mapFn[, thisArg]])
// arrayLike: 类数组对象或可迭代对象
console.log(Array.from([1, 2, 3])); // [1, 2, 3]
// mapFn: 可选,map 函数,用于对每个元素进行处理,放入数组的是处理后的元素
console.log(Array.from([1, 2, 3], (n) => n * 2)); // [2, 4, 6]
// thisArg: 可选,用于指定 map 函数执行时的 this 对象
let map = {
do: function(n) {
return n * 2;
}
}
let arrayLike = [1, 2, 3];
console.log(Array.from(arrayLike, function (n){
return this.do(n);
}, map)); // [2, 4, 6]
类数组对象:
一个类数组对象必须含有 length 属性,且元素属性名必须是数值或者可转换为数值的字符:
let arr = Array.from({
0: '1',
1: '2',
2: 3,
length: 3
});
console.log(arr); // ['1', '2', 3]
// 没有 length 属性,则返回空数组
let array = Array.from({
0: '1',
1: '2',
2: 3,
});
console.log(array); // []
// 元素属性名不为数值且无法转换为数值,返回长度为 length 元素值为 undefined 的数组
let array1 = Array.from({
a: 1,
b: 2,
length: 2
});
console.log(array1); // [undefined, undefined]
转换可迭代对象:
map:
let map = new Map();
map.set('key0', 'value0');
map.set('key1', 'value1');
console.log(Array.from(map)); // [['key0', 'value0'],['key1',
// 'value1']]
set:
let arr = [1, 2, 3];
let set = new Set(arr);
console.log(Array.from(set)); // [1, 2, 3]
string:
let str = 'abc';
console.log(Array.from(str)); // ["a", "b", "c"]
2.10.2 扩展的方法
查找:
// find:查找数组中符合条件的元素,如果有多个符合条件的元素则返回第一个:
let arr = Array.of(1, 2, 3, 4);
console.log(arr.find(item => item > 2)); // 3
// 数组空位处理为 undefined
console.log([, 1].find(n => true)); // undefined
// findIndex:查找数组中符合条件的元素索引,若有多个符合条件的元素,则返回第一个元素索引。
let arr = Array.of(1, 2, 1, 3);
// 参数1:回调函数
// 参数2(可选):指定回调函数中的 this 值
console.log(arr.findIndex(item => item == 2)); // 1
// 数组空位处理为 undefined
console.log([, 1].findIndex(n => true)); //0
填充:
// fill:将一定范围内索引的数组元素内容填充为单个指定的值
let arr = Array.of(1, 2, 3, 4);
// 参数1:用来填充的值
// 参数2:被填充的起始索引
// 参数3(可选):被填充的结束索引,默认为数组末尾
console.log(arr.fill(0,1,2)); // [1, 0, 3, 4]
判断包含:
判断数组是否包含指定值:
// 参数1:包含的指定值
[1, 2, 3].includes(1); // true
// 参数2:可选,搜索的起始索引,默认为0
[1, 2, 3].includes(1, 2); // false
// NaN 的包含判断
[1, NaN, 3].includes(NaN); // true
嵌套数组转一维数组:
flat:
console.log([1 ,[2, 3]].flat()); // [1, 2, 3]
// 指定转换的嵌套层数
console.log([1, [2, [3, [4, 5]]]].flat(2)); // [1, 2, 3, [4, 5]]
// 不管嵌套多少层
console.log([1, [2, [3, [4, 5]]]].flat(Infinity)); // [1, 2, 3, 4, 5]
// 自动跳过空位
console.log([1, [2, , 3]].flat());<p> // [1, 2, 3]
flatMap:
// 参数1:遍历函数,该遍历函数可接受3个参数:当前元素、当前元素索引、原数组
// 参数2:指定遍历函数中 this 的指向
console.log([1, 2, 3].flatMap(n => [n * 2])); // [2, 4, 6]
2.11 函数
支持默认参数:
// 需要注意:只有在未传递参数,或者参数为 undefined时,才会使用默认参数,null 值被认为是有效的值传递:
function fn(name,age=17){
console.log(name+","+age);
}
fn("Amy",18); // Amy,18
fn("Amy",""); // Amy,
fn("Amy"); // Amy,17
不定参数:
// 支持不确定数量的参数,比如...变量名,只能放在参数组的最后一个
function f(...values){
console.log(values.length);
}
f(1,2); //2
f(1,2,3,4); //4
箭头函数:
基本用法:
var f = v => v;
//等价于
var f = function(a){
return a;
}
f(1); //1
当箭头函数没有参数或者有多个参数,要用 () 括起来:
var f = (a,b) => a+b;
f(6,2); //8
当箭头函数要返回对象的时候,为了区分于代码块,要用()将对象包裹起来:
// 报错
var f = (id,name) => {id: id, name: name};
f(6,2); // SyntaxError: Unexpected token :
// 不报错
var f = (id,name) => ({id: id, name: name});
f(6,2); // {id: 6, name: 2}
需要注意箭头函数不会更改 this 指向,箭头函数中的 this 对象,是定义函数时的对象,而不是使用函数时的对象:
function fn(){
setTimeout(()=>{
// 定义时,this 绑定的是 fn 中的 this 对象
console.log(this.a);
},0)
}
var a = 20;
// fn 的 this 对象为 {a: 18}
fn.call({a: 18}); // 18
适合的场景:
在之前使用 uni 开发的时候,由于不知道箭头函数,所以经常会用 var self = this 这样的代码,为了将外部的 this 传入到回调函数中,现在可以直接用 this 了
// 回调函数
var Person = {
'age': 18,
'sayHello': function () {
setTimeout(function () {
// 这里的 this 指向 window
console.log(this.age);
});
}
};
var age = 20;
Person.sayHello(); // 20
var Person1 = {
'age': 18,
'sayHello': function () {
setTimeout(()=>{
// 这里的 this 指向 person1
console.log(this.age);
});
}
};
var age = 20;
Person1.sayHello(); // 18
所以,当我们需要维护一个 this 上下文的时候,就可以使用箭头函数。
2.12 Class 类
class 的本质是 function,可以看作是一个语法糖,让对象原型的写法更加清晰、更加面向对象。
class Example {
// 静态属性,class 本身的属性,可以通过 Example.a调用;
static b = 2;
// 实例属性,定义在示例 this 上的属性
a = 1;
// 构造方法,创建类的实例化对象时调用
constructor(a) {
this.a = a;
}
// getter不能单独出现,需要和 getter 同级出现
get a(){
console.log('getter');
return this._a;
}
set a(a){
console.log('setter');
this._a = a; // 自身递归调用
}
}
// 公共属性
Example.prototype.c = 2;
// 类的实例化
let example = new Example()
类的继承:
通过 extends 实现类的继承:
class Father {
constructor() {
}
test() {
return 0;
}
static test1() {
return 1;
}
}
class Child extends Father {
constructor(a) {
// 子类构造方法中必须调用 super(),并且要在 this 之前;
super();
this.a = a;
console.log(super.test()); // 0
}
static test3() {
return super.test1 + 2; // 3
}
}
不能继承常规对象:
var Father = {
// ...
}
class Child extends Father {
// ...
}
// Uncaught TypeError: Class extends value #<Object> is not a constructor or null
// 解决方案
Object.setPrototypeOf(Child.prototype, Father);
2.13 模块
ES6中引入了模块化,设计思想是在编译的时候就能确定模块的依赖关系,以及输入和输出的变量。分为导出 export 和导入 import 两个模块。
特点:
- 可以导出和导入各种类型的变量:函数,对象,字符串等
- 每个模块内声明的变量都是局部变量,不会污染全局作用域
- 每个模块只加载一次,会进行缓存
2.13.1 基础使用
// export test.js
let name = "tom"
let age = 20
let fn = function() {
return `name:${name},age:${age}`
}
let customClass = class CustomClass {
static a = "aaa";
}
// 建议使用大括号指定输出的一组变量,写在最底部
export {name, age, fn, customClass}
// import xxx.js
import {name, age, fn, customClass} from "./test.js";
...可以直接使用 name age fn customClass 了。
// 使用 as
import {myName as name} from "./test.js"
import命令的特点:
- 只读属性,不能修改导出的引用指向,但是可以修改变量类型为对象的属性值;
- 单例模式:多次重复执行同一 import 语句,只会执行一次;
- 导入时不能使用表达式和变量;
// 只读属性
import {a} from "./xxx.js"
a = {}; // error
import {a} from "./xxx.js"
a.foo = "hello"; // a = { foo : 'hello' }
// 单例模式:
import { a } "./xxx.js";
import { a } "./xxx.js";
// 相当于 import { a } "./xxx.js";
import { a } from "./xxx.js";
import { b } from "./xxx.js";
// 相当于 import { a, b } from "./xxx.js";
//静态执行
import { "f" + "oo" } from "methods";
// error
let module = "methods";
import { foo } from module;
// error
if (true) {
import { foo } from "method1";
} else {
import { foo } from "method2";
}
// error
export default:
- 在一个模块或者文件中,export default 只能有一个
- export default 中的 default 是对应的导出接口变量
- 通过export 方式导出,在导入时需要加 {},export default则不需要
- export default 向外暴露的成员,可以使用任意变量来接收
var a = "My name is Tom!";
export default a; // 仅有一个
export default var c = "error";
// error,default 已经是对应的导出变量,不能跟着变量声明语句
import b from "./xxx.js"; // 不需要加{}, 使用任意变量接收
3. Babel
Babel是一个用于web开发的自由开源的JavaScript编译器和转换器。它的主要作用是在当前和较旧的浏览器或环境中,将使用ECMAScript 2015+(也称为ES6+)或更高版本编写的代码转换为向后兼容的JavaScript版本。
Babel使得软件开发者能够使用他们偏好的编程语言或风格来编写源代码,然后利用Babel将其转换为所有现代浏览器都能理解的JavaScript。这极大地提高了开发者的编程效率和代码的可读性,同时也解决了浏览器兼容性问题。