ES2015笔记1

170 阅读6分钟

ES2015笔记1

1.let与块作用域

作用域:代码中某个成员能够起作用的范围

在ES2015之前,只有全局作用域函数作用域,ES2015中新增了块作用域。

let和const都是块作用域,区别在于const是“恒量”,是只读的(内存位置固定)

块:用花括号包裹的范围,例如if和for中的

if(true) {
    var foo = 'foo'
} 
console.log(foo) // foo

在之前没有块作用域时,在块外面可以访问块内变量(这时只有var)

ES2015使用let可以限定变量作用域在块内,外部无法访问

if(true) {
    let foo = 'foo'
} 
console.log(foo) // ReferenceError

有一个典型的应用场景,在DOM元素中使用循环

var elements = [{}, {}, {}]  // 模拟DOM元素
for(var i = 0;i<elements.length; i++ ){
    elements[i].onclick = function() {
        console.log(i)
    }
}
// 此时打印的永远是3,因为i是全局变量

这个问题在ES2015之前一般使用闭包解决,闭包实际上就是用函数作用域代替全局作用域防止泄露,使用立即调用函数来实现闭包

var elements = [{}, {}, {}]  // 模拟DOM元素
for(var i = 0;i<elements.length; i++ ){
    elements[i].onclick = (function(i) {
        return function() {
            console.log(i)
    	}
    })(i)
}

现在有了let和块作用域就没有那么麻烦和反逻辑了,我们可以直接用let代替var防止变量泄露

var elements = [{}, {}, {}]  // 模拟DOM元素
for(let i = 0;i<elements.length; i++ ){
    elements[i].onclick = function() {
        console.log(i)
    }
}

有一个特殊的例子,就是for中实际上有两个作用域,圆括号内和花括号内。

for(let i = 0;i<3;i++) {
    let i = 'foo'
    console.log(i)
}

// foo foo foo

这其实也很好理解,因为上述for循环可以被拆解成以下代码:花括号内部的let i实际上是在if中,所以两者是互不影响的。

let i = 0
if(i<3) {
    let i = 'foo'
    console.log(i)
}
let i = 1 
if(i<3) {
    let i = 'foo'
    console.log(i)
}
let i = 2 
if(i<3) {
    let i = 'foo'
    console.log(i)
}
let i = 3 
if(i<3) {
    let i = 'foo'
    console.log(i)
}

变量提升与暂时性死区

用var声明的变量会被自动的提升到作用域最顶部(全局就提升到代码最顶,函数就提升到函数最顶),而let就不会

console.log(i) // undefined 声明但没有赋值
var i = 'foo'
console.log(j) // ReferenceError
let j = 'foo'

ES2015新增的let、const关键字声明的变量会产生块级作用域,如果变量在当前作用域中被创建之前被创建出来,由于此时还未完成语法绑定,如果我们访问或使用该变量,就会产生暂时性死区的问题,由此我们可以得知,从变量的创建到语法绑定之间这一段空间,我们就可以理解为‘暂时性死区’

2. 解构

数组解构

const arr = [1,2,3]
const [one,two,three] = arr
console.log(one,two,three) // 1,2,3

直接用变量名写在数组的特定下标,会按照下标位置分配数值

如果不是顺序引用全部,可以直接空占位确保引用位置

const arr = [1,2,3]
const [ , ,three] = arr
console.log(three) // 3

也可以用...来代表引用包括此位置的余下全部参数

这只能在解构位置的最后一位使用,因为如果在中间使用则无法识别取出多少位

const arr = [1,2,3]
const [one,...twoAndThree] = arr
console.log(twoAndThree) // 2,3

如果解构参数小于数组长度,则只解构出那一位,如果大于数组长度,则与数组下标大于长度一样取出undefined

同样的可以给解构参数设置默认值,没有提取到数组内容时生效

const arr = [1,2,3]
const [one,two,three,more = 'default value'] = arr
console.log(one,two,three) // 1,2,3
console.log(more) // default value

对象解构

数组有下标用来有序的访问元素,而变量则需要用key来匹配提取的内容

const vimerio = {name = 'vimeriochen' , age = 21}
const {age} = vimerio
console.log(age)// 21

也可以给这个变量再取一个别名进行重命名防止变量名冲突

const vimerio = {name = 'vimeriochen' , age = 21}
const {age:ageOfVimerio} = vimerio // 左边进行匹配,右边进行重命名
console.log(ageOfVimerio)// 21

其他方面和数组解构类似(比如设置默认值)

3.模板字符串

使用反引号代替单双引号可以产生模板字符串,它会记录字符串中的换行信息而不用再使用/n

同时也可以在其中使用 ${}差值表达式来引入变量,当然也可以在里面写js语句

const name = 'vimerio'
const gender = true
const msg = `hey, ${name}`
const msgPlus = `hey, ${name + 'chen'} , you are a ${gender?'man':'woman'}`
console.log(msg) // hey, vimerio
console.log(msgPlus) // hey, vimeriochen , you are a man

可以在模板字符串之前定义标签tag,标签实际上就是一个处理函数,它接收一个数组参数,这个数组参数实际上是模板字符串中被插值表达式分隔开来的各个组成部分,当然也可以接收插值表达式。标签函数的返回值实际上就是模板字符串的返回值

可以看出其作用就是进行较为复杂的格式化输出

const name = 'vimerio'
const gender = true
function myTag(strings,name,gender) {
    const sex = gender?'man':'woman' 
	return strings[0] + name + strings[1] + sex + strings[2]
}
const result = myTag`hey, ${name} is a ${gender}`
console.log(result) // hey, vimeriochen , you are a man

4.字符串的拓展方法

主要有3个: includes(), startsWith(), endsWith()

都是判断字符串是否包含某个子字符串,作用位置也就像函数名所示,函数会返回一个布尔值

这种操作代替了原本用正则表达式查找字符串的繁琐操作

5.参数相关

参数默认值

在ES2015之前如果参数没有传入(值为undefined),想要给其设置默认值,需要判断其是否为undefined然后再设置默认值,但是现在只需要直接在其后设置默认值就可以了

注意:带有默认值的形参只能在参数列表的最后一位

function foo(isSign = false) {
    if(isSign === false) {
        console.log('请先登录')
    }
}

剩余参数

对于未知个数的参数,在ES2015之前都是使用arguments对象去接收,而现在则可以使用 ...剩余操作符来得到

同样只能出现在最后一位,而且只能使用一次

function foo(...args) {
    console.log(args)
}
foo(1,2,3,4) // [1,2,3,4]

6.展开数组

...还可以作为展开操作符,按照次序传入数组元素

const arr = ['foo','bar','baz']
// before ES2015
console.log.apply(console,arr) // foo bar baz
// ES2015
console.log(...arr) // foo bar baz

7.箭头函数

基本形式很好懂,有一个小关键就是:只有一行语句时自动返回其执行结果,如果多行语句则需要手动return

箭头函数是匿名函数,而且不会改表this的指向:

const person = {
    name: 'vimerio',
    sayHi: function() {console.log(`hi,my name is ${this.name}`)},
    sayHiInArrow: () => {console.log(`hi,my name is ${this.name}`)},
    sayHiAsync: function() {
        setTimeout(function() {console.log(this.name)},1000)
    },
    sayHiAsyncInArrow: function() {
        setTimeout(() => {console.log(this.name)},1000)
    },
}
person.sayHi() // hi,my name is vimerio
person.sayHiInArrow() // hi,my name is undefined
person.sayHiAsync() // undefined 因为setTimeout在全局对象上被调用,而window.name不存在
person.sayHiAsyncInArrow() // vimerio 因为箭头函数中的this始终指向当前作用域(在这个例子中就是person的作用域),所以就可以取得person.name

8.对象字面量增强

在ES2015中,如果对象的属性名和值的变量名一致,则不需要重复,只需要写一次

而如果是方法的话,则不需要写function

const name = 'vimerio'
const person = {
    // name: name before ES2015
    // sayHi: function() {console.log(`hi,my name is ${this.name}`)} before ES2015
    name, // ES2015
    sayHi() {console.log(`hi,my name is ${this.name}`)} // ES2015
}

计算属性名

在ES2015之前如果想要动态的添加属性名,只能在对象初始化之后用索引器添加(对象名[属性名],比如 person[Math.random()] = 123

而现在则可以使用计算属性名直接添加

const person = {
    [Math.random()]: 123,
    [1+1]: 2
}

9.对象新增方法

Object.assign

Object.assign可以将多个源对象中的属性复制到一个目标对象中,第一个参数时目标对象(接收),其他函数是源对象(被复制)

如果源对象中属性与目标对象中属性属性名相同,则源对象会覆盖目标对象

如果多个源对象中属性相同,则后面覆盖前面(因为实际上是两个的计算的迭代)

const source1 = {a:123, b:123}
const target = {a:456, c:456}
const result = Object.assign(target, source1)
console.log(result) // { a: 123, c:456, b:123 }
console.log(result === target) // true 因为改变了target

Object.is

在全等(===)运算符中有两个比较反直觉的结果:

console.log(+0 === -0) // true
console.log(NaN === NaN) // false

虽然在某些场景下这样是挺好的,但是在越来越多业务场景中需要去区分正负零,同时也希望NaN作为一个错误的计算值(NaN不等于NaN以前的设计逻辑是:很多种结果都可能导致NaN,所以不等;但是今天更想直接发现NaN是一个比如除零的计算失误)被直接识别

所以ES2015创制了一个新的相等判断符 Object.is来解决这个问题,实际上除了这两个特殊情况外其他结果都和全等运算符一致

Object.assign(+0,-0) // false
Object.assign(NaN, NaN) // true

10.Proxy代理对象

如果想要监视对象某个属性的读写,在ES5中使用的是Object.definePRoperty,比如Vue中的双向数据绑定(v-model

而现在,我们可以使用proxy来设置对象的访问代理器,监视对象的读写:

第一个参数是需要代理的对象,第二个参数是代理的处理对象,可以设置get和set方法

一般的get中接收访问对象(也就是代理对象)和访问的属性名,正常的业务逻辑是判断代理对象是否存在property,有则返回无则给出undefined或者其他默认值

而set接收代理对象、属性名以及需要写入的值,将值赋给对象的属性

const person = {name: 'vimerio', age: 21}
const personProxy = new Proxy(person, {
    get(target, property) {
        return property in target ? target[property] : undefined
    },
    set(target, property, value) {
        // 还可以进行数据校验等操作
        target[property] = value
    }
})
console.log(personProxy.name) // vimerio 调用get(person, name)
console.log(personProxy.gender) // undefined
personProxy.gender = 'male'
console.log(personProxy.gender) // male