let
& const
var
、let
、const
区别:
let
声明的变量不被挂载到globalThis
上
var a = 'a'
let b = 'b'
window.a // 'a'
window.b // undefined
let
变量不可重复声明
var a = 'a'
var a = 'aa'
let b = 'b'
let b = 'bb' // SyntaxError: Identifier 'b' has already been declared
let
变量不会变量提升,而是暂时性死区
console.log(a) // undefined
var a = 'a'
console.log(a) // 'a'
// ------------------------------
console.log(b) // ReferenceError: b is not defined
let b = 'b'
let
可以定义块级作用域(详见:作用域与 this)
{
let a = 'a'
console.log(a) // 'a'
}
console.log(a) // ReferenceError: a is not defined
const
与let
相同,只是let
定义变量,const
定义常量
// 常量不可更改
const a = 'a'
a = 'aa' // TypeError: Assignment to constant variable.
// 声明了就要赋值
const b = 'b'
const c // SyntaxError: Missing initializer in const declaration
c = 'c'
ES6 之后请使用 let & const
,var
是历史包袱
更多内容详见:变量与函数声明
Array
数组遍历
ES5 中数组遍历有多少种方法?他们有什么优势和缺点?
for
循环
const arr = [1, 2, 3, 4, 5]
for (let i = 0; i < arr.length; i++) {
console.log(i, arr[i])
}
Array#forEach()
arr.forEach(function(item, index) {
console.log(index, item)
})
// 类似方法:every()、some()、map() 等
// 不支持 break 和 continue
// 但可以通过 return 配合 every()、some() 等实现相同效果
for-in
for-in
是为对象而设计的遍历,虽然能遍历数组(数组也是对象),但是有瑕疵
for (let index in arr) {
console.log(index, arr[index])
}
// 瑕疵 1
// 这里的 index 是字符串,不是数字
for (let index in arr) {
// if (index === 2) break // 没效果,因为不全等
if (index === '2') break // 而是这样
console.log(index, arr[index])
}
// 瑕疵 2
arr.a = 'a' // 数组是对象,所以可以这么写
// for-in 遍历会把 a 也遍历出来
结论:for-in
是遍历对象的,不要用来遍历数组
ES6 的 for-of
用来遍历可迭代对象,数组实现了迭代协议
for (let item of arr) {
console.log(item)
}
伪数组 → 数组
伪数组:集合(如:set
、getElementByTagName()
)、arguments
- 该对象按照索引存储数据
- 有
length
属性
ES5:
var args = [].slice.call(arguments) // collection
var imgs = [].slice.call(document.querySelectorAll('img')) // NodeList
ES6:
Array.prototype.from(arrayLike, mapFn, thisArg)
伪数组 → 数组:
const args = Array.from(arguments)
const imgs = Array.from(document.querySelectorAll('img'))
初始化数组:
// 1. 初始化长度为 5 的数组
let arr1 = Array(5)
// 2. 初始化长度为 5 的数组,并填充 1
let arr2 = Array.from({ length: 5 }, () => 1)
- 扩展运算符
const args = [...arguments]
const imgs = [...document.querySelectorAll('img')]
创建新数组
ES5:
// 1. 字面量
let arr1 = [1, 2, 3]
// 2. 构造函数
let arr2 = Array(5)
ES6:
// 1. Array.prototype.of(...args)
let arr1 = Array.of(1, 2, 3, 4, 5)
// 2. Array.prototype.fill(value, start, end) // 包头不包尾
let arr2 = Array(5).fill(1)
// 3. Array.prototype.from(arrayLike, mapFn, thisArg)
// 见上文
查找数组中的元素
ES5:
indexOf
const arr = [1, 2, 3]
// 如果找到,返回索引(第一个),找到第一个就不会往下找
// 如果没找到,返回 -1
if (arr.indexOf(4) === -1) {
console.log('没找到')
}
filter
// 本质是过滤,但也可以做查找 -> 判断 find.lenght 是否等于 0
// 但是效率不高 -> 即使查到了也会继续遍历
// 所以:filter 是过滤,不要用 filter 做查找
let find = arr.filter(item => item === 4)
console.log(find) // []
ES6:
// Array.prototype.find()
let find = arr.find(item => item === 2)
console.log(find) // 2
let find = arr.find(item => item === 4)
console.log(find) // undefined
// 返回的是找到的值(第一个),而不是索引
// 找到就返回了,不会继续遍历数组
// Array.prototype.findIndex(item):返回的是索引
Class
封装
ES5:
function Animal(type) {
// 属性
this.type = type
// 挂载在实例上的方法,不要这样写
// 应该写成实例方法(挂到原型上)
// 不然每个实例都有一个方法,无法复用,占用内存大
this.walk = function() {}
}
// 实例对象的方法
Animal.prototype.eat = function() {
console.log('i am eat food')
}
// 静态方法,不能访问 this
Animal.getClassName = () => 'Animal'
let dog = new Animal('dog')
// dog.constructor === Animal
// dog.__proto__ === Animal.prototype
/*
Animal {type: "dog"}
type: "dog"
walk: ƒ ()
__proto__:
eat: ƒ ()
constructor: ƒ Animal(type)
__proto__: Object
*/
ES6:
// 和 ES5 那样写一模一样,只是语法糖
class Animal {
constructor(type) {
this.type = type
// 挂载在实例上的方法,不要这样写
// this.walk = function() {}
}
// 挂载在实例上的方法,不要这样写
walk = () => {}
// 实例对象的方法
eat() {
console.log('i am eat food')
}
// 静态方法,不能访问 this
static getClassName() {
return 'Animal'
}
}
console.log(typeof Animal) // function
// ------------------------------------------------
// 属性保护 -> setter & getter
// ES5 很难做
class Animal {
_age = 0
// 相当于
// constructor() {
// this._age = 0
// }
get age() {
return this._age
}
set age(val) {
if (val > this._age) this._age = val
}
}
// 遗憾:没有权限修饰符————public、private
继承
ES5:
function Animal(type) {
this.type = type
}
function Dog() {
// 初始化父类的构造函数
Animal.call(this, 'dog')
}
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog
ES6:
class Animal {
constructor(type) {
this.type = type
}
eat() {
console.log('i am eat food')
}
}
class Dog extends Animal {
constructor() {
// 初始化父类的构造函数
super('dog')
}
// override
eat() {
console.log('dog eating')
}
}
多态
JS 是弱类型语言,多态体现没有强类型语言那么明显
详见:Java 中的多态
function
函数参数默认值
ES5:
function bar(a, b) {
if (b === undefined) b = 'b'
// 或
b = b || 'b'
}
ES6:
// 将有默认值的参数放到最后
// 参数默认值可以是表达式
function bar(a, b = 'b', c = a + b) {}
// 如果想使 b 保持默认值,改变 c 呢?
bar('a', undefined, 'd')
// 也就是说:这也是语法糖 -> if (b === undefined) b = 'b'
// 获取必传形参的个数(不包括有默认值的形参)
bar.length // 1
不确定参数
ES5:
function bar() {
// 函数内使用 -> 获取实参列表
arguments // 伪数组: [1, 2, 3]
}
bar(1, 2, 3)
// ----------------------------------------
const arr = [4, 5, 6]
bar(arr[0], arr[1], arr[2])
// 或
bar.apply(this, arr) // 这里的 this 是 window
ES6:
// ES6 已经弃用 arguments
function bar(a, ...args) {
console.log(a) // 1
console.log(args) // 数组: [2, 3]
}
bar(1, 2, 3)
// ----------------------------------------
const arr = [4, 5, 6]
bar(...arr) // 扩展运算符
箭头函数
- 不能作为构造函数,无原型对象和原型链
- 不能作为 Generator 函数
- 没有
arguments
- 没有自己的执行上下文 →
this
是外界的this
(所在词法作用域) →this
是写的时候决定的
// 语法
(参数列表) => {函数体}
// 有且仅有一个参数,() 可以省略
a => {}
// 函数体只有一条语句,{} 和 return 可以同时省略
a => a + 1
// return 的是对象,还要省略 -> 用 () 包裹
a => ({ a2: a*2 })
let test = {
name: 'test',
say: function() {
console.log(this.name)
}
}
test.say() // test
// -----------------------------
let test = {
name: 'test',
say: () => {
console.log(this.name, this) // this 指向 window
// 如果使用 webpack 构建,就会放入 eval 环境,那么 this 就是空对象
}
}
test.say() // undefined
Object
简写
const a = 'a'
const c = 'cc'
const obj = {
a: a,
b: function() {}
bar: function*() {} // generator 函数
}
obj[c] = 'c' // obj { cc: 'c' }
// 简写
const obj = {
a, // 属性简写
b() {}, // 方法简写
*bar() {} // generator 函数
[c]: 'c', // 动态 key 简写
// 甚至可以这样写
[a + c]: 'z', // obj { acc: 'z' }
}
对象拷贝
const target = {}
const source = { a: 1, b: 2 }
// 浅拷贝
Object.assign(target, source)
Set 数据结构
- 类比:
Array
,但性能更高 - 特点:
- 存储的值唯一(不重复)
- 有序:按照添加的顺序
WeakSet
:和Set
一样,但是存储的值是对象。弱引用,不影响 GC
const set = new Set()
const set2 = new Set([1, 2, 3]) // 不仅仅是数组,接收参数是可迭代对象
// 添加
set.add(1)
set.add(2).add(3) // 级联操作
// 删除
set.delete(1)
// 清空
set.clear()
// 查
set.has(1) // false
// 存了多少值
set2.size // 3
// 遍历
set2.keys() // SetIterator {1, 2, 3}
set2.values() // SetIterator {1, 2, 3}
set2.entries() // SetIterator {1 => 1, 2 => 2, 3 => 3}
set2.forEach(item => {})
// Set 是可迭代对象,可以用 for...of
for (let item of set2) {
console.log(item)
}
Map 数据结构
- 类比:
Object
,但性能更高 - 特点:
key
唯一(不重复)key
可以是任意值(函数、对象等);对象的值只能是 String 和 Symbol- 有序:按照添加的顺序
WeakMap
:和Map
一样,但是key
必须是对象。弱引用,不影响 GC
const map = new Map()
const map2 = new Map([[1, 2], [3, 4]]) // 接收可迭代对象,但迭代项要是 entry
console.log(map2) // Map(2) {1 => 2, 3 => 4}
// 添加 & 修改
map.set(key, value)
// 没有就添加
// 有就修改
// 取
map.get(key)
// 删除
map.delete(key)
// 清空
map.clear()
// 查
map.has(key) // true | false
// 存了多少值
map.size
// 遍历
// 返回的都是 MapIterator(可迭代对象)
map.keys()
map.values()
map.entries()
map.forEach((value, key) => {})
// Map 是可迭代对象,可以用 for...of
for (let [key, value] of map) {
console.log(key, value)
}
RegExp
y
修饰符
const str = 'aaa_aa_a'
const r1 = /a+/g
const r2 = /a+/y
r1.exec(str) // ["aaa", index: 0, input: "aaa_aa_a", groups: undefined]
r2.exec(str) // ["aaa", index: 0, input: "aaa_aa_a", groups: undefined]
r1.exec(str) // ["aa", index: 4, input: "aaa_aa_a", groups: undefined]
r2.exec(str) // null
// y 修饰符: 表示粘连(sticky)
// 第一次取 aaa
// 第二次从剩余部分(_aa_a)取
// _aa_a 的第一个字符不符合 /a+/,返回 null
u
修饰符
在正则中处理中文,参考:fileformat.info
// u 修饰符: unicode 编码
let s = '𠮷'
let s2 = '\uD842\uDFB7' // 即:𠮷
console.log(s === s2) // true
/^\uD842/.test(s2) // true: 不符合预期,\uD842\uDFB7 表示一个字不能拆开
/^\uD842/u.test(s2)) // false: 正确
/^.$/.test(s) // false: 错误:𠮷怎么不是任意一个字符?
/^.$/u.test(s) // true
/𠮷{2}/.test('𠮷𠮷') // false
/𠮷{2}/u.test('𠮷𠮷') // true
// 总结:要匹配中文字符就使用 u 修饰符
// --------------------------------------------------
// \u20BB7 是𠮷的码点
/\u{20BB7}/u.test(s) // true
/\u{61}/u.test('a') // true
/\u{61}/.test('a') // false
// 总结:使用 unicod 码点要用 u 修饰符
模板字符串
字符串拼接
const a = 20
const b = 10
const c = 'javascript'
// ES5
const str = 'my age is ' + (a + b) + ', i love' + c
// ES6
const str2 = `my age is ${a + b}, i love ${c}`
字符串换行
// ES5
const htmlStr = '\n\
<div>\n\
content\n\
</div>\n\
'
// ES6
const htmlStr = `
<div>
content
</div>
`
标签函数
// ES5
const retailPrice = 20
const wholeSalePrice = 16
const type = 'retail'
let showTxt
if (type === 'retail') {
showTxt = '您此次购买的单价是: ' + retailPrice
} else {
showTxt = '您此次购买的批发价是: ' + wholeSalePrice
}
console.log(showTxt) // 您此次购买的单价是: 20
// ---------------------------------------------
// ES6
function Price(strings, type) {
let s1 = strings[0]
const retailPrice = 20
const wholeSalePrice = 16
let txt
if (type === 'retait') {
txt = '单价是: ' + retailPrice
} else {
txt = '批发价是: ' + wholeSalePrice
}
return `${s1}${txt}`
}
let showTxt = Price`您此次购买的${'retail'}`
console.log(showTxt) // 您此次购买的单价是: 20
解构赋值
数组解构赋值
// ES5
const arr = ['hello', 'world']
const firstName = arr[0]
const surName = arr[1]
// ES6
const arr = ['hello', 'world']
const [firstName, surName] = arr
// 跳过某项
const [, surName] = arr
// ----------------
// 可迭代对象都可以这么解构
// 字符串
const str = 'abcd'
const [first, , third] = str
first // a
third // c
// -----------------
// 变量互换
let a = 1
let b = 2
[a, b] = [b, a]
// --------------
// 对对象属性赋值
let user = { name: 's', surname: 't' };
[user.name, user.surname] = [1, 2]
// ----------------
let user = { name: 's', surname: 't' }
for (let [k, v] of Object.entries(user)) {
// 隐式赋值
console.log(k, v)
}
// ---------------------------
let arr = []
let [a, b = 2, ...last] = arr
a // undefined
b // 2
last // []
对象解构赋值
let options = {
title: 'menu',
// h: 100,
width: 100,
height: 200
}
let { width: myWidth, height, h = 130 } = options
// -----------------------
let { title, ...last } = options
// -------------------------
// 非扁平数据
let options = {
size: {
width: 100,
height: 200
},
items: ['Cake', 'Donut']
}
let { size: { width }, items: [, item2] } = options
console.log(width) // 100
console.log(item2) // 'Donut'
console.log(size) // ReferenceError: size is not defined
Promise
Proxy
new Proxy()
const target = {
name: 'xiaoming',
price: 190
}
const handler = {
get(target, key) {
if (key === 'price') {
return Reflect.get(target, key) + 20
} else {
return Reflect.get(target, key)
}
},
set(target, key, value) {
return false // 只读
}
}
const p = new Proxy(target, handler)
console.log(p.price) // 210
p.name = 'tom' // 不成功
// --------------------------------------
// ES5 如何实现?
for (let [key] of Object.entries(target)) {
Object.defineProperty(target, key, {
writable: false // 不可写
})
}
// Proxy 和 defineProperty 可以实现相同的效果,但原理完全不同
// Proxy:代理对象
// defineProperty:完全锁死原对象(修饰符)
// ES6 后:优先考虑 Proxy
// 这也是 vue2 -> vue3 的一个底层改变
Proxy.revocable()
临时代理
const o = {
name: 'xiaoming',
price: 190
}
const d = Proxy.revocable(o, {
get (target, key) {
if (key === 'price') {
return target [key] + 20
} else {
return target[key]
}
}
})
console.log(d.proxy.price) // 210
setTimeout (() => d.revoke()}, 1000) // 取消代理
// d.proxy // 代理对象
// d.revoke() // 取消代理方法,调用即可取消代理
实际应用
数据校验:
- 保证对象结构
- 拦截非法输入
let o = {
name: 'xiaoming',
price: 190
}
let validator = new Proxy(o, {
get(target, key) {
return target[key] || '' // undefined -> ''
},
set(target, key, value) {
if (Reflect.has(target, key)) {
if (key === 'price' && value < 0) {
return false // 拦截非法输入
// throw new Error('price must be positive') // 或者抛出错误
}
Reflect.set(target, key, value)
} else {
return false // 保证对象结构
}
}
})
Reflect
详见:元编程
Generator 函数
更多内容详见:Generator 函数
Iterator
Iterable
:可迭代,是否有Symbol.iterator
属性iterator
:迭代器,返回值必须是这样的对象:
{
next() {
return {
value: xxx,
done: true | false
}
}
}
示例:
const authors = {
allAuthor: {
fiction: ['Agla', 'Skks', 'LP'],
scienceFiction: ['Neal', 'Arthru', 'Ribert'],
fantasy: ['V.R.Tole', 'J.M.R', 'Terry P.K']
},
address: ['shanghai']
}
// 手动编写 Iterator 返回的对象
/*
authors[Symbol.iterator] = function () {
// input: this, 即:authors
const allAuthor = this.allAuthor
let keys = Reflect.ownKeys(allAuthor)
let values = []
// output: return 的对象
return {
next() {
if (!values.length && keys.length) {
values = allAuthor[keys[0]]
keys.shift()
}
return {
done: !values.length,
value: values.shift()
}
}
}
}
*/
// 使用 Generator 函数
authors[Symbol.iterator] = function * () {
const allAuthor = this.allAuthor
let keys = Reflect.ownKeys(allAuthor)
let values = []
while (true) {
if (!values.length) {
if (keys.length) {
values = allAuthor[keys[0]]
keys.shift()
yield values.shift()
} else {
return false
}
} else {
yield values.shift()
}
}
}
for (let author of authors) {
console.log(author)
}
更多内容详见:迭代协议
ESModule
导出:
/* module1.js */
export default function () {}
// export const a = 'a'
// export function bar() {}
// 或
const a = 'a'
function bar() {}
export {
a,
bar
}
导入:
/* index.js */
// import module1, { a, bar as ber } from './module1'
// 或
import * as module1 from './module1'
// module1.default
// module1.a
// module1.bar()
// -----------------------------
// 导入同时就导出
// 用于聚集模块
export * from 'foo'
export { foo } from 'foo'
export { foo as bar } from 'foo'
export { foo as default } from 'foo'