const let var

var 存在声明提升,值为undefined;可以重复声明,声明后被覆盖

let 不存在声明提升,所以会存在暂时性死区;在同一个作用域内不能重复声明,声明的变量在所在的代码块内有效

const 定义常量,所以一旦声明就需要立即初始化,声明的变量在所在的代码块内有效,在同一个作用域内不能重复声明

js数据类型

  • 基本数据类型

    1. Number
    2. String
    3. Boolean
    4. Undefined
    5. null
    6. symbol
  • 引用类型(object)

    复杂数据类型统称为Object es6新添加有 Set Map

基本数据类型存储在栈中;引用类型的对象存储在堆中

当变量赋值,解析器首先要确认引用类型还是基本类型

  • 声明变量时不同的内存地址分配:
    • 简单类型的值存放在栈中,在栈中存放的是对应的值
    • 引用类型对应的值存储在堆中,在栈中存放的是指向堆内存的地址
  • 不同的类型数据导致赋值变量时的不同:
    • 简单类型赋值,是生成相同的值,两个对象对应不同的地址
    • 复杂类型赋值,是将保存对象的内存地址赋值给另一个变量。也就是两个变量指向堆内存中同一个对象

!!! es6新增基本数据类型Symbol

​ 因为对象属性的数据类型都是字符串,会导致属性名重复;symbol就是解决对象属性名重复,导致属性值被覆盖的问题

  • 唯一性

    1
    2
    3
    console.log(Symbol() === Symbol() )//false
    //类似于NaN
    console.log(NaN() === NaN() )//false
  • 不具备迭代器接口( !Symbo.iterator ) 不能用for in 或 for of 循环

    1
    2
    3
    4
    5
    6
    7
    var person = {
    name: "张三",
    age: 12,
    [Symbol('level')]: 'A'
    }
    //需要用Reflect.ownKeys() 才能获取到所有的key
    Reflect.ownKeys(person)
  • Symbol.for() 与 Symbol.keyFor()

    1
    2
    3
    let s1 = Symbol.for('foo')
    let s2 = Symbol.for('foo')
    s1 == s2 //true 创建Symbol.for('foo')会全局寻找是否之前创建过Symbol.for('foo'),如果有则直接使用以创建的没有则创建

数组常用方法

    • push() unshift() splice() concat()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//push()  接收任意数量参数,并添加到末尾,返回数组最新长度
let colors = []
let count = colors.push("red", "green")
console.log(count) //2

//unshift() 在数组开头添加任意多个值,然后返回新的数组长度
let colors = new Array
let count = colors.unshift("red", "green")
console.log(count) //2

// splice() 传入3个参数,分别是(开始位置, 要删除的元素数量, 插入的元素...) , 返回空数组
let colors = ["red", "green", "blue"]
let removed = colors.splice(1, 1, "yellow", "orange")
console.log(colors) // ["red", "yellow", "orange", "blue"]
console.log(removed) // []

//concat() 会先创建当前数组的副本, 然后把参数添加到副本末尾,最后返回新构建的数组,不会影响原始数组
let colors = ["red", "green", "blue"]
let removed = colors.concat("yellow", ["orange", "blue"])
console.log(colors) // ["red", "green", "blue"]
console.log(removed) // ["red", "green", "blue", "yellow", "orange", "blue"]
    • pop() shift() splice() slice()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//pop() 用于删除数组的最后一项,返回被删除项
let colors = ["red", "green"]
let item = colors.pop()
console.log(item) // "green"
console,log(colors, colors.length) // ["red"] 1

//shift() 用于删除数组第一项,同时返回删除项
let colors = ["red", "green"]
let item = colors.shift()
console.log(item) // "red"
console,log(colors, colors.length) // ["green"] 1

//spice() 传入两个参数,不传第三个参数,只删减不添加,返回空数组
let colors = ["red", "green", "blue"]
let removed = colors.splice(1, 1)
console.log(colors) // ["red", "blue"]
console.log(removed) // []

//slice() 用于创建一个原数组中包含的数组,生成系数组,不影响原数组
let colors = ["red", "green", "blue", "yellow", "purple"];
let colors2 = colors.slice(1);
let colors3 = colors.slice(1, 4);
let colors4 = colors.slice(-1);
let colors5 = colors.slice(-2);
let colors6 = colors.slice(1,-1);
console.log(colors) // ["red", "green", "blue", "yellow", "purple"]
concole.log(colors2); // ["green", "blue", "yellow", "purple"]
concole.log(colors3); // ["green", "blue", "yellow"]
concole.log(colors4); // ["purple"]
concole.log(colors5); // ["yellow", "purple"]
concole.log(colors6); // ["green", "blue", "yellow"]
    • splice() 删一个加一个,相当于改
1
2
3
4
5
6
7
8
9
// 下标
let arr = [1, 2, 3, 4, 5];
arr[2] = 10; // 将数组中索引为2的元素修改为10
console.log(arr); // [1, 2, 10, 4, 5]
// splice()
let colors = ["red", "green", "blue"];
let removed = colors.splice(1, 1, "red", "purple"); // 插入两个值,删除一个元素
console.log(colors); // red,red,purple,blue
console.log(removed); // green,只有一个元素的数组
    • indexOf() includes() find()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// indexOf() 返回查找元素位置,找到返回index,找不到返回 -1
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
numbers.indexOf(4) // 3

//includes() 返回查找元素位置,找到返回true, 找不到返回 false
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
numbers.includes(4) // true

/**
find(callback[, thisArg])
第一个参数是函数类似一个for循环 (item, index, array) => item > 10
element:数组中当前正在处理的元素
index:正在处理的元素在数组中的索引
array: 调用该方法的数组
将在调用 callbackFn 时用作 this 值*/
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
numbers.find((item, index, array) => item > 3) // 当callback返回true时,会返回当前元素item,后结束循环;如果提供 thisArg 參數予 find,其將會被當作 callback 每次被呼叫的 this。若是沒提供,則會使用 undefined (en-US)。
var inventory = [
{ name: "apples", quantity: 2 },
{ name: "bananas", quantity: 0 },
{ name: "cherries", quantity: 5 },
];

function isCherries(fruit) {
return fruit.name === "cherries";
}

console.log(inventory.find(isCherries));
// { name: 'cherries', quantity: 5 }
  • 排序
    • reverse() 反转 sort() 传入一个比较函数
1
2
3
4
5
6
7
8
9
10
11
12
13
function compare(value1, value2) {
if (value1 < value2) {
return -1; // value1 在前
} else if (value1 > value2) {
return 1; //value2 在前
} else {
return 0; //保持不变
}
}
let values = [0, 1, 5, 10, 15];
values.sort(compare);
console.log(values.sort())//[0,1,10,15,5]
console.log(values); // [0,1,5,10,15]
  • 转换方法

    • join() 方法接收一个参数,即字符串分隔符,返回包含所有项的字符串
    1
    2
    let array = [12,23,34]
    let arr = array.join("+") // "12+23+34"
  • 迭代方法

    • some()[ES6] every()[ES6] forEach() filter() map()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//some()	对数组每一项都运行传入的测试函数,如果至少有1个元素返回 true ,则这个方法返回 true
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let someResult = numbers.some((item, index, array) => item > 2);
console.log(someResult) // true

//every() 对数组每一项都运行传入的测试函数,如果所有元素都返回 true ,则这个方法返回 true
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let everyResult = numbers.every((item, index, array) => item > 2);
console.log(everyResult) // false

//forEach() 对数组每一项都运行传入的函数,没有返回值
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
numbers.forEach((item, index, array) => {
// 执行某些操作
});

//filter() 对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let filterResult = numbers.filter((item, index, array) => item > 2);
console.log(filterResult); // [3,4,5,4,3]

//map() 对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let mapResult = numbers.map((item, index, array) => item * 2);
console.log(mapResult) // [2,4,6,8,10,8,6,4,2]
  • ES6新增数组方法

    • Array构造函数新增方法:扩展运算符 Array.from() Array.of()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    //扩展运算符
    console.log(...[1, 2, 3]) // 1 2 3
    console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5
    [...'hello'] // [ "h", "e", "l", "l", "o" ]
    [...document.querySelectorAll('div')] // [<div>, <div>, <div>] 伪数组转成真数组
    /**
    伪数组:伪数组没有Array.prototype,它只是一个对象;伪数组的索引,就是那些键值对的key,没有真正的顺序可言;长度是手动设置的
    数组:数组有Array.prototype,他是对象的同时,也是数组;数组的索引和长度是内置属性
    */

    //Array.from() 将两类对象转为真正的数组:类似数组的对象和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)
    let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
    };
    let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
    //还可以接受第二个参数,用来对每个元素进行处理
    Array.from([1, 2, 3], (x) => x * x)// [1, 4, 9]

    //Array.of()
    /**
    没有参数的时候,返回一个空数组;
    将一组值,转换为数组*/
    Array.of() // []
    Array.of(3) // [3]
    Array.of(3, 11, 8) // [3,11,8]
    • 实例对象新增方法:copyWithin() find()、findIndex() fill() entries(),keys(),values() includes() flat(),flatMap()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    //fill() 	使用给定值,填充一个数组
    new Array(3).fill(7)// [7, 7, 7]
    //还可以带上参数,填充到指定位置
    ['a', 'b', 'c'].fill(7, 1, 2)// ['a', 7, 'c']

    //entries(),keys(),values()
    /**
    entries()是对键值对的遍历
    keys()是对键名的遍历
    values()是对键值的遍历*/
    for (let [index, item] of ['a', 'b'].entries()) {
    console.log(index, item);
    }
    // 0 "a"
    for (let index of ['a', 'b'].keys()) {
    console.log(index);
    }
    // 0
    // 1
    for (let item of ['a', 'b'].values()) {
    console.log(item);
    }
    // 'a'
    // 'b'

    //flat() 数组扁平化处理
    [1, 2, [3, [4, 5]]].flat(2)// [1, 2, 3, 4, 5]
    // flatMap()相当于 [[2, 4], [3, 6], [4, 8]].flat()
    [2, 3, 4].flatMap((x) => [x, x * 2]) //[2, 4, 3, 6, 4, 8]

数组incloudes方法

可以简便实现同一个属性的多种判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//正常判断用法
function printAnimals(animal){
if(animal === 'dog' || animal === 'cat' || animal === 'hamster'){
console.log(`has a animal ${anomal}`)
}
}

//使用incloudes方法,也能实现上面的功能
function printAnimals(animal){
const animals = ['dog', 'cat', 'hamster']
if(animals.includes(animal)){
console.log(animal)
}
}

printAnimals('dog')

字符串常用方法

  • 增 concat
1
2
3
4
5
//concat 用于将一个或多个字符串拼接成新字符串,不会改变原字符串
let stringValue = "hello "
let result = stringValue.concat("work")
console.log(result) // "hello work"
console.log(stringValue) // "hello "
  • 删 slice() substr() substring()
1
2
3
4
5
6
7
8
9
10
let stringValue = "hello word"
//slice() 传入一个参数,或两个参数;开始截取位置,与结束位置
console.log(stringValue.slice(3)); // "lo world"
console.log(stringValue.slice(3, 7)); // "lo w"
//substring() 传入一个参数,或两个参数;开始截取位置,与结束位置
console.log(stringValue.substring(3)); // "lo world"
console.log(stringValue.substring(3,7)); // "lo w"
//substr() 传入一个参数,或两个参数;开始截取位置,与切取数量
console.log(stringValue.substr(3)); // "lo world"
console.log(stringValue.substr(3, 7)); // "lo worl"
    • trim() trimLeft() trimRight()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let stringValue = "  hello world  "
    //trim() 删除前后打头的所有空格符,返回新字符串
    let trimStringValue = stringValue.trim()
    console.log(trimStringValue) // "hello word"
    //trimLeft() 删除前面打头所有空格,返回新字符串
    let trimLeftStringValue = stringValue.trimLeft()
    console.log(trimLeftStringValue) //“hello word ”
    //trimRight() 删除后面打头所有空格,返回新字符串
    let trimRightStringValue = stringValue.trimRight()
    console.log(trimRightStringValue) //" hello word"
    • repeat()
    1
    2
    3
    //repeat()	接收一个整数参数,表示要将字符串复制多少,然后返回拼接所有副本后的结果
    let strintValue = "nana "
    let copyResult = stringValue.repeat(2) //"nana nana "
    • padStart() podEnd()
    1
    2
    3
    4
    5
    6
    7
    let stringValue = "foo"
    //padStart() 复制字符串,如果小于指定长度,则在前面填充字符,直到长度满足,默认填充空格
    console.log(stringValue.padStart(6)) // " foo"
    console.log(stringValue.padStart(6"a")) // "aaafoo"
    //padEnd() 复制字符串,如果小于指定长度,则在后面填充字符,直到长度满足,默认填充空格
    console.log(stringValue.padEnd(6)) // "foo "
    console.log(stringValue.padEnd(6"a")) // "fooaaa"
    • toLowerCase() toUpperCase()
    1
    2
    3
    4
    5
    let stringValue = "hello word"
    // toLowerCase() 将字符串转成小写
    console.log(stringValue.toUpperCase()) //""hello word""
    // toUpperCase() 将字符串转成大写
    console.log(stringValue.toLowerCase()) //"HELLO WORD"
    • chatAt()
    1
    2
    3
    //chatAt()	返回给定索引位置的字符
    let stringValue = "hello word"
    console.log(stringValue.charAt(2)) //"l"
    • indexOf()
    1
    2
    3
    //indexOf()	从字符串开头去搜索传入的字符串,并返回索引位置(没有找到,则返回-1)
    let stringValue = "hello word"
    console.log(stringValue.indexOf("o")) //4
    • startsWith() includes()
    1
    2
    3
    4
    5
    6
    7
    8
    //从字符串中搜索传入字符串,并返回一个表示是否包含的返回布尔值
    let stringValue = "hello word"
    //startWith()
    console.log(stringValue.startsWith(llo)) //true
    console.log(stringValue.startsWith(oll)) //false
    //includes()
    console.log(stringValue.includes(oll)) //false
    console.log(stringValue.includes(llo)) //true
  • 转换成数组

    1
    2
    let stringValue = "12+13+14"
    let arr = str.split("+") //[12, 23, 34]
  • 模板匹配

    • match() search() replace()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    let text = "cat, bat, sat, fat"
    //match() 接收一个参数,可以是正则表达式,也可以是RexExp对象,返回数组
    let pattern = /.at/;
    let matches = text.match(pattern)
    console.log(matches[0]) // "cat" 只要遇到匹配的后续不会再进行匹配

    //search() 接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象,找到则返回匹配索引,否则返回 -1
    let pos = text.search(/at/)
    console.log(pos) //1

    //replace() 接收两个参数,第一个参数为匹配的内容,第二个参数为替换的元素(可用函数),返回替换后的字符串,只会更换第一个匹配的字符串
    let result = text.replace("at", "omd")
    console.log(result) //"comd, bat, sat, fat"
  • 字符串反转(应用)

  1. 利用扩展运算符变成数组(或字符串的split() 方法),对数组进行反转,在合并成字符串
1
2
3
4
5
let str = "123456"
let unstr = [...str].reverse().join("");//"654321"
let unstr = str.split("").reverse().join("");//"654321"
let unstr = Array.from(str).reverse().join("");//"654321"
//附加什么样的数据结构能够使用扩展运算符
  1. 利用循环

    1
    2
    3
    4
    5
    var res = [];
    for(i = str.length; i>=0; i--){
    res.push(i+1)
    };
    let unstr = res.join("") //"654321";

对象

对象便利 (对象不具备迭代器数据结构,不能使用for of 遍历)
  • 对象 for in 遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
let obj = {
UID: '561525',
nickName: '昵称',
acatar: 'https://a.jpg'
}
//遍历key
for(key in obj){
console.log(key)
} // UID nickName acatat
//通过key遍历value
for(key in obj){
console.log(obj[key])
} // 561525 昵称 https://a.jpg
  • 对象keys遍历
1
2
3
4
5
6
7
8
9
10
11
12
let obj = {
UID: '561525',
nickName: '昵称',
acatar: 'https://a.jpg'
}
let keys = Object.keys(obj)
//返回的是一个数组
console.log(keys) //["UID", "nickName", "acatat"]
//获取对象值,通过forEach()
keys.forEach(item=>{
console.log(`${item}:${obj[item]}`)
}) // UID:561525 nickName:昵称 acatat:https://a.jpg
  • 对象value遍历
1
2
3
4
5
6
7
8
let obj = {
UID: '561525',
nickName: '昵称',
acatar: 'https://a.jpg'
}
Object.values(obj).forEach(value=>{
console.log(value)
})// 561525 昵称 https://a.jpg
  • 对象getOwnPropertyNames 遍历
1
2
3
4
5
6
7
8
let obj = {
UID: '561525',
nickName: '昵称',
acatar: 'https://a.jpg'
}
Object.getOwnPropertyNames(obj).forEach(key=>{
console.log(key)
})// UID nickName acatat
  • 使用Reflect.ownKeys(obj) 遍历
1
2
3
4
5
6
7
8
let obj = {
UID: '561525',
nickName: '昵称',
acatar: 'https://a.jpg'
}
Reflect.ownKeys(obj).forEach(key=>{
console.log(key)
})// UID nickName acatat
  • 属性简写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const onj = {
foo, //value跟key一致时可以简写
method() { // 方法也可以省略 function()
return "Hello!";
}
}
//简写的对象方法不能用作构造函数,否则会报错
new obj.method()

//可以通过函数return {} 返回一个对象
function getPoint() {
const x = 1;
const y = 10;
return {x, y};
}
  • 属性名表达式
1
2
3
4
5
6
7
8
9
10
11
12
//sysbol  ES6 允许字面量定义对象时,将表达式放在括号内
//属性名表达式与简洁表示法,不能同时使用,会报错
const a = {
'first word': 'hello',
[lastWord]: 'world',
['h' + 'ello']() {
return 'hi';
}
};
a['first word'] // "hello"
a[lastWord] // "world"
a.hello() // hi
  • super关键字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象
const proto = {
foo: 'hello'
};

const obj = {
foo: 'world',
find() {
return super.foo;
}
};

Object.setPrototypeOf(obj, proto); // 为obj设置原型对象
obj.find() // "hello"
  • 扩展运算符的应用
1
2
3
4
5
6
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
//解构赋值是浅拷贝
//对象的扩展运算符等同于使用Object.assign()方法
  • ES6对象新增的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Object.is() //严格判断两个值是否相等,与 === 行为基本一致。
//不同点 1. +0不等于-0 2.NaN等于自身 (这两点与 === 相反)

Object.assign()
//Object.assign()方法用于对象的合并,将源对象source的所有可枚举属性,复制到目标对象target
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
Object.assign(target, source1);
target // {a:1, b:2, c:3}

Object.getOwnPropertyDescriptors()//返回指定对象所有自身属性(非继承属性)的描述对象

//Object.setPrototypeOf方法用来设置一个对象的原型对象
//Object.getPrototypeOf用于读取一个对象的原型对象
Object.setPrototypeOf(),Object.getPrototypeOf()

//遍历
Object.keys(),Object.values(),Object.entries()

//用于将一个键值对数组转为对象
Object.fromEntries()

//对象解构
let person = {
name: 'fafafa',
age: 23
}
let {name, age, play} = person
// name = 'fafafa' age = 23 play = undefined

map数据类型使用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var obj1 = {
name: '张三'
}
var obj2 = {
name: '李四'
}
var obj3 = {
[obj1]: '11',
[obj2]: '22'
// 对象key默认为字符串,如果传入的不是字符串而是对象,会将对象隐式转换为toString方法值也就是字符串,值为[Object Object] ,所以[obj2]: '22' 会覆盖[obj1]: '11' ,obj3打印只会打印{[Object Object]: '22'}
}
//而Map 数据类型就能够解决这个问题
var b =new Map().set(obj1, '123').set(obj2, '456')
console.log(b)
/**
Map(2) {
{ name: '张三' } => '123',
{ name: '李四' } => '456'
}
*/

对象解构赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
let person = {
name: '张三',
age: 20
}
//解构后的变量为解构后的变量名
let {name: name1, age: age1, hobby : hobby1} = person
let {name: name2, age : age2, hobby : hobby2 = '刷剧'} = person
console.log(name1, age1, hobby1)//张三 20 undefined
//当属性值为undefined, 我们就可以用 = 给默认值
console.log(name2, age2, hobby2) //张三 20 刷剧

//对象解构当遇到{}打头的参数前面已经声明过了需要加()或者开头打;
let x;
({x} = {x: 1})
;{x} = {x: 1}
//盲区 需要加;
({} = {truefalse});
({} = 123)
/**
拓展
JavaScript什么时候必须加分号;
①当一行代码是以 ( 开头的时候,则在前面补上一个分号用以避免一些语法解析错误。
②当一行代码是以 [ 开头的时候,则在前面补上一个分号用以避免一些语法解析错误
③当一行代码是以 ` 开头的时候,则在前面补上一个分号用以避免一些语法解析错误
JavaScript结尾时候必须加分号;
var name = 3
(function () {})()

// 由于没有分号,上面的会被解析为下面的语句,导致出现报错
var name = 3(function () {})()

*/

函数

ES6新增方法
  • 默认参数
1
2
3
4
function foo({x, y = 5} = {}) {
console.log(x, y);
}
foo() // undefined 5
  • 函数的length属性
1
2
3
4
5
6
7
8
9
10
//	1.length将返回没有指定默认值的参数个数
(function (a) {}).length // 1
(function (a = 5) {}).length // 0

// 2.rest 参数也不会计入length属性
(function(...args) {}).length // 0

// 3.设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了(只数默认参数前的参数数量)
(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
  • name属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//返回该函数的函数名
var f = function () {};
// ES5
f.name // ""
// ES6
f.name // "f"

//将一个具名函数赋值给一个变量,则 name属性都返回这个具名函数原本的名字
const bar = function baz() {};
bar.name // "baz"

//Function构造函数返回的函数实例,name属性的值为anonymous
(new Function).name // "anonymous"

//bind返回的函数,name属性值会加上bound前缀
function foo() {};
foo.bind({}).name // "bound foo"
(function(){}).bind({}).name // "bound "

ES6新增

Set

Set是es6新增的数据结构,类似于数组,但是成员的值都是唯一的,没有重复的值,我们一般称为集合

  • 增 添加某个值,返回 Set 结构本身
1
2
3
const s = new Set();
//add()
s.add(1).add(2).add(2); // 2只被添加了一次
  • 删 删除某个值,返回一个布尔值,表示删除是否成功
1
2
3
const s = new Set();
//delete()
s.delete(1) //true
  • 查 判断值是否存在
1
2
const s = new Set();
s.has(2) //true
  • clear() 清除所有成员,没有返回值
1
2
const s = new Set();
s.clear()
  • 遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let set = new Set(['red', 'green', 'blue']);
//keys():返回键名的遍历器
for (let item of set.keys()) {
console.log(item);
}
//values():返回键值的遍历器
for (let item of set.values()) {
console.log(item);
}
//entries():返回键值对的遍历器
for (let item of set.entries()) {
console.log(item);
}
//forEach():使用回调函数遍历每个成员
set.forEach((value, key) => console.log(key + ' : ' + value))
Map

Map类型是键值对的有序列表,而键和值都可以是任意类型

  • 增 设置键名key对应的键值为value,然后返回整个 Map 结构;可采用链式写法
1
2
const m = new Map()
m.set(1, 'a').set(2, 'b').set(3, 'c') // 链式操作
  • 删 delete方法删除某个键,返回true。如果删除失败,返回false
1
2
const m = new Map();
m.delete(undefined)
  • key已经有值,则键值会被更新,否则就新生成该键
1
2
3
4
const m = new Map()
m.set(1, 'a')
m.set(1, 'b')
m.get(1) // 'b'
  • has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中
1
2
3
const m = new Map();
m.set('edition', 6);
m.has('edition') // true
  • get get方法读取key对应的键值,如果找不到key,返回undefined
1
2
3
const m = new Map();
m.set(hello, 'Hello ES6!') // 键是函数
m.get(hello) // Hello ES6!
  • size属性
1
2
3
4
5
const map = new Map();
map.set('foo', true);
map.set('bar', false);

map.size // 2
  • clear clear方法清除所有成员,没有返回值
1
2
3
4
5
let map = new Map();
map.set('foo', true);
map.set('bar', false);
map.clear()
map.size // 0
  • 遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const map = new Map([['F', 'no'],['T',  'yes']]);
//keys():返回键名的遍历器
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"

//values():返回键值的遍历器
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"

//entries():返回所有成员的遍历器
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
//或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"

//forEach():遍历 Map 的所有成员
map.forEach(function(value, key, map) {
console.log("Key: %s, Value: %s", key, value);
});

ES6 之前, 如果对象属性是对象,则后面会覆盖前面的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj1 = {
name: 'aa'
}
var obj2 = {
name: 'bb'
}
var obj3 = {
[obj1]: '11',
[obj2]: '22'
}
//当属性用key存储时,如果key不是str,则会调用toString()方法把他变成str,就会变成[object Object],两个[object Object]一样就会覆盖前面的属性
// 可以用Map代替对象
console.log(obj3) // {[object Object]:22}
WeakSet 和 WeakMap
  • WeakSet

    • WeakSet可以接受一个具有 Iterable接口的对象作为参数

    • 没有遍历操作的API

    • 没有size属性

    • WeakSet只能成员只能是引用类型,而不能是其他类型的值;WeakSet里面的引用只要在外部消失,它在 WeakSet里面的引用就会自动消失

1
2
3
4
5
6
7
8
9
10
11
let ws=new WeakSet();

// 成员不是引用类型
let weakSet=new WeakSet([2,3]);
console.log(weakSet) // 报错

// 成员为引用类型
let obj1={name:1}
let obj2={name:1}
let ws=new WeakSet([obj1,obj2]);
console.log(ws) //WeakSet {{…}, {…}}
  • WeakMap

    • WeakMap结构与Map结构类似,也是用于生成键值对的集合;在APIWeakMapMap有两个区别:
      • 没有遍历操作的API
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 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只接受对象作为键名(null除外),不接受其他类型的值作为键名;WeakMap的键名所指向的对象,一旦不再需要,里面的键名对象和所对应的键值对会自动消失
    1
    2
    3
    4
    5
    6
    7
    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

箭头函数

  • 箭头函数this指向

  • 箭头函数不能当做构造函数

  • 箭头函数不可以当做迭代器

this指向(谁调用我我指向谁)

1
2
3
4
5
6
function get(content){
console.log(content)
}
//两种调用是一样,get()可以看作get.call()语法糖
get('调用函数get')
get.call(window, '调用函数get') // 浏览器为window,node环境中为global
1
2
3
4
5
6
7
8
var person = {
name: '张三',
run: function(time){
console.log(this.name+“在跑步,”+time+“分钟了”)
}
}
person.run(30)
person.run.call(person, 30 )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var name = 222;
var a = {
name: 111,
say: function(){
console.log(this.name)
}
}
var fun = a.say //赋值了a.say的方法地址给fun
fun() //可以看作fun.call(window) window是全局作用域 所以时222 在node运行环境下,值为undefined
a.say() //a.say.call(a) 所以时111


vat b = {
name: 333,
say: function(fn){
fn() //调用可以看作 fn.call(window)
}
}
b.say(a.say) //222 在node运行环境下,值为undefined
b.say = a.say //将a的say方法指向地址覆盖到b的say方法
b.say() //b.say.call(b) //333

改变this指向

箭头函数没有自己的this,this是外层代码块的this,this是在定义函数时绑定的。不能够使用做构造函数

1
2
3
4
5
6
7
8
var name = 11
var obj = {
name:22,
say: ()=>{
console.log(this.name)
}
}
obj.say() //不适用obj.say.call(obj),箭头函数this将指向它的外层 //11 //如果外层没有name,则为undefined

手写call、apply

作用:改变this指向

场景: js的继承(原型链继承;构造函数继承(使用call实现) )

  • call

    可以用作

    1. 继承
    2. 判断复杂数据类型

    image-20231115000045019

    1. 伪数组转换成数组

      image-20231115000349050

    2. 手写call方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    var person = {
    getName: function(){
    //以函数的形式调用对象方法则指向window(浏览器环境)|global(nodejs环境),以方法的形式调用时,this指向调用方法的对象
    return this.name
    }
    }
    var person1 = {
    name: ”张三“
    }

    //因为call方法是对象直接调用,有点类似是类的静态变量,每个对象方法都能调用,实现方法是直接挂在到Function构造函数的原型上。
    Function.prototype.myCall = function(context) {
    //这里面的this是调用该myCall方法的对象方法function
    //所以传入的参数第一个参数必须是function类型,不是function返回错误
    if(typeof this !== 'function'){
    throw new Error('error')
    }
    //如果参数为空则直接this指向window
    context = context || window
    //获取除第一个参数的其余参数
    var args = [...arguments].slice(1)
    //这里直接使用this,则直接指向getName,以函数的方式调用,所以getName的this指向window,获取到的name是全局变量。但是我们可以用context传入的参数来改变getName的this
    //首先是确定context传入的参数对象有getName方法,直接赋予getName方法
    context.fn = this
    //最后返回传入对象参数context.fn,也就是context.getName
    let result = contest.fn(...args)//对数组args解构
    delete context.fn
    return result
    }

    //实际实现方法就是将传入的对象赋予该对象调用的函数,然后再用传入进去的对象调用该函数,从而达到改变this指向
    person.getName.myCall(person1, 1, 2)
  • apply

​ 手写apply(其实与call方法一致,只是传入参数方式不同)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var person = {
getName: function(){
//以函数的形式调用对象方法则指向window(浏览器环境)|global(nodejs环境),以方法的形式调用时,this指向调用方法的对象
return this.name
}
}
var person1 = {
name: ”张三“
}

//因为call方法是对象直接调用,有点类似是类的静态变量,每个对象方法都能调用,实现方法是直接挂在到Function构造函数的原型上。
Function.prototype.myApply = function(context) {
//这里面的this是调用该myCall方法的对象方法function
//所以传入的参数第一个参数必须是function类型,不是function返回错误
if(typeof this !== 'function'){
throw new Error('error')
}
//如果参数为空则直接this指向window
context = context || window
//这里直接使用this,则直接指向getName,以函数的方式调用,所以getName的this指向window,获取到的name是全局变量。但是我们可以用context传入的参数来改变getName的this
//首先是确定context传入的参数对象有getName方法,直接赋予getName方法
context.fn = this
//判断是否传入了数组
let result
if(arguments[1]){
result = contest.fn(...arguments[1])
}else{
result = contest.fn()
}
delete context.fn
return result
}

//实际实现方法就是将传入的对象赋予该对象调用的函数,然后再用传入进去的对象调用该函数,从而达到改变this指向
person.getName.myApply(person1, [1, 2])

js事件循环机制;宏任务与微任务 事件队列

  • js语言特点

    • 单线程
    • 解释性语言(解释一行执行一行)
  • event-loop - 事件循环机制

    • 事件循环机制 是基于事件驱动的,主要涉及执行栈、事件队列、宏任务和微任务。

      • 执行栈(Call Stack): 用于存储执行上下文(函数调用)的栈结构。当函数被调用时,会被推入执行栈,执行完毕后会被弹出。如果执行栈为空,则表示当前代码执行完毕。
      • 事件队列(Event Queue): 用于存储待执行的任务,分为宏任务队列和微任务队列。宏任务和微任务会被按顺序放入对应的队列中等待执行。
      • 事件队列 - 宏任务(Macrotask): 包括由浏览器或Node.js提供的异步API,如setTimeout、setInterval、I/O操作等。宏任务会被放入宏任务队列中等待执行。
      • 事件队列 - 微任务(Microtask): 包括由JavaScript引擎自身提供的异步API,如Promise、process.nextTick等。微任务会被放入微任务队列中等待执行。
    • 工作流程

      • 当代码开始执行时,首先会执行同步任务,将同步任务按顺序推入执行栈。
      • 遇到异步任务时,会将其放入对应的事件队列中。
      • 当执行栈为空时,事件循环会从微任务队列中取出所有微任务依次执行,直到微任务队列为空。
      • 如果在执行微任务的过程中,又产生了新的微任务,会继续执行微任务队列中的任务,直到微任务队列为空。
      • 当微任务队列为空时,事件循环会从宏任务队列中取出一个宏任务执行。
      • 执行完当前宏任务后,再次检查微任务队列,重复上述步骤。
      • 这个过程会一直循环执行,直到执行栈和事件队列中都没有任务。

微任务的执行优先级高于宏任务,也就是说,当宏任务队列和微任务队列中都有任务时,JS引擎会先执行微任务队列中的所有任务,再去执行宏任务队列中的任务。

事件循环机制

回调地狱优化

  • 利用es11 的可选链式操作符
1
2
3
4
5
6
var Animal = ({type, name, gender})=>{
//!animal 判断animal是否为假值(包括undefined、null、false、0、''等),如果是,则返回'no animal'。
//: !type ? 'type' 如果animal存在,判断type是否为假值,如果是,则返回'type'。
//: !name ? 'name' 如果type存在,判断name是否为假值,如果是,则返回'name'。
return !animal ? 'no animal' : !type ? 'type' : !name ? 'name' : gerder ? `${name}is a${gender}-${type}`
}
  • 利用提前退出和提前返回
1
2
3
4
5
6
7
var Animal = ({type, name, gender})=>{
if(!type) return 'no type'
if(!name) return 'no name'
if(!gender) return 'no gender'
//因为js解释性语言, 能往下执行便是没有报错,当上面条件都通过时返回数据
return `${name}is a${gender}-${type}`
}

*

展开运算符

  • […arr]

  • 支持展开运算符是要有Symbol.iterator

    arr[Symbol.iterator]

面试题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//我们能否以某种方式为下面的语句使用展开运算而不导致类型错误 ?
//错误代码示例
let obj = {x: 1, y: 2, z: 3}
console.log([...obj]) //报错 根本原因是因为对象是不具备迭代器属性的数据结构

//正确代码
obj[Symbol.iterator] = function(){
return {
next: function() {
let objArr = Reflect.ownKeys(obj)
if(this.index < objArr.length -1){
let key = objArr[this.index]
this.index++
return{
value: obj[key]
}
}else{
return {done: true}
}
},
index: 0
}
}
console.log([...obj]) // [1, 2, 3]

// 也可以在Object 构造函数的原型上添加,为所有对象提供迭代器
Object.prototype[Symbol.iterator] = function(){

}

//ES7 也提出了新的写法
//console.log({...objj}) 会涉及到对象的拷贝操作,相当于对obj进行了浅拷贝
console.log({...obj}) // {x: 1, y: 2, z: 3}


  • 对象转数组案例
  1. 使用 Object.keys() 方法获取对象的所有键,然后使用 map() 方法将每个键对应的值存入新数组中。
1
2
3
const obj = { a: 1, b: 2, c: 3 };
const arr = Object.keys(obj).map(key => obj[key]);
console.log(arr); // [1, 2, 3]
  1. 使用 Object.values() 方法获取对象的所有值,直接将值存入新数组中。
1
2
3
const obj = { a: 1, b: 2, c: 3 };
const arr = Object.values(obj);
console.log(arr); // [1, 2, 3]
  1. 使用 Object.entries() 方法获取对象的键值对数组,然后对每个键值对进行处理。
1
2
3
const obj = { a: 1, b: 2, c: 3 };
const arr = Object.entries(obj).map(([key, value]) => value);
console.log(arr); // [1, 2, 3]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#### 高效运算符  '?.' 和 '??' 和 '??='  的区别

* 可选链运算符(?.)

可选链运算符, 允许读取位于连接对象深处的属性值,而不必明确验证链式中的每个引用是否有效。

使用场景:当我们不确定一个对象是否存在时,调用对象属性,可以使用该操作符

* 空值合并运算符(??)

空值合并运算符,是一个逻辑运算符,当左侧的操作为null或undefined时,返回其右侧操作数,否则返回左侧操作数

有点类似于 ||(逻辑或) ,与 || 区别在于 ??. 对0和 " 会判断为真

* 逻辑空赋值(??=)

逻辑空赋值运算符(X ?? = Y) 仅在X是空值(null 或undefined) 时对其赋值。

使用场景:当你需要通过if判断某个变量不存在时,才需要为该变量赋值时使用

#### 原型和原型链

1. **原型(Prototype)**:每个对象都有一个原型对象,可以通过`__proto__`属性来访问。原型对象可以包含共享的属性和方法,可以被对象实例共享。
2. **原型链(Prototype Chain)**:当访问对象的属性或方法时,如果对象本身没有该属性或方法,JavaScript引擎会顺着原型链向上查找,直到找到对应的属性或方法或者到达原型链的顶端。

​~~~ js
// 定义了一个构造函数Person,并在其原型对象上定义了一个方法greet。创建了一个Person对象实例person1,并演示了访问属性和方法以及原型链的关系
function Person(name, age) {
this.name = name;
this.age = age;
}

// 在Person的原型对象上定义一个方法
Person.prototype.greet = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}

// 创建一个Person对象实例
let person1 = new Person('Alice', 30);

// 访问实例对象的属性和方法
console.log(person1.name); // Output: Alice
person1.greet(); // Output: Hello, my name is Alice and I am 30 years old.

// 演示原型链 实例的__proto__ === 其构造函数的prototype
console.log(person1.__proto__ === Person.prototype); // Output: true
// 构造函数的原型Object
console.log(Person.prototype.__proto__ === Object.prototype); // Output: true
// Object的原型为null
console.log(Object.prototype.__proto__); // Output: null
  • 其他

    • 继承:通过原型链,我们可以实现对象之间的继承关系,子对象可以继承父对象的属性和方法。可以尝试创建一个新的构造函数,让它继承自Person构造函数,并添加新的属性或方法。
    • 原型修改:可以尝试修改Person构造函数的原型对象上的方法,然后看看实例对象是否能够访问到修改后的方法。
    • 原型链终止:在原型链的顶端是Object.prototype,它的原型是null。可以思考一下为什么原型链的顶端是Object.prototype,而不是其他对象。

ES6 - ES11

ES6

怎么理解ES6 中的Promise;使用场景
  • 介绍
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
promise解决异步操作的优点:
1.链式操作减低了编码难度
2.代码可读性明显增强 */

//未使用promise
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('得到最终结果: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);

//使用promise
doSomething()
.then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('得到最终结果: ' + finalResult);
})
.catch(failureCallback);
  • 状态
1
2
3
4
promise 有3种状态
1.pending (进行中)
2.fulfilled (已成功)
3.rejected (已失败)
  • 特点
1
2
1.对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态
2.一旦状态改变(从pending变为fulfilled和从pending变为rejected),就不会再变,任何时候都可以得到这个结果
  • 流程

image-20231126211227519

  • 用法
1
2
3
4
5
6
const promise = new Promise(function(resolve, reject) {});

/**
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”
reject函数的作用是,将Promise对象的状态从“未完成”变为“失败” */
  • 实例方法

    • Promise构建出来的实例存在以下方法:

      • then()
      1
      2
      3
      4
      5
      6
      7
      8
      /**
      then()是实例状态发生改变时的回调函数,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数
      then方法返回的是一个新的Promise实例,也就是promise能链式书写的原因 */
      getJSON("/posts.json").then(function(json) {
      return json.post;
      }).then(function(post) {
      // ...
      });
      • catch()
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      /**
      catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数 */
      getJSON('/posts.json').then(function(posts) {
      // ...
      }).catch(function(error) {
      // 处理 getJSON 和 前一个回调函数运行时发生的错误
      console.log('发生错误!', error);
      });

      //Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止
      getJSON('/post/1.json').then(function(post) {
      return getJSON(post.commentURL);
      }).then(function(comments) {
      // some code
      }).catch(function(error) {
      // 处理前面三个Promise产生的错误
      });

      /**
      一般来说,使用catch方法代替then()第二个参数
      Promise对象抛出的错误不会传递到外层代码,即不会有任何反应 */
      • finally()
      1
      2
      3
      4
      5
      //finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
      promise
      .then(result => {···})
      .catch(error => {···})
      .finally(() => {···});
  • 构造函数方法

    • all()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //Promise.all()方法用于将多个 Promise实例,包装成一个新的 Promise实例
    const p1 = new Promise((resolve, reject) => {
    resolve('hello');
    })
    .then(result => result)
    .catch(e => e);

    const p2 = new Promise((resolve, reject) => {
    throw new Error('报错了');
    })
    .then(result => result)
    .catch(e => e);

    Promise.all([p1, p2])
    .then(result => console.log(result))
    .catch(e => console.log(e));
    //如果p2没有自己的catch方法,就会调用Promise.all()的catch方法
    • race()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //将多个 Promise 实例,包装成一个新的 Promise 实例;只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变
    const p = Promise.race([
    fetch('/resource-that-may-take-a-while'),
    new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
    })
    ]);

    p
    .then(console.log)
    .catch(console.error);
    • allSettled()
    1
    2
    //Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例
    //只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束
    • resolve()
    1
    2
    3
    4
    //将现有对象转为 Promise对象
    Promise.resolve('foo')
    // 等价于
    new Promise(resolve => resolve('foo'))
    • reject()
    • try()
  • 使用场景

    1. 将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化
    1
    2
    3
    4
    5
    6
    7
    8
    const preloadImage = function (path) {
    return new Promise(function (resolve, reject) {
    const image = new Image();
    image.onload = resolve;
    image.onerror = reject;
    image.src = path;
    });
    };
    1. 通过链式操作,将多个渲染数据分别给个then,让其各司其职。或当下个异步请求依赖上个请求结果的时候,我们也能够通过链式操作友好解决问题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    / 各司其职
    getInfo().then(res=>{
    let { bannerList } = res
    //渲染轮播图
    console.log(bannerList)
    return res
    }).then(res=>{

    let { storeList } = res
    //渲染店铺列表
    console.log(storeList)
    return res
    }).then(res=>{
    let { categoryList } = res
    console.log(categoryList)
    //渲染分类列表
    return res
    })
    1. 通过all()实现多个请求合并在一起,汇总所有请求结果,只需设置一个loading即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function initLoad(){
    loading.show() //加载loading
    Promise.all([getBannerList(),getStoreList(),getCategoryList()]).then(res=>{
    console.log(res)
    loading.hide() //关闭loading
    }).catch(err=>{
    console.log(err)
    loading.hide()//关闭loading
    })
    }
    //数据初始化
    initLoad()
    1. 通过race可以设置图片请求超时
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    //请求某个图片资源
    function requestImg(){
    var p = new Promise(function(resolve, reject){
    var img = new Image();
    img.onload = function(){
    resolve(img);
    }
    //img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg"; 正确的
    img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg1";
    });
    return p;
    }

    //延时函数,用于给请求计时
    function timeout(){
    var p = new Promise(function(resolve, reject){
    setTimeout(function(){
    reject('图片请求超时');
    }, 5000);
    });
    return p;
    }

    Promise
    .race([requestImg(), timeout()])
    .then(function(results){
    console.log(results);
    })
    .catch(function(reason){
    console.log(reason);
    });
如何理解ES6中的Porxy; 使用场景
  • 介绍
1
Proxy 亦是如此,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
  • 用法

    1. Proxy为 构造函数,用来生成 Proxy实例
    1
    2
    3
    4
    5
    6
    var proxy = new Proxy(target, handler)
    /**
    参数
    target表示所要拦截的目标对象(任何类型的对象,包括原生数组,函数,甚至另一个代理))
    handler通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为
    */
    1. 参数 handler 解析
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    关于handler拦截属性,有如下:

    get(target,propKey,receiver):拦截对象属性的读取
    set(target,propKey,value,receiver):拦截对象属性的设置
    has(target,propKey):拦截propKey in proxy的操作,返回一个布尔值
    deleteProperty(target,propKey):拦截delete proxy[propKey]的操作,返回一个布尔值
    ownKeys(target):拦截Object.keys(proxy)、for...in等循环,返回一个数组
    getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象
    defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc),返回一个布尔值
    preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值
    getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象
    isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值
    setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值
    apply(target, object, args):拦截 Proxy 实例作为函数调用的操作
    construct(target, args):拦截 Proxy 实例作为构造函数调用的操作
    1. Reflect
    1
    2
    3
    4
    5
    6
    若需要在Proxy内部调用对象的默认行为,建议使用Reflect,其是ES6中操作对象而提供的新 API

    基本特点:
    只要Proxy对象具有的代理方法,Reflect对象全部具有,以静态方法的形式存在
    修改某些Object方法的返回结果,让其变得更合理(定义不存在属性行为的时候不报错而是返回false
    Object操作都变成函数行为
    1. 一些参数用法

      • get() get接受三个参数,依次为目标对象、属性名和 proxy 实例本身,最后一个参数可选
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      var person = {
      name: "张三"
      };
      var proxy = new Proxy(person, {
      get: function(target, propKey) {
      return Reflect.get(target,propKey)
      }
      });
      proxy.name // "张三"
      //get能够对数组增删改查进行拦截,下面是试下你数组读取负数的索引
      function createArray(...elements) {
      let handler = {
      get(target, propKey, receiver) {
      let index = Number(propKey);
      if (index < 0) {
      propKey = String(target.length + index);
      }
      return Reflect.get(target, propKey, receiver);
      }
      };
      let target = [];
      target.push(...elements);
      return new Proxy(target, handler);
      }
      let arr = createArray('a', 'b', 'c');
      arr[-1] // c
      //如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修改该属性,否则会报错
      const target = Object.defineProperties({}, {
      foo: {
      value: 123,
      writable: false,
      configurable: false
      },
      });

      const handler = {
      get(target, propKey) {
      return 'abc';
      }
      };

      const proxy = new Proxy(target, handler);

      proxy.foo
      // TypeError: Invariant check failed
      • set() 拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      let validator = {
      set: function(obj, prop, value) {
      if (prop === 'age') {
      if (!Number.isInteger(value)) {
      throw new TypeError('The age is not an integer');
      }
      if (value > 200) {
      throw new RangeError('The age seems invalid');
      }
      }

      // 对于满足条件的 age 属性以及其他属性,直接保存
      obj[prop] = value;
      }
      };

      let person = new Proxy({}, validator);
      person.age = 100;
      person.age // 100
      person.age = 'young' // 报错
      person.age = 300 // 报错


      //如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用
      const obj = {};
      Object.defineProperty(obj, 'foo', {
      value: 'bar',
      writable: false,
      });

      const handler = {
      set: function(obj, prop, value, receiver) {
      obj[prop] = 'baz';
      }
      };

      const proxy = new Proxy(obj, handler);
      proxy.foo = 'baz';
      proxy.foo // "bar"


      //严格模式下,set代理如果没有返回true,就会报错
      'use strict';
      const handler = {
      set: function(obj, prop, value, receiver) {
      obj[prop] = receiver;
      // 无论有没有下面这一行,都会报错
      return false;
      }
      };
      const proxy = new Proxy({}, handler);
      proxy.foo = 'bar';
      // TypeError: 'set' on proxy: trap returned falsish for property 'foo'
      • deleteProperty() deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      var handler = {
      deleteProperty (target, key) {
      invariant(key, 'delete');
      Reflect.deleteProperty(target,key)
      return true;
      }
      };
      function invariant (key, action) {
      if (key[0] === '_') {
      throw new Error(`无法删除私有属性`);
      }
      }

      var target = { _prop: 'foo' };
      var proxy = new Proxy(target, handler);
      delete proxy._prop
      // Error: 无法删除私有属性

      //目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错
      • 取消代理
      1
      Proxy.revocable(target, handler);
  • 使用场景

    Proxy其功能非常类似于设计模式中的代理模式,常用功能如下:

    • 拦截和监视外部对对象的访问
    • 降低函数或类的复杂度
    • 在复杂操作前对操作进行校验或对所需资源进行管理
    1. 使用 Proxy 保障数据类型的准确性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    let numericDataStore = { count: 0, amount: 1234, total: 14 };
    numericDataStore = new Proxy(numericDataStore, {
    set(target, key, value, proxy) {
    if (typeof value !== 'number') {
    throw Error("属性只能是number类型");
    }
    return Reflect.set(target, key, value, proxy);
    }
    });

    numericDataStore.count = "foo"
    // Error: 属性只能是number类型

    numericDataStore.count = 333
    // 赋值成功
    1. 声明了一个私有的 apiKey,便于 api 这个对象内部的方法调用,但不希望从外部也能够访问 api._apiKey
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    let api = {
    _apiKey: '123abc456def',
    getUsers: function(){ },
    getUser: function(userId){ },
    setUser: function(userId, config){ }
    };
    const RESTRICTED = ['_apiKey'];
    api = new Proxy(api, {
    get(target, key, proxy) {
    if(RESTRICTED.indexOf(key) > -1) {
    throw Error(`${key} 不可访问.`);
    } return Reflect.get(target, key, proxy);
    },
    set(target, key, value, proxy) {
    if(RESTRICTED.indexOf(key) > -1) {
    throw Error(`${key} 不可修改`);
    } return Reflect.get(target, key, value, proxy);
    }
    });

    console.log(api._apiKey)
    api._apiKey = '987654321'
    // 上述都抛出错误
    1. 观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行; observable函数返回一个原始对象的 Proxy 代理,拦截赋值操作,触发充当观察者的各个函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const queuedObservers = new Set();

    const observe = fn => queuedObservers.add(fn);
    const observable = obj => new Proxy(obj, {set});

    function set(target, key, value, receiver) {
    const result = Reflect.set(target, key, value, receiver);
    queuedObservers.forEach(observer => observer());
    return result;
    }
    //观察者函数都放进Set集合,当修改obj的值,在会set函数中拦截,自动执行Set所有的观察者
怎么理解ES6中Generator;使用场景
  • 介绍

    • 执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态

      形式上,Generator函数是一个普通函数,但是有两个特征:

      • function关键字与函数名之间有一个星号
      • 函数体内部使用yield表达式,定义不同的内部状态
    1
    2
    3
    4
    5
    function* helloWorldGenerator() {
    yield 'hello';
    yield 'world';
    return 'ending';
    }
  • 使用

    • Generator 函数会返回一个遍历器对象,即具有Symbol.iterator属性,并且返回给自己
    1
    2
    3
    4
    5
    6
    7
    8
    function* gen(){
    // some code
    }

    var g = gen();

    g[Symbol.iterator]() === g
    // true
    • 通过yield关键字可以暂停generator函数返回的遍历器对象的状态
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    function* helloWorldGenerator() {
    yield 'hello';
    yield 'world';
    return 'ending';
    }
    var hw = helloWorldGenerator();

    /**
    上述存在三个状态:hello、world、return
    通过next方法才会遍历到下一个内部状态,其运行逻辑如下:
    1.遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
    2.下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式
    3.如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
    4.如果该函数没有return语句,则返回的对象的value属性值为undefined
    */
    hw.next()
    // { value: 'hello', done: false }
    hw.next()
    // { value: 'world', done: false }
    hw.next()
    // { value: 'ending', done: true }
    hw.next()
    // { value: undefined, done: true }

    /**
    done用来判断是否存在下个状态,value对应状态值
    yield表达式本身没有返回值,或者说总是返回undefined
    通过调用next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
    */
    function* foo(x) {
    var y = 2 * (yield (x + 1));
    var z = yield (y / 3);
    return (x + y + z);
    }

    var a = foo(5);
    a.next() // Object{value:6, done:false}
    a.next() // Object{value:NaN, done:false}
    a.next() // Object{value:NaN, done:true}

    var b = foo(5);
    b.next() // { value:6, done:false }
    b.next(12) // { value:8, done:false }
    b.next(13) // { value:42, done:true }

    //正因为Generator函数返回Iterator对象,因此我们还可以通过for...of进行遍历
    function* foo() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    yield 5;
    return 6;
    }

    for (let v of foo()) {
    console.log(v);
    }
    // 1 2 3 4 5

    //原生对象没有遍历接口,通过Generator函数为它加上这个接口,就能使用for...of进行遍历了
    function* objectEntries(obj) {
    let propKeys = Reflect.ownKeys(obj);

    for (let propKey of propKeys) {
    yield [propKey, obj[propKey]];
    }
    }

    let jane = { first: 'Jane', last: 'Doe' };

    for (let [key, value] of objectEntries(jane)) {
    console.log(`${key}: ${value}`);
    }
    // first: Jane
    // last: Doe
  • 异步解决方案

    • 回调函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,再调用这个函数
    fs.readFile('/etc/fstab', function (err, data) {
    if (err) throw err;
    console.log(data);
    fs.readFile('/etc/shells', function (err, data) {
    if (err) throw err;
    console.log(data);
    });
    });
    //readFile函数的第三个参数,就是回调函数,等到操作系统返回了/etc/passwd这个文件以后,回调函数才会执行
    • Promise 对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //Promise就是为了解决回调地狱而产生的,将回调函数的嵌套,改成链式调用
    const fs = require('fs');

    const readFile = function (fileName) {
    return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
    if (error) return reject(error);
    resolve(data);
    });
    });
    };
    readFile('/etc/fstab').then(data =>{
    console.log(data)
    return readFile('/etc/shells')
    }).then(data => {
    console.log(data)
    })
    //这种链式操作形式,使异步任务的两段执行更清楚了,但是也存在了很明显的问题,代码变得冗杂了,语义化并不强
    • generator 函数
    1
    2
    3
    4
    5
    6
    7
    //yield表达式可以暂停函数执行,next方法用于恢复函数执行,这使得Generator函数非常适合将异步任务同步化
    const gen = function* () {
    const f1 = yield readFile('/etc/fstab');
    const f2 = yield readFile('/etc/shells');
    console.log(f1.toString());
    console.log(f2.toString());
    };
    • async/await
    1
    2
    3
    4
    5
    6
    7
    //将上面Generator函数改成async/await形式,更为简洁,语义化更强了
    const asyncReadFile = async function () {
    const f1 = await readFile('/etc/fstab');
    const f2 = await readFile('/etc/shells');
    console.log(f1.toString());
    console.log(f2.toString());
    };
  • 区别:

1
2
3
4
5
6
7
通过上述代码进行分析,将promise、Generator、async/await进行比较:
1.promise和async/await是专门用于处理异步操作的
2.Generator并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator接口...)
3.promise编写代码相比Generator、async更为复杂化,且可读性也稍差
4.Generator、async需要与promise对象搭配处理异步情况
5.async实质是Generator的语法糖,相当于会自动执行Generator函数
6.async使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案
  • 使用场景

    1. Generator是异步解决的一种方案,最大特点则是将异步操作同步化表达出来
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function* loadUI() {
    showLoadingScreen();
    yield loadUIDataAsynchronously();
    hideLoadingScreen();
    }
    var loader = loadUI();
    // 加载UI
    loader.next()

    // 卸载UI
    loader.next()
    1. 包括redux-saga中间件也充分利用了Generator特性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
    import Api from '...'

    function* fetchUser(action) {
    try {
    const user = yield call(Api.fetchUser, action.payload.userId);
    yield put({type: "USER_FETCH_SUCCEEDED", user: user});
    } catch (e) {
    yield put({type: "USER_FETCH_FAILED", message: e.message});
    }
    }

    function* mySaga() {
    yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
    }

    function* mySaga() {
    yield takeLatest("USER_FETCH_REQUESTED", fetchUser);
    }

    export default mySaga;
    1. 还能利用Generator函数,在对象上实现Iterator接口
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function* iterEntries(obj) {
    let keys = Object.keys(obj);
    for (let i=0; i < keys.length; i++) {
    let key = keys[i];
    yield [key, obj[key]];
    }
    }

    let myObj = { foo: 3, bar: 7 };

    for (let [key, value] of iterEntries(myObj)) {
    console.log(key, value);
    }

    // foo 3
    // bar 7
怎么理解ES6中Module; 使用场景
  • 介绍

    模块,(Module),是能够单独命名并独立地完成一定功能的程序语句的集合(即程序代码和数据结构的集合体)

    两个基本的特征:外部特征和内部特征

    • 外部特征是指模块跟外部环境联系的接口(即其他模块或程序调用该模块的方式,包括有输入输出参数、引用的全局变量)和模块的功能
    • 内部特征是指模块的内部环境具有的特点(即该模块的局部数据和程序代码

    为什么需要模块化

    • 代码抽象
    • 代码封装
    • 代码复用
    • 依赖管理
  • 使用

    ES6模块内部自动采用了严格模式,这里就不展开严格模式的限制,毕竟这是ES5之前就已经规定好

    模块功能主要由两个命令构成:

    • export:用于规定模块的对外接口
    • import:用于输入其他模块提供的功能
    1. export
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // profile.js
    export var firstName = 'Michael';
    export var lastName = 'Jackson';
    export var year = 1958;
    //或
    // 建议使用下面写法,这样能瞬间确定输出了哪些变量
    var firstName = 'Michael';
    var lastName = 'Jackson';
    var year = 1958;
    export { firstName, lastName, year };

    //输出函数或类
    export function multiply(x, y) {
    return x * y;
    };

    //通过as可以进行输出变量的重命名
    function v1() { ... }
    function v2() { ... }
    export {
    v1 as streamV1,
    v2 as streamV2,
    v2 as streamLatestVersion
    };
    1. import
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    //使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块
    // main.js
    import { firstName, lastName, year } from './profile.js';

    function setName(element) {
    element.textContent = firstName + ' ' + lastName;
    }


    //同样如果想要输入变量起别名,通过as关键字
    import { lastName as surname } from './profile.js';


    //当加载整个模块的时候,需要用到星号*
    // circle.js
    export function area(radius) {
    return Math.PI * radius * radius;
    }
    export function circumference(radius) {
    return 2 * Math.PI * radius;
    }

    // main.js
    import * as circle from './circle';
    console.log(circle) // {area:area,circumference:circumference}

    //输入的变量都是只读的,不允许修改,但是如果是对象,允许修改属性

    //如果不需要知道变量名或函数就完成加载,就要用到export default命令,为模块指定默认输出
    //加载该模块的时候,import命令可以为该函数指定任意名字
    // export-default.js
    export default function () {
    console.log('foo');
    }
    // import-default.js
    import customName from './export-default';
    customName(); // 'foo'
  • 动态加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
允许您仅在需要时动态加载模块,而不必预先加载所有模块,这存在明显的性能优势
这个新功能允许您将import()作为函数调用,将其作为参数传递给模块的路径。 它返回一个 promise,它用一个模块对象来实现,让你可以访问该对象的导出
*/
import('/modules/myModule.mjs')
.then((module) => {
// Do something with the module.
});

//复合写法
//如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起
export { foo, bar } from 'my_module';
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
怎么理解Decorator (装饰器); 使用场景
  • 介绍

    即装饰器,在不改变原类和使用继承的情况下,动态地扩展对象功能

    优点:

    1. 代码可读性变强了,装饰器命名相当于一个注释
    2. 在不改变原有代码情况下,对原来功能进行扩展
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //定义一个类,什么属性值,方法也没有
    class soldier{
    }

    //定义一个方法,作为装饰器
    function strong(target){
    target.AK = true
    }

    //将装饰器装饰在类上
    @strong
    class soldier{
    }

    //调用类的AK属性
    soldier.AK // true
  • 用法

    Docorator修饰对象为下面两种:

    • 类的装饰
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    /**
    类的装饰
    当对类本身进行装饰的时候,能够接受一个参数,即类本身
    将装饰器行为进行分解,大家能够有个更深入的了解
    */
    @decorator
    class A {}

    // 等同于

    class A {}
    A = decorator(A) || A;


    //下面@testable就是一个装饰器,target就是传入的类,即MyTestableClass,实现了为类添加静态属性
    @testable
    class MyTestableClass {
    // ...
    }

    function testable(target) {
    target.isTestable = true;
    }

    MyTestableClass.isTestable // true

    //如果想要传递参数,可以在装饰器外层再封装一层函数
    function testable(isTestable) {
    return function(target) {
    target.isTestable = isTestable;
    }
    }

    @testable(true)
    class MyTestableClass {}
    MyTestableClass.isTestable // true

    @testable(false)
    class MyClass {}
    MyClass.isTestable // false
    • 类属性的装饰
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    /**
    类属性的装饰
    当对类属性进行装饰的时候,能够接受三个参数:
    1.类的原型对象
    2.需要装饰的属性名
    3.装饰属性名的描述对象
    */

    //首先定义一个readonly装饰器
    function readonly(target, name, descriptor){
    descriptor.writable = false; // 将可写属性设为false
    return descriptor;
    }
    //使用readonly装饰类的name方法
    class Person {
    @readonly
    name() { return `${this.first} ${this.last}` }
    }
    //相当于以下调用
    readonly(Person.prototype, 'name', descriptor);

    //如果一个方法有多个装饰器,就像洋葱一样,先从外到内进入,再由内到外执行
    function dec(id){
    console.log('evaluated', id);
    return (target, property, descriptor) =>console.log('executed', id);
    }

    class Example {
    @dec(1)
    @dec(2)
    method(){}
    }
    // evaluated 1
    // evaluated 2
    // executed 2
    // executed 1
    //外层装饰器@dec(1)先进入,但是内层装饰器@dec(2)先执行
  • 注意项

    装饰器不能用于修饰函数,因为函数存在变量声明情况

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    var counter = 0;

    var add = function () {
    counter++;
    };

    @add
    function foo() {
    }
    //编译阶段,变成下面
    var counter;
    var add;

    @add
    function foo() {
    }

    counter = 0;

    add = function () {
    counter++;
    };
    //意图是执行后counter等于 1,但是实际上结果是counter等于 0
  • 使用场景

    基于Decorator强大的作用,我们能够完成各种场景的需求,下面简单列举几种:

    1. 使用react-redux的时候,如果写成下面这种形式,既不雅观也很麻烦
    1
    2
    3
    4
    5
    class MyReactComponent extends React.Component {}
    export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
    //通过装饰器就变得简洁多了
    @connect(mapStateToProps, mapDispatchToProps)
    export default class MyReactComponent extends React.Component {}
    1. mixins,也可以写成装饰器,让使用更为简洁了
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function mixins(...list) {
    return function (target) {
    Object.assign(target.prototype, ...list);
    };
    }

    // 使用
    const Foo = {
    foo() { console.log('foo') }
    };

    @mixins(Foo)
    class MyClass {}

    let obj = new MyClass();
    obj.foo() // "foo"
    1. 讲讲core-decorators.js几个常见的装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//@antobind
//autobind装饰器使得方法中的this对象,绑定原始对象
import { autobind } from 'core-decorators';

class Person {
@autobind
getPerson() {
return this;
}
}

let person = new Person();
let getPerson = person.getPerson;

getPerson() === person;
// true



//@readonly
//readonly装饰器使得属性或方法不可写
import { readonly } from 'core-decorators';

class Meal {
@readonly
entree = 'steak';
}

var dinner = new Meal();
dinner.entree = 'salmon';
// Cannot assign to read only property 'entree' of [object Object]



//@deprecate
//deprecate或deprecated装饰器在控制台显示一条警告,表示该方法将废除
import { deprecate } from 'core-decorators';

class Person {
@deprecate
facepalm() {}

@deprecate('功能废除了')
facepalmHard() {}
}

let person = new Person();

person.facepalm();
// DEPRECATION Person#facepalm: This function will be removed in future versions.

person.facepalmHard();
// DEPRECATION Person#facepalmHard: 功能废除了
ES6 class

​ class 没有声明提升

类方法 constructor

​ 这个方法类似于其他语言,类的init方法,每次new实例时都会调用

关键字 extends (继承)
1
class Son_of_Bullshit extends Bullshit{}
关键字 super
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 当我们获取父类属性时,需要用到super进行获取
// 例如子类写了constructor , 父类的属性就需要super进行获取
class Bullshit{
constructor (text, color){
this.text = text;
this.color = color;
}
}
class Son_of_Bullshit extends Bullshit{
constructor (text, color, fontSize) {
super(text, color)
this.fontSize = fontSize
}
}
关键字 static

​ 在类里,静态属性、方法是属于类自身的,当调用时直接使用类调用即可

​ 静态方法里的this是类而非实例对象,所以静态方法里面可以调用另外一个静态方法

关键字setter getter (设置属性和获取属性)
class实现单例模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//
class SingletonApple {
constructor(name, creator, products) {
this.name = name;
this.creator = creator;
this.products = prodcts;
}
// 静态方法
static getInstance(name, creator, products) {
if(!this.instance){
this.instance = new SingletonApple(name, creator,products);
}
return this.instance;
}
}
let appleComany = SingletonApple()
let copyApple = SingletonApple()
console.log(appleComany === copyApple) //true