ES是一把利器,也是一匹野马。
扎实好ES基础,会让人如虎添翼,你还在犹豫什么。
- 暂时性死区:块级作用域内,在const和let声明变量之前该变量都是不可使用的。不管外部是否用同名变量已经声明。
- 不存在变量提升
- 不允许重复声明
- es5没有块级作用域,es6中使用const或let则自动会形成块级作用域
- 块级作用域可以达到和IIFE相同的效果
// IIFE 写法
(function () {
var num = ...;
...
}());
// 块级作用域写法
{
let num = 123;
}
- es6规范允许在块级作用域内声明函数,但是客户端(其他环境除外)的实现依旧是类似var的形式。所有考虑到兼容,不要在块级作用域内声明函数,即使需要函数也要写成表达式的形式(
let a = function(){}
) - es6的块级作用域必须要有大括号,否则引擎不会把它作为块级作用域:
if (true) let a = 1; // 不存在块级作用域
- const常量,指的是常量对应的内存地址不得改变,而不是对应的值不得改变。所有把引用类型的数据设置为常量,其内部的值是可以改变的,因此需要小心:
const a = {};
a.b = 13; // 没毛病,不会报错
const arr = [];
arr.push(123); // 也没毛病
// 如果需要,可以将对象冻结
const a = Object.freeze({})
// 除了对象本身,其属性也应该冻结
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
- es5的var/function声明的全局变量会自动挂载到全局对象中,但是es6的const/let/class声明的全局则不会。
数组解构
- 解构不到时变量的值为undefined
var [a, c] = [2];
a // 2
c // undefined
- 等号右侧只要具有iterator解构的数据都可以进行数组解构
- 解构可以设置默认值,只有严格等于undefined时才会使用默认值:
let [a, b = 1] = [2, null] // 1, null
let [a, b = 1] = [2] // 2 1
let [a, b = 1] = [2, undefined] // 2 1
- 解构可以使用其他变量,但是需要先声明
let [x = y, y = 1] = []; // 报错,使用y时,y还未声明
let [x = 1, y = x] = []; // x=1; y=1
对象解构
- 数组按照位置解构,对象按照同名属性解构
- 如果等号右侧不是对象或数组,解构时会先将其转换成对象。由于undefined/null无法转换成对象,对其解构时会报错:
const {a, b} = undefined || []; // 个人推荐这种这种写法避免报错,a undefined, b undefined
解构其他用法
- 解构函数返回的多个值
// 函数返回多个值只能通过数组或者对象的形式返回
const func = () => [1, {a: 1}, [1,2,3,4]];
const [first, second, third] = func(); // first 1,second {a: 1}, third [1,2,3,4]
const func = () => {
return {
first: 1,
second: { a: 1},
third: [1, 2, 3, 4, 5]
}
};
const {first, second, third} = func(); // first 1, second {a: 1}, third [1, 2, 3, 4, 5]
- 从导入的模块进行解构
import { moduleA, moduleB } from 'someModule';
- 字符串模板中,可以使用变量/函数等
var func = function () {return '123'}
var val = '456'
console.log(`this is string ${func()} + ${val}`) // this is string 123 + 456
- 如果变量的不是字符串,会按照一般规则转换成字符串,例如对象会调用其
toString
方法 - 字符串方法
// 字符串是否包含某个字符串
// 可接受第二个参数表示从下标n开始往后查找
var str = 'abcdefg';
str.includes('c', 1); // true
// 字符开始是否包含某个字符串
// 第二个参数表示从下标n开始是否包含某个字符串
str.startsWith('b') // false
str.startsWith('b', 1) // true
// 结尾是否包含
str.endsWith('b') // false
str.endsWith('b', 2) // true,表示前n个字符的结尾是否包含查询的字符串
str.endsWith('b', 1) // false
// 返回一个重复n次的新字符串
str.repeat(2) // 'abcdefgabcdefg'
// 补全字符串(返回补全后的新字符串,不改变原字符串)
var str = 'a';
str.padStart(4, 'b') // bbba,在前面补
str.padEnd(4, 'b') // abbb。 后补
str.padStart(4) // ' a', 默认补空格
// 消除前/尾空格 (返回新字符,不改变原字符串)
var str = ' a ';
str.trimStart() // "a "
str.trimEnd() // " a"
- 新增方法,建议放在Number对象上使用(标准也在逐步减少全局方法,使语言逐渐模块化)
// 判断有限数
Number.isFinite(123) // true
Number.isFinite(Infinity) // false
Number.isFinite('123') // false,非数字类型一律返回false
// parseInt和parseFloat移植到了Number对象上
Number.parseInt()
Number.parseFloat()
// 判断整数
Number.isInteger(2) // true
Number.isInteger(2.0) // true js中整数和浮点数采用的是同样的储存方法,所以 2 和 2.0 被视为同一个值
JavaScript 采用 IEEE 754 标准, 数值存储为64位双精度格式,
数值精度最多可以达到 53 个二进制位(1 个隐藏位与 52 个有效位)。
如果数值的精度超过这个限度,第54位及后面的位就会被丢弃,这种情况下,Number.isInteger可能会误判。
- js中的最小常量, 表示1与大于1的最小浮点数的差值
Number.EPSILON
// 2.220446049250313e-16
引入该常量是为浮点数计算,设置一个误差范围:
0.1 + 0.2 === 0.3 // false
// 可以设计如下函数,误差小于某个差值时自动忽略
const isEqualInAcceptErrorRange = (a, b) => Math.abs(a - b) < Number.EPSILON * Math.pow(2, 2);
isEqualInAcceptErrorRange(0.1 + 0.2, 0.3) // true
- Math扩展
Math.trunc(1.111) //1, 返回去除小数后的值,等于1.111 | 0
Matn.sign(1.111) // +1,判断一个数是正数还是负数,-1负数,0,-0,其他值反水NaN
- 函数可以写默认参数
// 不能有同名函数参数
// 已声明的参数变量在函数体内不可以用let/const重复声明
// 参数表达式是惰性求值的,每次调用函数时都会重新计算参数的值(也只会在每次调用时才计算参数的值)
function func(a = 1, b = 2, c) {
}
- 与对象解构一起使用
// 与对象解构一起使用,但是如果函数调用时没传参数,
// 那么从undefined进行解构则会报错
const func = ({ x, y = 1 }) => {};
func({}) // 正常
func() // 报错
// 与对象解构+函数参数默认值一起使用
const func2 = ({ x, y = 1 } = {}) => {};
func2() // 正常
- 一旦使用了函数默认参数,函数的length属性将失真,length本质是函数预期传入的参数
- rest参数
// 只能使用在最后,后面不能再有参数
// rest参数是一个真正的数组,arguments是类数组
const func = function (...args) {
return args
// 等同于
// return Array.prototype.slice.call(arguments)
}
- 箭头函数返回对象时必须加小括号
const func5 = () => ({a: 1, b: 2})
- 箭头函数的this指定义时的上下文中的this(因为箭头函数没有this,所有this自然是其外部的this),而普通函数中this则指向运行时的上下文
- 箭头函数不可以作为构造函数使用
- 箭头函数无arguments对象,可以哟过rest参数替代
- 不应该使用箭头函数的场景:
var cat = {
count: 0,
add: () => {
// 由于对象构不成单独的作用域,所以如果写成箭头函数,
// 则this在此时指向全局了,而不是该对象
this.count ++
}
}
cat.add() // cat.count并没有变化
// 事件中需要this指向当前元素时,也不可以使用箭头
button.addEventListener('click', () => {
// 得不到期望的结果
this.classList.toggle('on');
});
- 箭头函数可以嵌套
// 例如定义一个管道函数,即前一个函数返回的值作为下一个函数的参数
const pipeline = (...funcs) => v => funcs.reduce((a, b) => b(a), v);
- 数组扩展运算符只能用在函数中,用于将数组转换成逗号分割的参数序列
- 扩展运算符可以替代apply来进行传递参数
var arg = [1, 2, 3, 4]
func.apply(null, arg)
func(...arg)
Max.max.apply(null, arg)
Math.max(...arg)
- 合并数组
// 注意是浅拷贝
var arr = [...arr1, ..arr2, ..arr3]
- 所有定义了Iterator接口的数据都可以使用扩展运算符转换成真正的数组
[...Iterator]
- Array.from()将累数组对象和Iterator接口的数据解构转化成真正的数组
var divs = document.getElementsByTagName('div')
Array.from(divs)
// 接受第二个参数,用于处理每一项数据,类似于map
Array.from(arrayLike, x => x * x);
- Array.of()返回参数组成的新数组
- 扩展方法
// 拷贝数组部分内容覆盖到数组其他位置
// 参数,覆盖开始的位置(含)/拷贝开始的位置(含)/拷贝结束的位置(不含)
// 负数表示倒数
[1,2,3,4,5].copyWithin(1, 2, 3) // [1, 3, 3, 4, 5]
// 找到某个值,否则返回undefined
// 接受第二个参数绑定回调函数的上下文
[1,2,3,4].find((e) => e === 3) // 3
// 找到符合条件的第一个值的下标,否则返回-1
var arr = [1,2,3,4,5]
arr.findIndex(e => e > 4) // 4
// 填充数组
// 参数:填充值,填充开始位置,填充结束位置(不含)
// 只会填充已有的值
[1,3,4,5,6,7].fill('a', 3, 11111) // [1, 3, 4, "a", "a", "a"]
// 遍历键名,arr.keys()
var arr = ['a', 'b' ,'c' ,'d']
for(let index of arr.keys()) {
console.log(index)
}
// 遍历值,arr.values()
var arr = ['a', 'b' ,'c' ,'d']
for(let index of arr.values()) {
console.log(index)
}
// 遍历键值对,arr.entries()
var arr = ['a', 'b' ,'c' ,'d']
for(let index of arr.entries()) {
console.log(index)
}
// 是否包含某个值
[1, 2, 3].includes(2) // true
// falt数组,默认flat一层
[1, [2,3], [[4, [5, [6]]]]].flat() // 只有第一层 嵌套的被flat
// 等价于
[1, [2,3], [[4, [5, [6]]]]].flat(1)
// 接收一个参数表示要flat的层数,如果想flat全部层级
[1, [2,3], [[4, [5, [6]]]]].flat(Infinity) // [1, 2, 3, 4, 5, 6] Infinity表示flat全部层级
// flatMap先对数组每项的值进行回调函数的处理后再flat
// 只能拉伸一层
[2, 3, 4].flatMap((x) => [x, x * 2])
// 空位
// 空位的值不是undefined,[,,,,]或者new Array(5)都会产生空位
// es6明确规定空位会转换成undefined
各种方法对空位的处理不一致,所以要绝对避免开发中出现空位
- 表达式作为对象的属性名
var name = 'xiaoming';
var o = {
['liu'+ name]: 1
}
o.liuxiaoming // 1
- 获取属性的描述信息
Object.getOwnPropertyDescriptor({a: 1}, 'a')
- 对象属性的遍历
var o = {a: 1}
for/in // 自身和继承的除symbols外的所有属性
Object.keys(o) // 自身的除Symbols和不可枚举外的所有属性
Object.getOwnPropertyNames(o) // 自身的除Symbols外所有属性
Object.getOwnPropertySymbols(o) // 所有Symbols属性
Reflect.ownKeys(o) // 自身所有属性
- super关键字
// es6新增super指向对象的原型对象
// 只能用在对象的方法中,否则报错
var o = {a: 1}
var o2 = {
a: 2,
b () {
return super.a
}
}
// 将o2的原型设置为o
Object.setPrototypeOf(o2, o)
o2.b() // 1
- 对象的解构,用法和数组一样,也是浅拷贝,不能解构undefined/null
- 解构可以解构到原型上继承到值,但是扩展运算符到解构无法解构到原型上继承到值
var o = Object.create({a: 1})
var {a} = o // a 1
var {...rest} = {} // rest {}
- 对象到方法
// 是否相等
Object.is(a, b) // 判断两个值是否完全相等,比===强在+0-0,NaN的判断
// 对象浅拷贝
Object.assign(target, o2, o3, o4) // 将o234合并到target对象上,同名属性替换
// 获取对象单个属性的描述对象
Object.getOwnPropertyDescriptor(o2, 'a')
// 获取对象所有属性的描述对象,注意两者的区别,一个加s,一个没有s
Object.getOwnPropertyDescriptors(o2)
// 设置对象的原型
var o1 = {}
var o2 = {a: 1}
Object.setPrototypeOf(o1, o2) // 把o1的原型设置为o2
// 读取对象的原型对象
Object.getPrototypeOf(o1)
- Symbol是新增的第七种原始数据类型,凡是属性名为Symbol类型的,可以保证不与其他属性名冲突
var s1 = Symbol()
var s2 = Symbol('s2') // 为s2增加一个描述
s2.description // 获取s2的描述
// 作为属性名使用
var o = {
[s2]: 456
}
o[s1] = 123 // 不能使用点运算符,否则会认为是字符串
-
Symbol不会被
for...in
、for...of
循环,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回 -
Symbol.for()/Symbol.keyfor()
// Symbol.for接收一个参数,每次先搜索有没有存在这个Symbol,有则返回,无则创建再返回
var s1 = Symbol.for('s1')
var s2 = Symbol.for('s1')
s1 === s2
// Symbol.keyFor(s1)返回一个Symbol.for()登记过的值的key
// 参数是一个Symbol.for()注册的数据类型
Symbol.keyFor(s1) // "s1"
- 内置的Symbol值
// 对象的Symbol.hasInstance指向对象内部一个方法,使用instanceOf判断一个变量是否是该对象的实例时,会自动调用该对象的这个方法去验证
// 比如,foo instanceof Foo在语言内部,实际调用的是Foo[Symbol.hasInstance](foo)
class MyClass {
[Symbol.hasInstance] (arr) {
return arr instanceof Array
}
}
[] instanceof new MyClass() // true
'aa' instanceof new MyClass() // false
var o = {}
o instanceof new MyClass() // false
// 类似与不能重复的数组
var s = new Set()
var s2 = new Set([1, 2, 3])
s2.add(4)
// 利用Set去重
[...new Set([1,2,3,4,5,5,5,5])]
// 获取长度
s.size
// 删除
s.delete(1) // 删除成功返回true,否则返回false
// 清除所有
s.clear()
// 检测是否含有
s.has(2) // 返回true/false
// 转换成数组
Array.from(new Set([1,2,3,4]))
// Set可以直接使用forEach方法
// filter/map等只能间接使用
s.forEach((value, key) => {})
s = new Set([...s].filter(e => {}))
// 利用Set实现并集
var s1 = new Set([1,2,3])
var s2 = new Set([2,3,4])
new Set([...s1, ...s2])
// 交集
new Set([...s1].filter(e => s2.has(e)))
// 键值对的集合,类似于对象,但是键不再只是字符串
var m = new Map()
var m2 = new Map([[1, 2], ['key', 345]])
// 如果键是基本数据类型,只要严格相等则认为是同一个键
// 如果键是引用类型,只有是同一个引用才判定是同一个键
// 新增数据,set返回当前map对象,所以支持链式调用
// 有则覆盖,无则生成
m.set(2, 3)
m.set(['aa'], 1234)
m.set({}, 543).set('b', 123)
// 读取键值,无则返回undefined
m.get(2)
// 获取长度
m.size
// 删除
m.delete(键名) // 返回true/false
// 清除所有
m.clear()
// 判断是否含有某个键
m.has()
// 遍历,遍历顺序就是插入顺序
m.forEach()
m.keys()
m.values()
m.entries()
- Proxy代理,给原目标设置一个代理器来控制访问。代理器只对Proxy返回的实例有效,对原目标对象无效
var obj = {x: 1}
var proxyObj = new Proxy(obj, {
get () {
return 2222;
}
})
console.log(proxyObj.x); // 2222
// Proxy的实例可以作为其他对象的原型对象使用
var obj2 = Object.create(proxyObj)
console.log(obj2.x) // 2222
- 代理器拦截设置(即new Proxy的第二个参数)
// get拦截对象属性的读取
// 接收三个参数:拦截目标,拦截属性,proxy实例
// 例如,利用proxy生成dom节点的函数
const dom = new Proxy({}, {
get (target, key, proxySelf) {
return function (attrs = {}, ...children) {
let el = document.createElement(key);
for (let i of Object.keys(attrs)) {
el.setAttribute(i, attrs[i])
}
for (let child of children) {
if (typeof child === 'string') {
child = document.createTextNode(child)
}
el.appendChild(child)
};
return el;
}
}
});
// 调用生成节点
const el = dom.div({},
'Hello, my name is ',
dom.a({href: '//example.com'}, 'Mark'),
'. I like:',
dom.ul({},
dom.li({}, 'The web'),
dom.li({}, 'Food'),
dom.li({}, '…actually that\'s it')
)
);
document.body.appendChild(el);
// set拦截对象属性的设置
// 接收四个参数:目标对象,设置的属性,属性值,proxy实例
var proxyHandler = {
get(target, key, value, proxySelf) {
if (key[0] === '_') {
throw new Error(`${key}是一个内部属性`)
}
return target[key]
},
set(target, key, value, proxySelf) {
if (key[0] === '_') {
throw new Error(`${key}是一个内部属性`)
}
target[key] = value
}
}
var objProxy = new Proxy({}, proxyHandler)
objProxy.a = 123
objProxy.a // 123
objProxy._a // 报错内部属性
// has拦截HasProperty操作,例如典型的'a' in obj,但是不对for/in生效
// 第一个参数源对象,第二个参数属性
// construct 拦截new操作
// 三个参数:原目标对象/函数参数/proxy实例本身
var Person = function() {}
var PersonProxy = new Proxy(Person, {
construct (target, args, proxySelf) {
console.log('拦截了new操作')
return new target(args)
}
})
new PersonProxy()
// deleteProperty拦截delete操作,如果返回false则无法删除
// 参数 target,key
// defineProperty拦截Object.defineProperty操作
// 返回false则该操作无效
// 参数: target, key, proxySelf
// getOwnPropertyDescriptor拦截Object.getOwnPropertyDescriptor()操作
// 其他拦截方法
- getPrototypeOf
- isExtensible
- ownKeys
- preventExtensions
- setPrototypeOf
// 取消proxy的代理器,用于在完成代理后,立即收回代理权
// Proxy.revocable返回一个对象,该对象的proxy属性是该代理的实例,revoke是一个可以收回proxy代理权的函数。
var Person = function() {}
var {proxy, revoke} = Proxy.revocable(Person, {
construct (target, args, proxySelf) {
console.log('拦截了new操作')
return new target(args)
}
})
new proxy() // 正常实例化
revoke() // 取消其proxy的代理权
new proxy() // 无法实例化
// proxy实例之后,实例中的this指代proxy实例
const getname = "GET_USER_NAME";
class Person {
// 实例属性也可以写在最顶部,此时不需要加this
time = new Date();
// 构造函数里面的内容
constructor (x, y) {
this.x = x;
this.y = y;
console.log(new.target === Person) //
}
// 类原型上的方法
toString () {
console.log(this.x + ' - ' + this.y);
}
// 静态方法,只有类能访问的,实例不能访问
// 因此这里的x,y都是undefined,因为x,y属性都是实例属性
static toString () {
console.log('静态方法:', this.x + ' - ' + this.y)
}
// 可以使用表达式命名
[getName] () {
}
}
const p1 = Person('mack', 25);
p1.toString(); // mack - 25
Person.toString() // 静态方法: undefined - undefined
- class中的this指向类的实例
- 类的属性名可以采取表达式
- 可以对属性设置get/set拦截
- 如果不定义constructor函数,则类会默认增加一个constructor函数,并返回类的实例(即this)。如果在constructor中显示返回一个对象,会导致实例结果不再是类的实例。
- es6为new引入了target属性,返回实例的构造函数,如果不是通过new调用的,则返回undefined
function Person () {
// 以前的写法
if (!(this instanceof Person)) {
return new Person()
}
// 现在可以通过new.target
if (new.target !== Person) {
return new Person()
}
}
// 定义父类
class Parent {
constructor (name, age) {
this.name = name;
this.age = age;
}
static getName () {
console.log(this.name);
return this.name;
}
getAge () {
console.log('age: ', this.age);
}
}
// 定义子类,子类继承父类
class Child extends Parent {
constructor (...arg) {
// 通过super继承父类的属性和方法
// 必须通过super继承后,才能使用子类自己的this
// 否则报错,得不到子类的this对象
super(...arg); // es5的实现方式Parent.apply(this, arg)
// this.name = name;
// this.age = age;
}
}
var p1 = new Child('xiaoming', 24);
Child.getName() // 父类的静态方法,会被子类继承
p1.getAge() // 父类的原型方法,会被子类的实例继承
Object.getPrototypeOf(Child)
获取父类,可以用来判断一个类是否继承另一个类- super作为函数时指代父类的constructor,只能用在constructor中
- super作为对象使用时,在普通方法中指代父类原型,在静态方法中,指代父类。
- 通过super对象调用父类方法时,父类方法中的this,此时指向子类。
const a = 5
const b = function(){}
const c = class {}
// 导出
export const age = '123'
export { a, b, c } // 导出多个,建议采取该方式,放在文件底部,一眼就看清楚有哪些导出
export { a as bobyAge } // 导出时定义别名
// 导入
import { a, b, c } from './someJs' // 需要和导出的变量名对应
import { a as otherName } './someJs' // 可以起一个别名
import * as types from './someJs' // 导入所有变量,并起一个types变量名
- 多次导入同一个文件,只会执行一次
- export导出的内容,import时需要加{}
- import在编译时执行,不是在运行时执行,因此不能使用变量
- import存在声明提升,因此最好不要和require时使用还对其有依赖关系
const a = 123;
const f = function () {}
const o = {}
const C = Class {}
// 默认导出
// 一个模块文件只能有一个默认导出
export default a;
// 或
export default f
// 或
export default {
a,
f,
o,
C
}
// 导入
import newName from './some.js' // 导入时不用和导出的变量名对应,可以随便起一个名字
import _ from 'underscore';
// 同时导入默认内容和其他内容
import o, {a, b, c} from './some.js'
- 导入导出的复合写法
// 先导入后导出,此时并没有把a,b导入到当前模块,只相当于对外做了一个转发
export {a, b} from './some.js'
// 也可以使用别名
export {a as otherName} from './some.js'
本文内容为温习es6内容,对遗漏知识点进行记录,便于日后翻阅、加深记忆。 内容拜读的阮一峰大大的ES6入门
百尺竿头、日进一步。
我是愣锤,一名前端爱好者。
欢迎批评与交流。