let与块级作用域
全局污染
for (var i = 0; i < 3; i++) {
for (var i = 0; i < 3; i++) {
console.log(i)
}
}
输出结果
0 1 2
以上代码只会输出3次,因为内层循环执行完毕之后,i的值已经是3了,因为使用var声明的变量是全局作用域,所以外层循环不满足条件,不会执行。
使用let
关键可以产生块级作用域
for (let i = 0; i < 3; i++) {
for (let i = 0; i < 3; i++) {
console.log(i)
}
}
以上代码可以按预期输出9次
var全局污染demo2
var elements = [{}, {}, {}]
for (var i = 0; i < elements.length; i++) {
const element = elements[i];
element.onClick = function () {
console.log(i)
}
}
elements[0].onClick()
elements[1].onClick()
elements[2].onClick()
以上代码无论调用哪一个元素的onClick方法,都会打印3,原因跟上面一样,使用var声明的i变量为全局变量,循环执行完成之后,i的值已经是3了。
可以使用闭包解决这个问题
var elements = [{}, {}, {}]
for (var i = 0; i < elements.length; i++) {
const element = elements[i];
element.onClick = (function (i) {
return function() {
console.log(i)
}
})(i)
}
elements[0].onClick()
elements[1].onClick()
elements[2].onClick()
使用let
解决此问题
var elements = [{}, {}, {}]
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
element.onClick = function () {
console.log(i)
}
}
elements[0].onClick()
elements[1].onClick()
elements[2].onClick()
for循环两层嵌套作用域
for (let i = 0; i < 3; i++) {
let i = 'foo'
console.log(i)
}
以上代码正常输出三次"foo"
代码拆解
let i = 0
if (i < 3) {
let i = 'foo'
console.log(i)
}
i++
if (i < 3) {
let i = 'foo'
console.log(i)
}
i++
if (i < 3) {
let i = 'foo'
console.log(i)
}
i++
if (i < 3) {
let i = 'foo'
console.log(i)
}
i++
变量声明提升
console.log(foo)
var foo = 'aa'
以上代码能正常运行,并输出undefined
使用let修改
console.log(foo)
let foo = 'aa'
以上代码会报出异常
const 恒量/常量
变量一旦声明就不能被修改
const name = 'a'
name = 'b'
const name
name = 'a'
以上代码都会报错
但是使用const声明的对象内的属性可以被重新赋值
const obj = {}
obj.name = 'a'
const变量不能被修改意思是不能被重新分配内存地址
数组的解构
const arr = [100, 200, 300]
const [foo, bar, baz] = arr
console.log(foo, bar, baz)
// 解构部分值
const [, , baz] = arr
console.log(baz)
// ...rest剩余值
const [foo, ...rest] = arr
console.log(foo, ...rest)
// 多出来的值会输出undefined
const [foo, bar, baz, more] = arr
console.log(foo, bar, baz, more)
// 设置默认值
const [foo, bar, baz, more = 'default value'] = arr
console.log(foo, bar, baz, more)
//示例
const path = '/foo/bar/baz'
const [, rootDir] = path.split('/')
console.log(rootDir)
对象的解构 Destructuring
const obj = { name: 'zce', age: 18 }
const { name, age } = obj
console.log(name)
// 重命名+默认值
const { name: objName = 'jack' } = obj
模板字符串
const str = `hello es 2015, this is a \`string\``
const name = 'tome'
const msg = `hey, ${name}`
带标签的模板字符串
const name = 'tom'
const gender = true
function myTagFunc(strings, name, gender) {
// console.log(strings, name, gender)
// return '123'
const sex = gender ? 'man' : 'woman'
return strings[0] + name + strings[1] + sex + strings[2]
}
const result = myTagFunc`hey, ${name} is a ${gender}`
console.log(result)
可以用来处理模板字符串中的参数
字符串的扩展方法
startWith
endWith
includes
const messge = 'Error: foo is not defined.'
message.startWith('Error')
message.endWith('.')
message.includes('foo')
参数默认值
function foo (bar, enable = true) {
console.log(enable)
}
foo(false)
带有默认值的参数需要放到最后
剩余参数
function foo() {
console.log(arguments) // 伪数组
}
foo(1, 2, 3, 4)
function bar(...args) {
console.log(...args)
}
bar(1, 2, 3, 4)
展开数组Spread
const arr = ['foo', 'bar', 'baz']
console.log.apply(console, arr)
console.log(...arr)
箭头函数
const fn = (a) => a + 1
箭头函数和this
const person = {
name: 'tom',
sayHi: function () {
console.log(`${this.name}`)
},
sayHi2: () => {
console.log(`${this.name}`) // 输出undefined
},
sayHiAsync: function () {
setTimeout(function (){
console.log(this.name) // 输出undefined
}, 200);
},
sayHiAsync: function () {
const _this = this
setTimeout(function (){
console.log(_this.name) // 输出tom
}, 200);
},
sayHiAsync2: function () {
setTimeout(() => {
console.log(this.name) // 输出tom
}, 200);
}
}
对象字面量增强
const bar = '1'
const obj = {
foo: 123,
// bar: bar,
bar,
method1: function () {
console.log('method1')
},
method2() {
console.log('method2')
console.log(this) // this指向当前字面量
},
[Math.random()]: 123 // 计算属性名
}
console.log(obj.method2())
对象扩展方法 Object.assign
将多个原对象中的属性复制到一个目标对象中
const source1 = {
a: 123,
b: 123
}
const target = {
a: 456,
c: 456
}
const result = Object.assign(target, source1)
console.log(target) // { a: 123, c: 456, b: 123 }
console.log(target === result) // true
function func (obj) {
obj.name = 'func obj' // 形参obj与全局变量obj指向同一块内存地址,所以这样修改会影响到全局变量
console.log(obj)
// 使用Object.assign生成一个新的对象
const funcObj = Object.assign({}, obj)
funcObj.name = 'func obj'
console.log(funcObj)
}
const obj = { name: ' global obj'}
func(obj)
console.log(obj)
对象扩展方法 is
console.log(
NaN === NaN, //false
-0 === +0, // true
Object.is(NaN, NaN), // true
Object.is(-0, +0) // false
)
Proxy 代理对象
ES5中可以使用Object.defineProperty来为对象添加属性,这样可以捕获到属性的读写过程
const personProxy = new Proxy(person, {
get (target, property) {
console.log(target, property)
return property in target ? target[property] : 'default'
// return 100
},
set (target, property, value) {
console.log(target, property, value)
if (property === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError(`${value} is not an int`)
}
}
target[property] = value
}
})
personProxy.gender = true
personProxy.age = '11' // 报错
console.log(personProxy.name)
console.log(personProxy.xxx)
Proxy和Object.defineProperty区别
- Object.defineProperty只能监视到属性的读写,Proxy能够监视到更多对象操作,如delete,对对象中方法的调用等。
const personProxy2 = new Proxy(person, {
deleteProperty(target, property) {
console.log(target, property)
delete target[property]
}
})
delete personProxy2.name
console.log(person)
Proxy可以监视的对象操作
handler方法 | 触发方法 |
---|---|
get | 读取某个操作 |
set | 写入某个属性 |
has | in操作符 |
deleteProperty | delete操作符 |
getPrototypeOf | Object.getPrototypeOf() |
setPrototypeOf | Object.setPrototypeOf() |
isExtensible | Object.isExtensible() |
preventExtensions | Object.preventExtensions() |
getOwnPropertyDescriptor | Object.getOwnPropertyDescriptor() |
defineProperty | Object.defineProperty |
ownKeys | Object.getOwnPropertyNames()、Object.getOwnPropertySymbols() |
apply | 调用一个函数 |
construct | 用new调用一个函数 |
Proxy更好的支持数组对象的监视
之前都是重写数组的操作方法
const list = []
const listProxy = new Proxy(list, {
set (target, property, value) {
console.log('set', property, value)
target[property] = value
return true // 表示设置成功
}
})
listProxy.push(100)
Proxy不需要侵入对象
Proxy是以非侵入的方式监管了对象的读写
const person = {}
// 使用Object.defineProperty
Object.defineProperty(person, 'name', {
get () {
console.log('name 被访问')
return person._name
},
set (value) {
console.log('name 被设置')
person._name = value
}
})
Object.defineProperty(person, 'age', {
get () {
console.log('age 被访问')
return person._age
},
set (value) {
console.log('age 被设置')
person._age = value
}
})
person.name = 'jack'
console.log(person)
const person2 = {
name: 'zce',
age: 20
}
// 使用Proxy更加合理
const personProxy = new Proxy(person2, {
get(target, property) {
return target[property]
},
set (target, property, value) {
target[property] = value
}
})
Reflect
统一提供一套用于操作对象的API
示例
const objA = {
foo: '123',
bar: '456'
}
const proxy = new Proxy(objA, {
get(target, property) {
console.log('watch logic')
Reflect.get(target, property)
}
})
const personA = {
name: 'zce',
age: 20
}
console.log('name' in personA)
console.log(delete personA['age'])
console.log(Object.keys(personA))
console.log(Reflect.has(personA, 'name'))
console.log(Reflect.deleteProperty(personA, 'age'))
console.log(Reflect.ownKeys(personA))
Promise
解决了传统异步编程中回调函数嵌套过深的问题
class类
function Student(name) {
this.name = name
}
Student.prototype.say = function () {
console.log(`hi, my name is ${this.name}`)
}
const stu = new Student('andy')
stu.say()
class StudentClass {
constructor(name) {
this.name = name
}
say () {
console.log(`hi, my name is ${this.name}`)
}
}
const sc = new StudentClass('jack')
sc.say()
extends
class Person {
constructor(name) {
this.name = name
}
say () {
console.log(`hi, my name is ${this.name}`)
}
}
class Student extends Person {
constructor(name, number) {
super(name)
this.number = number
}
hello () {
this.say()
console.log(`my number is ${this.number}`)
}
}
const s = new Student('andy', 10)
s.hello()
Set
const set = new Set()
set.add(1).add(2).add(3).add(4).add(2)
console.log(set)
set.forEach(element => {
console.log(element)
});
for (const i of set) {
console.log(i)
}
Map
传统对象只能使用字符串作为key
const obj = {}
obj[true] = 'value'
obj[123] = 'value'
obj[{ a: 1 }] = 'value'
console.log(Object.keys(obj))
// 输出 [ '123', 'true', '[object Object]' ]
使用Map
const m = new Map()
const tom = { name: 'tom'}
m.set(tom, 90)
console.log(m)
console.log(m.get(tom))
m.forEach((value, key) => {
console.log(value, key)
})
Symbol
最主要的作用就是为对象添加独一无二的属性名
// SharedArrayBuffer.js
const cache = {}
// a.js
cache['foo'] = Math.random()
// b.js
cache['foo'] = '123'
console.log(cache)
const s = Symbol()
console.log(s)
console.log(typeof s)
console.log(Symbol() === Symbol())
console.log(Symbol('foo'))
console.log(Symbol('bar'))
console.log(Symbol('baz'))
const obj = {
[Symbol()]: 123
}
console.log(obj)
// 私有成员
const name = Symbol()
const person = {
[name]: 'zce',
say () {
console.log(this[name])
}
}
console.log(Symbol() === Symbol(), Symbol('foo') === Symbol('foo')) // false
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(s1 === s2) // true
const obj = {
[Symbol.toStringTag]: 'SObject',
}
console.log(obj.toString()) // [object SObject]
const obj2 = {
[Symbol()]: 'symbol value',
foo: 'normal value'
}
for (const key in obj2) {
console.log(key) // foo
}
console.log(Object.keys(obj2)) //[ 'foo' ]
console.log(JSON.stringify(obj2)) //{"foo":"normal value"}
console.log(Object.getOwnPropertySymbols(obj2)) //[ Symbol() ]
for...of
const arr = [100, 200, 300, 400]
for (const item of arr) {
console.log(item)
// 100
// 200
// 300
// 400
}
arr.forEach(item => console.log(item))
for (const item of arr) {
console.log(item)
// 随时跳出循环
if (item > 100) {
break
}
}
// arr.forEach() //不能跳出循环
// arr.some()
// arr.every()
const m = new Map()
m.set('foo', '123')
m.set('bar', '345')
for (const [key, value] of m) {
console.log(key, value)
}
for...of循环是一种数据统一遍历方式,但是不能遍历普通对象
const obj = { name: 'andy'}
for (const item of obj) {
console.log(item)
}
TypeError: obj is not iterable
可迭代接口
Array,Set,Map都实现了Ieterator接口
const set = new Set(['foo', 'bar', 'baz'])
const iterator = set[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
let isDone = false
while(!isDone) {
const valueObj = iterator.next()
console.log(valueObj)
isDone = valueObj.done
}
迭代器模式
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '英语'],
work: ['喝茶'],
each: function(callback) {
const all = [].concat(this.life, this.learn, this.work)
for (const item of all) {
callback(item)
}
},
[Symbol.iterator]: function() {
const all = [...this.life, ...this.learn, ...this.work]
let index = 0
return {
next: function () {
return {
value: all[index],
done: index++ >= all.length
}
}
}
}
}
todos.each(item => console.log(item))
for (const item of todos) {
console.log(item)
}
Generator生成器函数
function * foo2 () {
console.log(111)
yield 100
console.log(222)
yield 200
console.log(333)
yield 300
}
const generator = foo2()
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
应用
function * createIdMaker () {
let id = 1
while (true) {
yield id++
}
}
const idMaker = createIdMaker()
console.log(idMaker.next().value)
const todoList = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '英语'],
work: ['喝茶'],
[Symbol.iterator]: function * () {
const all = [...this.life, ...this.learn, ...this.work]
for (const item of all) {
yield item
}
}
}
for (const item of todoList) {
console.log(item)
}
ECMAScript 2016
Array.prototype.includes
const arr = ['foo', 1, NaN, false]
console.log(arr.indexOf('foo')) // 0
console.log(arr.indexOf('bar')) // -1
console.log(arr.indexOf(NaN)) // -1
console.log(arr.includes('foo')) // true
console.log(arr.includes(NaN)) // true
指数运算符
console.log(Math.pow(2, 10));
console.log(2 ** 10)
ECMAScript 2017
const obj = {
foo: 'value1',
bar: 'value2'
}
// Object.values
console.log(Object.values(obj))
// Object.entries
console.log(Object.entries(obj))
for (const [key, value] of Object.entries(obj)) {
console.log(key, value)
}
console.log(new Map(Object.entries(obj)))
// Object.getOwnPropertyDescriptors
const p1 = {
firstName: 'Lei',
lastName: 'Wang',
get fullName() {
return this.firstName + ' ' + this.lastName
}
}
const p2 = Object.assign({}, p1)
p2.firstName = 'zce'
console.log(p2) // { firstName: 'zce', lastName: 'Wang', fullName: 'Lei Wang' }
const descriptors = Object.getOwnPropertyDescriptors(p1)
console.log(descriptors)
const p3 = Object.defineProperties({}, descriptors)
p3.firstName = 'zce'
console.log(p3) // { firstName: 'zce', lastName: 'Wang', fullName: [Getter] }
// String.prototype.padStart / String.prototype.padEnd
const books = {
html: 5,
css: 16,
javascript: 128
}
for (const [name, count] of Object.entries(books)) {
console.log(name, count)
}
for (const [name, count] of Object.entries(books)) {
console.log(`${name.padEnd(16, '-')}|${count.toString().padStart(3, '0')}`)
}