[JavaScript] 记录下我学习ES6的一些总结

前言:这篇文章主要是对于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。这极大地提高了开发者的编程效率和代码的可读性,同时也解决了浏览器兼容性问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容