三十三、es6了解吗?说几个常见的新特性,set和weakSet的区别是什么

90 阅读13分钟

一、不一样的变量声明:const和let

ES6推荐使用let声明局部变量,相比之前的var(无论声明在何处,都会被视为声明在函数的最顶部) let和var声明的区别:


var x = '全局变量';

{

let x = '局部变量';

console.log(x); // 局部变量

}

console.log(x); // 全局变量

let表示声明变量,而const表示声明常量,两者都为块级作用域;const 声明的变量都会被认为是常量,意思就是它的值被设置完成后就不能再修改了:


const a = 1

a = 0 //报错

如果const的是一个对象,对象所包含的值是可以被修改的。抽象一点儿说,就是对象所指向的地址没有变就行:


const student = { name: 'cc' }

student.name = 'yy';// 不报错

student = { name: 'yy' };// 报错

有几个点需要注意:

  • let 关键词声明的变量不具备变量提升(hoisting)特性
  • let 和 const 声明只在最靠近的一个块中(花括号内)有效
  • 当使用常量 const 声明时,请使用大写变量,如:CAPITAL_CASING
  • const 在声明时必须被赋值

二、模板字符串

在ES6之前,我们往往这么处理模板字符串: 通过“\”和“+”来构建模板


$("body").html("This demonstrates the output of HTML \

content to the page, including student's\

" + name + ", " + seatNumber + ", " + sex + " and so on.");

而对ES6来说

  1. 基本的字符串格式化。将表达式嵌入字符串中进行拼接。用${}来界定;

  2. ES6反引号(``)直接搞定;


$("body").html(`This demonstrates the output of HTML content to the page,

including student's ${name}, ${seatNumber}, ${sex} and so on.`);

三、箭头函数(Arrow Functions)

ES6 中,箭头函数就是函数的一种简写形式,使用括号包裹参数,跟随一个 =>,紧接着是函数体;

箭头函数最直观的三个特点。

  • 不需要 function 关键字来创建函数
  • 省略 return 关键字
  • 继承当前上下文的 this 关键字

// ES5

var add = function (a, b) {

return a + b;

};

// 使用箭头函数

var add = (a, b) => a + b;

// ES5

[1,2,3].map((function(x){

return x + 1;

}).bind(this));

// 使用箭头函数

[1,2,3].map(x => x + 1);

细节:当你的函数有且仅有一个参数的时候,是可以省略掉括号的。当你函数返回有且仅有一个表达式的时候可以省略{} 和 return;

四、函数的参数默认值

在ES6之前,我们往往这样定义参数的默认值:


// ES6之前,当未传入参数时,text = 'default';

function printText(text) {

text = text || 'default';

console.log(text);

}

// ES6;

function printText(text = 'default') {

console.log(text);

}

printText('hello'); // hello

printText();// default

五、Spread / Rest 操作符

Spread / Rest 操作符指的是 ...,具体是 Spread 还是 Rest 需要看上下文语境。

当被用于迭代器中时,它是一个 Spread 操作符:


function foo(x,y,z) {

console.log(x,y,z);

}

let arr = [1,2,3];

foo(...arr); // 1 2 3

当被用于函数传参时,是一个 Rest 操作符:当被用于函数传参时,是一个 Rest 操作符:


function foo(...args) {

console.log(args);

}

foo( 1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]

六、二进制和八进制字面量

ES6 支持二进制和八进制的字面量,通过在数字前面添加 0o 或者0O 即可将其转换为八进制值:


let oValue = 0o10;

console.log(oValue); // 8

let bValue = 0b10; // 二进制使用 `0b` 或者 `0B`

console.log(bValue); // 2

七、对象和数组解构


// 对象

const student = {

name: 'Sam',

age: 22,

sex: '男'

}

// 数组

// const student = ['Sam', 22, '男'];

// ES5;

const name = student.name;

const age = student.age;

const sex = student.sex;

console.log(name + ' --- ' + age + ' --- ' + sex);

// ES6

const { name, age, sex } = student;

console.log(name + ' --- ' + age + ' --- ' + sex);

八、对象超类

ES6 允许在对象中使用 super 方法:


var parent = {

foo() {

console.log("Hello from the Parent");

}

}

var child = {

foo() {

super.foo();

console.log("Hello from the Child");

}

}

Object.setPrototypeOf(child, parent);

child.foo(); // Hello from the Parent

// Hello from the Child

九、for...of 和 for...in

for...of 用于遍历一个迭代器,如数组:


let letters = ['a', 'b', 'c'];

for (let letter of letters) {

console.log(letter);

}

// 结果: a, b, c

for...in 用来遍历对象中的属性:


const stus = {

name: 'Sam',

age: 22,

sex: '男'

}

for (let stu in stus) {

console.log(stus[stu]);

}

// 结果: Sam, 22, 男

十、ES6中的类

ES6 中支持 class 语法,不过,ES6的class不是新的对象继承模型,它只是原型链的语法糖表现形式。

函数中使用 static 关键词定义构造函数的的方法和属性:


class Student {

constructor() {

console.log("I'm a student.");

}

study() {

console.log('study!');

}

static read() {

console.log("Reading Now.");

}

}

console.log(typeof Student); // function

let stu = new Student(); // "I'm a student."

stu.study(); // "study!"

Student.read(); // "Reading Now."

stu.read(); // Uncaught TypeError: stu.read is not a function

类中的继承和超集:


class Phone {

constructor() {

console.log("I'm a phone.");

}

}

class MI extends Phone {

constructor() {

super();

console.log("I'm a phone designed by xiaomi");

}

}

let mi8 = new MI();

extends 允许一个子类继承父类,需要注意的是,子类的constructor 函数中需要执行 super() 函数。 当然,你也可以在子类方法中调用父类的方法,如super.parentMethodName()。

有几点值得注意的是:

  • 类的声明不会提升(hoisting),如果你要使用某个 Class,那你必须在使用之前定义它,否则会抛出一个 ReferenceError 的错误

  • 在类中定义函数不需要使用 function 关键词

十一、export & import

这两个对应的特性就是,模块化开发。封装模块的时候,用export把模块暴露出去


let _host = CONFIG.localEnv ? 'http://localhost:3000' : '';

let httpUrl = {

msiteAdress: _host + '/v1/pois',

searchplace: _host + '/v2/pois',

currentcity: _host + '/v1/ccities',

groupcity: _host + '/v1/groups',

hotcity: _host + '/v1/hots',

cityGuess: _host + '/v1/cities',

}

export { httpUrl }

需要使用的时候,用import引进行来。


import { httpUrl } from './../lib/http_url';

另一个按需引入的方法


let _host = CONFIG.localEnv ? 'http://localhost:3000' : '';

let httpUrl = {

msiteAdress: _host + '/v1/pois',

searchplace: _host + '/v2/pois',

currentcity: _host + '/v1/ccities',

groupcity: _host + '/v1/groups',

hotcity: _host + '/v1/hots',

cityGuess: _host + '/v1/cities',

}

let wxUrl = {

'checkedOpenId': _host + '/api/checked',

'getWxAuthor': _host + '/api/getAuthor',

'share': _host + '/api/share',

}

export { httpUrl, wxUrl }


import { httpUrl } from './../lib/http_url';

十二、Set, WeakSet, Map, WeakMap

1、Set

(1)基本用法

  • 类似于数组,但是成员的值是唯一的,没有重复的值。

  • var s = new Set()

  • Set内部判断两个值是否不同使用的算法类似于精确相等运算符('===')。这意味着两个对象总是不相等的。唯一的例外是NaN等于自身('==='认为NaN不等于自身)

(2)实例的属性

  • Set.prototype.constructor: 构造函数,默认是Set函数

  • Set.prototype.size: 返回Set实例成员的个数

(3)实例的操作方法

  • add(): 添加某个值,返回Set结构本身

  • delete(): 删除某个值,返回一个布尔值,表示是否删除成功

  • has(): 返回一个布尔值,表示参数是否是Set结构的成员

  • clear(): 清空所有成员,没有返回值

(4)实例的遍历方法

  • keys()

  • values() // Set结构的键名和键值是同一个值

  • entries()

  • forEach()

(5)总结

如果想在遍历操作中同步改变原来的Set结构,目前没有直接的方法,只有两种变通的方法。


// 方法一

let set = new Set([1,2,4])

set = new Set([...set].map(item => item * 2))

// 方法二

let set = new Set([1,2,4])

set = new Set(Array.from(set, x => x * 2))

2、WeakSet

(1)基本用法

  • WeakSet结构和Set类似,也是不重复的值的集合。但是,它与Set有两个区别

  • WeakSet的成员只能是对象

  • WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用。也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象是否还存在于WeakSet结构中。

  • var ws = new WeakSet()

(2)实例方法

  • WeakSet.prototype.add()

  • WeakSet.prototype.delete()

  • WeakSet.prototype.has()

3、Map

(1)基本用法

  • 类似于对象,也是键值对的集合,但是 '键' 的范围不限于字符串 (普通对象只能用字符串做key值),各种类型的值(包括对象)都可以作为key。

  • 可以接受一个数组为参数,并且该数组的成员是一个个表示键值的数组new Map([['name', 'alex.cheng'], ['age', 24]])

  • 获取一个未知的key,返回 undefined

  • Map的键实际上是跟内存地址绑定的,只要内存地址不同,就视为两个键

  • 0 和 -0,NaN 和 NaN 表示同一个键

(2)实例的属性和方法

  • Map.prototype.size

  • Map.prototype.constructor

  • set(key, value)

  • get(key)

  • has(key)

  • delete(key)

  • clear()

(3)遍历方法

  • keys()

  • values()

  • entries()

  • forEach()

(4)总结

Map本身没有map和filter方法,结合数组的方法,实现过滤


let map1= new Map()

.set(1, 'a')

.set(2, 'b')

.set(3, 'c')

let map2 = new Map([...map1].filter(([key, value]) => k < 3))

// filter(([key, value) => {}) [key, value] 这里是解构赋值

4、WeakMap

(1)基本使用


// WeakMap 可以使用 set 方法添加成员

const wm1 = new WeakMap();

const key = {foo: 1};

wm1.set(key, 2);

wm1.get(key) // 2

// WeakMap 也可以接受一个数组

// 作为构造函数的参数

const k1 = [1, 2, 3];

const k2 = [4, 5, 6];

const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]);

wm2.get(k2) // "bar"

  • WeakMap 结构与 Map 结构基本类似,唯一的区别是它只接受对象作为键名(null除外),不接受其他类型的值作为键名,而且键名所指向的对象不计入垃圾回收机制

const map = new WeakMap();

map.set(1, 2)

// TypeError: 1 is not an object!

map.set(Symbol(), 2)

// TypeError: Invalid value used as weak map key

map.set(null, 2)

// TypeError: Invalid value used as weak map key

上面代码中,如果将数值1和Symbol值作为WeakMap的健名,都会报错

  • WeakMap的设计目的在于,键名是对象的弱引用(垃圾回收机制不将该引用考虑在内),所以其所对应的对象可能会被自动回收。当对象被回收或者被销毁后,WeakMap将自动移出对应的键值对,再次访问的时候就是undefined。

  • 有助于防止内存泄漏


let myElement = document.getElementById('logo');

let myWeakmap = new WeakMap();

myWeakmap.set(myElement, {timesClicked: 0});

myElement.addEventListener('click',function() {

let logoData = myWeakmap.get(myElement);

logoData.timesClicked++;

}, false);

上面代码中,myElement是一个DOM节点,每当发生click事件,就更新一下状态。我们将这个状态作为键值放在WeakMap里,对应的键名就是myElement。一旦这个DOM节点删除,该状态就会自动消失,不存在内存泄漏风险。

十三、Symbol(js的第七种数据类型)

ES5 的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入Symbol的原因。

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。

Symbol 值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。


let s = Symbol();

typeof s

// "symbol"

上面代码中,变量s就是一个独一无二的值。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)"

上面代码中,s1和s2是两个 Symbol 值。如果不加参数,它们在控制台的输出都是Symbol(),不利于区分。有了参数以后,就等于为它们加上了描述,输出的时候就能够分清,到底是哪一个值。

如果 Symbol 的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,然后才生成一个 Symbol 值。


const obj = {

toString() {

return 'abc';

}

};

const sym = Symbol(obj);

sym // Symbol(abc)

注意,Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。

十四、 API推荐

1、字符串

(1)repeat

repeat方法返回一个新字符串,表示将原字符串重复n次。


repeat方法返回一个新字符串,表示将原字符串重复n次。

(2)includes & startsWith & endsWith

  • includes:是否找到了参数字符串,返回布尔值。

  • startsWith:参数字符串是否在原字符串的头部,返回布尔值。

  • endsWith:参数字符串是否在原字符串的尾部,返回布尔值。

三个方法都接受两个参数,第一个参数是参数字符串,第二个是开始检索的位置


var str='我就是小林老师'

str.startsWith('我就是') //true

str.startsWith('我') //true

str.startsWith('我',2) //false

str.startsWith('老师') //false

str.endsWith('老师') //true

str.includes('老师') //true

str.includes('我',3) //false

(3)padStart & padEnd

  • padStart: 如果字符串不够指定长度,在头部补全指定字符

  • padEnd:如果字符串不够指定长度,在尾部补全指定字符

两个方法都接收两个参数,第一个是指定字符串的最小长度,第二个用来补全的字符串。如果指定字符串的长度,等于或大于指定的最小长度(第一个参数)。就直接返回原字符串,如果忽略第二个参数,就使用空格补全原字符串!


var str='老湿'

str.padEnd(10,'123')//"老湿12312312"

str.padStart(10,'123')//"12312312老湿"

str.padEnd(10)//"老湿 "

str.padStart(10)//" 老湿"

str.padStart(1)//"老湿"

str.padEnd(1)//"老湿"

2、数值

(1)isNaN

检查一个值是否为NaN


Number.isNaN(NaN)//true

Number.isNaN(15)//false

(2)isInteger

判断一个值是否为整数,需要注意的是,比如1和1.0都是整数。


Number.isInteger(1)//true

Number.isInteger(1.0)//true

Number.isInteger(1.1)//false

(3)sign

判断一个数到底是正数、负数、还是零


Math.sign(-10)// -1

Math.sign(10)// +1

Math.sign(0)// +0

Math.sign(-0)// -0

Math.sign(NaN)// NaN

Math.sign('10')// +1

Math.sign('小林')// NaN

Math.sign('')// 0

Math.sign(true)// +1

Math.sign(false)// 0

Math.sign(null)// 0

(4)trunc

去除一个数的小数部分,返回整数部分


Math.trunc(1.1)//1

Math.trunc(-1.1)//-1

Math.trunc(-0.1)//-0

Math.trunc('123.456')//123

Math.trunc('小林')//NaN

3、对象

(1)assign

用于对象的合并,复制到目标对象。


var _name={name:'小林老师'},sex={sex:'男'},city={'city':'成都'}

Object.assign(_name,sex,city)//{name: "小林老师", sex: "男", city: "成都"}

var info1={name:'小林',sex:'男'},info2={name:'老师',city:'成都'}

Object.assign(info1,info2)//{name: "老师", sex: "男", city: "成都"}

克隆对象


var info1={name:'小林老师',sex:'男'}

var info3=Object.assign(info1,{})//{name:'小林老师',sex:'男'}

(2)keys

根据对象自身可遍历的键名进行遍历,返回一个数组


var info={name: "小林老师", sex: "男", city: "成都"}

Object.keys(info)//["name", "sex", "city"]

(3)values

根据对象自身可遍历的键值进行遍历,返回一个数组


Object.values(info)//["小林老师", "男", "成都"]

(4)entries

根据对象自身可遍历的键值对进行遍历,返回一个数组


Object.entries(info)//[["name", "小林老师"],["sex", "男"],["city", "成都"]]

4、数组

(1)from

from用于将两类对象转为真正的数组:类似数组的对象和可遍历的对象


Array.from('小林老师')//["小", "林","老","师"]

//常见的使用方式还有-将Dom集合和arguments转成真正的数组

let oLi = document.querySelectorAll('li');

Array.from(oLi).forEach(function (item) {

console.log(item);

});

// arguments对象

function fn() {

let args = Array.from(arguments);

}

//顺便说下Set

let newSet = new Set(['a', 'b', 'a', 'c'])

Array.from(newSet) // ['a', 'b','c']

//ES6 新增的数据结构--Set。它类似于数组,但是成员的值都是唯一的,不重复的。

//相信大家很容易想到怎么用了,比如数组去重,用Set实现就简单多了。

removeRepeatArray(arr) {

//return [Array.from(arr)]

return [...new Set(arr)]

}

(2)find

find方法,用于找出第一个符合条件的数组成员。如果没找到符合条件的成员就返回underfind


//第一个大于2的成员

[1, 2, 3, 4].find((n) => n > 2) //3

(3)findIndex

findIndex方法,用于找出第一个符合条件的数组成员的索引。


//第一个大于2的成员的索引

[1, 2, 3, 4].findIndex((n) => n > 2)//2

(4)includes

includes方法,用于某个数组是否包含给定的值,返回一个布尔值。如果没找到符合条件的成员就返回undefined


[1, 2, 3].includes(2)//true

[1, 2, 3].includes(5)//false

[1, 2, NaN].includes(NaN)//true