一、不一样的变量声明: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来说
-
基本的字符串格式化。将表达式嵌入字符串中进行拼接。用${}来界定;
-
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