let、const和var的作用和区别
var
- 声明变量 ,声明的变量可以重复声明,同一作用域下,后声明的会覆盖前声明的,
- 有变量提升的特点
let
没有变量提升,必须先声明再使用,
同一作用域下不能重复声明同一个变量名,会报错。可以重新赋值。
let声明的变量产生一个块级作用域。
- 块级作用域:在js中成每个花括号之间的叫代码块 –{ 代码块 },{}的区域叫块级作用域,在每一个{}内let声明出来的变量只能在这个{}中访问到。
暂时性死区
- 简而言之:在代码块内,使用
let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区” - 当程序的控制流程在新的作用域进行实例化时,在此作用域中用let/const声明的变量会先在作用域中被创建出来。但因这时还未进行词法绑定,所以是不能访问的,如果访问就会报错。因此,在这运行流程进入作用域创建变量,到变量可以被访问之间的这一段时间,就称之为暂时性死区
1
2
3
4
5let a = 10;
if (true) {
console.log(a) //会报错,因为该代码块内有一个let,所以使该块区形成了一个封闭的作用域,a在使用let声明前不能被调用
let a = 30;
}- 简而言之:在代码块内,使用
const
- 没有变量提升,必须声明在使用
- 用于声明常量,初始化时一定要赋值,声明之后不能再重复声明也不能再赋值。
- 注意用const声明引用类型时(对象、数组):对象和数组内部的项可以修改。也不能直接给对象、数组的变量名重新赋值 –不能 obj= 10
- 会产生块级作用域,暂时性死区。
拓展
局部作用域又叫(函数作用域),全局作用域和局部作用域都是相对函数内外而定义的。
console.time() ……… console.timeEnd()可以控制台打印中间代码执行的时间。
循环变量时循环内部的处理函数,拿到的都是循环结束之后的最终变量值
- 解决:
- 闭包:可以将闭包内部变量不会直接垃圾回收,而会存储在内存中,这样使用的时候就是上次存的变量值,而不是循环结束之后最终的变量值。
- let:而let本身就 有块级作用域的特点,所以相当于闭包,在块级作用域使用的时候也会把值存到内存中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//let的应用场景
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i) //打印5个5
})
}
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i) // 0-4
})
}
//注:若用var声明循环变量,还可用匿名函数自执行的方式解决
for(var i = 0; i < 5; i++){
;(function(i){
setTimeout(function(){
console.log(i) // 0-4
})
})(i)//将i值传到匿名函数中
}- 解决:
变量的解构赋值
概念
- ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构—–要求*模式匹配
数组解构赋值
有序— 按位置对应
特点
完全解构:值与变量对应
1
let [a,b,c] = [1,2,3];
不完全解构:值比变量多
1
2
3let [a]=[1,2,3] --- a = 1
//缺省
let [,,a]= [1,2,3] --- a=3解构失败:变量比值多
1
2let [a,b,c] = [1]
返回值:a=1,b=undefind,c=undefind拓展默认值
- —解构失败的情况下,变量如果有默认值的情况下就显示默认值,有值的时候显示值
- 赋值为undefined时默认值生效,赋值为null时默认值不生效
1
2
3
4
5
6
7let [a=1,b=2,c=3] = [10]// 返回 a=10,b=2,c=3;
let [a=1,b=2,c=3] = [10,20,30] //返回a=10,b=20,c=30;
let [a=1,b=2,c=3]=[10,undefined,undefined] //返回a=10,b=2,c=3
let [a=1,b=2,c=3]=[10,null,null] //返回a=10,b=null,c=null- 默认值可以引用解构赋值的其他变量,但该变量必须已经声明
1
2
3
4
5let [x = 1, y = x] = []; // x=1;y=1
let [x = 1, y = x] = [2]; //x=2;y=2
let [x = 1, y = x] = [1,2]; //x=1;y=2
let [x = y, y = 1] = [];//ReferenceError: y is not defined
//最后一个表达式之所以会报错,是因为x用y做默认值时,y还没有声明
适用场景
数组的解构赋值,可以做数据交换
1
2
3let a = 10 ;
let b = 20 ;
[a,b] = [b ,a]
对象解构赋值
无序 – 匹配属性名
特点
- 无序集合,必须按照属性 名匹配 ,所以解构中也没有缺省的情况。
- 完全解构,不完全解构、默认值和数组一样
作用
对象解构的作用:适用于取值,和函数的参数适用
1
2
3
4
5
6
7
8
9
10
11
12
13let data = {
username: "xiaoming",
age: 18,
address: "长沙",
sex: "man"
};
let {
username,
age
} = data;
console.log(username, age) //xiaoming 18
注意点
如果要将一个已经声明的变量用于解构赋值,必须非常小心
1
2
3
4
5// 错误的写法
let x;
{x} = {x: 1};
// SyntaxError: syntax error报错的原因:JavaScript引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免JS将其解释为代码块,才能解决这个问题
1
2let x;
({x} = {x: 1})
拓展 函数参数的解构赋值
函数的参数也可以适用解构赋值
1
2
3
4function add([x,y]){//
return x + y;
}
add([1,2]);//传入参数的那一刻,数组参数就被解构成x和y。对于函数内部的代码来说,参数就是x和y
模板字符串
语法
``两个反 引号,变量用${ 变量 }包裹 ,
优点:
- 换行不需要使用换行符,可以直接写html结构,
- 不需要考虑单双引号嵌套问题
- ${}里面也可以自己写js语句;
- 方便简洁不容易出错
对象的扩展
对象的简写
对象属性的简写
属性名和属性值的变量名相同的情况下
1
2
3
4
5
6
7let username = "xiaoming";
let age = 18;
let obj = {
username,
age
}
console.log(obj)
对象方法的简写
省略冒号和function
1
2
3
4
5
6
7
8let obj = {
eat() {
console.log(this)
console.log("红烧肉")
}
}
obj.eat()
对象表达式
一般情况下:[]使用的是变量,表达式,属性为数字时也使用[]
obj[]—[]里面放的是变量不是属性名,如果使用[]要获取属性名,[]里就要加字符串,也就是变量需要加引号,
1
2
3
4
5
6
7
8
9
10
11
12
13
14let username = "hello"
let obj = {
username: "a",
[username]: "b",
10: "world"
}
console.log(obj) // {10: "world", username: "a", hello: "b"}
console.log(obj.username) //a
console.log(obj['username']) //a
console.log(obj[username]) //b
obj["a" + "b"] = "ab"; //添加了一个属性名为ab的属性
console.log(obj) // {10: "world", username: "a", hello: "b", ab: "ab"}
console.log(obj[10]) //world
对象的方法
数组的拼接:cancat()
对象拼接Object.assign(): 可以实现对象的浅拷贝
1
2
3
4
5
6
7
8
9
10
11// 第一个参数是目标对象,从第二个参数开始就要拼接的对象
let o1 = {
a: 10
};
let o2 = {
b: 20
};
Object.assign(o1, o2, {
c: 30
});
console.log(o1)//{a: 10, b: 20, c: 30}Object.keys() 返回的是一个数组,数组里面是对象所有的属性名
Object.values() 返回的是一个数组,数组里面是对象所有的值
1
2
3
4
5
6let o = {
a: 10,
b: 20
}
console.log(Object.keys(o)) //["a", "b"]
console.log(Object.values(o)) //[10, 20]
对象属性的设置
获取对象中某个属性的描述对象;
设置对象中的某一个属性的特性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 数据劫持 vue
const obj = {
a: 10,
b: 20
}
// 读取对象中的某个属性的设置
console.log(Object.getOwnPropertyDescriptor(obj, "a"))
Object.defineProperty(obj, "a", {
writable: false, // 值是否可以重写
enumerable: false, // 是否可以循环遍历
value: "world",
configurable: false // 是否可以重新修改属性的设置
})
obj.a = "hello";
console.log(obj)
for (let key in obj) {
console.log(key)
}
函数拓展
函数默认值
直接在形参上进行赋值
1
2
3
4
5
6function fn(x = "hello") {
console.log(x)
}
fn(10)特点
- 不传实参,默认值生效
- 传递undefined,默认值生效
- 传递null,默认值不生效
注意参数的位置
在参数传递过程中,要注意位置问题;
如果是最后一个参数的话,可以忽略不传递实参;
但是其他位置的话,要用undefined;
- 解决方法1:有默认值的参数放到最后
- 解决方法2:形参和实参都以对象的形式(对象解构赋值)
rest剩余参数
语法:…变量名
- 返回的是一个数组,接收的是没有形参接收的值
- 剩余参数必须写在形参的最后。
1
2
3
4
5
6function fn(x, y, ...rest) {
console.log(rest) //[3,4,5]
console.log(Array.isArray(rest)) //true
}
fn(1, 2, 3, 4, 5)
箭头函数*
语法:
箭头左边(参数)
- 一个参数的情况()括号可以省略
箭头右边(函数的执行代码)
- 执行代码只有一句话的时候{}和return可以省略,但是对象要注意,加上()避免代码歧义。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24var f = v => v;
// 等同于
var f = function (v) {
return v;
};
var f = () => 5;//不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
//由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
// 报错
let getTempItem = id => { id: id, name: "Temp" };
// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });
特点:
this指向的是定义this所在的对象,并且call、apply、bind是无法改变this的指向,
1
2
3
4
5
6
7
8
9
10let obj = {
fn() {
console.log(this)
},
f1: () => {
console.log(this) // window
}
}
obj.fn() //this
obj.f1() // window- this是固定的=>有利于封装回调函数;
1
2
3
4
5
6
7
8
9
10
11
12
13
14//将DOM事件的回调函数封装在一个对象里
var handler = {
id: '123456',
init: function() {
document.addEventListener('click',
event => this.doSomething(event.type), false);
},
doSomething: function(type) {
console.log('Handling ' + type + ' for ' + this.id);
}
};
//使用了箭头函数,导致这个箭头函数里面的this,总是指向handler对象。否则,回调函数运行时,this.doSomething这一行会报错,因为此时this指向document对象箭头函数不能当做构造函数使用,不能使用new命令
箭头函数没有prototype显示原型;
箭头函数不能使用arguments,在遇到不定参的情况使用剩余参数代替(…变量名);
不适用场景
对象中方法的简写不要使用箭头函数
事件绑定函数,要谨慎使用箭头函数
严格模式
关键字 use strict ; 必须放在第一行
规则
- 变量先声明再使用
- 不允许指向全局对象,返回的是undefined
- 对arguments的限制:
- 不允许重新赋值
- 不再追踪参数的变化
- arguments.callee返回的是函数的本身,但是在严格模式下,不允许使用;
1
2
3
4
5
6
7
8
9function fn(num){
// arguments=10;
num++;
console.log(num)
console.log(arguments)
}
fn(1,2,3)对于只读属性重新赋值是会报错的
数组拓展
拓展运算符…
…
拓展运算符是三个点(…)
剩余参数的逆运用,将数组转换为参数序列
数组运用
数组去重
拉平多维数组(推荐使用flat())
实现浅拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//数组去重
let arr = [1,1,2,2,2,3,4,4,4];
console.log([...new Set(arr)])
//拉平多维数组
let arr = [
[1, 2, 3],
[4, 5]
];
let newArr = []
arr.forEach(function(item) {
newArr.push(...item)
})
console.log(newArr)
//实现浅拷贝
let arr = [1, 2, 3];
let arr2 = [...arr];
arr[0] = "hello";
console.log(arr, arr2)
对象运用
对象浅拷贝
1
2
3
4
5
6
7let obj = {
a: 10,
b: 20
}
let obj2 = {...obj
}模拟vuex
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
29let mapState = {
userInfo: {
username: "xiaoming",
age: 18
},
admin: {
username: "guanliyuan"
}
}
let mapMutations = {
run() {
console.log("xiaoming 正在跑")
},
eat() {
console.log("排骨")
}
}
let vm = {
data: {
...mapState //浅拷贝mapState,这样在mapState中可以随需求添加数据
},
methods: {
...mapMutations
}
}
console.log(vm)
数组方法的扩展
Array.of() 将参数内的数据转为数组,弥补new Array()的不足
find()和findIndex()
- find 返回符合条件的第一个成员
- findIndex 返回符合条件的第一个成员的下标索引
Includes() 判断是否含有某一个成员(在字符串中也可以使用);返回的是一个布尔值;
Array.from() 伪数组转真数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23let arr = new Array(5);
console.log(arr) //(5) [empty × 5]
let arr2 = Array.of(5);
console.log(arr2) //[5]
let arr3 = Array.of(true);
console.log(arr3) //[true]
let arr4 = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5];
let index = arr4.findIndex((item, index, obj) => {
return item > 3
})
console.log(index) //3 (第一个满足条件的下标是3)
let x = arr4.find((item, index, obj) => {
return item > 3
})
console.log(x) //4 (第一个满足条件的是4)
let arr5 = ["a", "b", "c"];
console.log(arr5.includes("hello")) //false
Symbol()
概念和作用
ES6新增了Symbol数据类型(是基础数据类型),它用来生成一个独一无二的值
Symbol数据常用来给对象属性赋值,让对象属性具备唯一性,不容易被覆盖。
1
2
3
4
5
6
7
8
9
10
11
12
13
14obj{
a: 10,
a: 20
}
console.log(obj)
//因为obj中属性名a冲突了,所以打印出来的结果会是
// obj{ a: 20}
//因此,为了避免这种情况,可以用symbol当作属性名
obj{
[Symbol()]:10,
[Symbol()]:20
}注:括号内可以加字符串,但只是对该Symbol进行解释说明(字符串相同时,两个symbol也不相等)
1
2
3
4
5
6
7
8
9
10
11
12
13
14let s1 = Symbol('s1');
let s2 = Symbol('s2');
console.log(s1, s2) //Symbol(s1) Symbol(s2)
console.log(s1.description) //s1
console.log(s1 === s2) //false
let s3 = Symbol("s3")
let obj = {
[Symbol()]: 10,
[Symbol()]: 20,
[s3]: 30
}
console.log(obj)//{Symbol(): 10, Symbol(): 20, Symbol(s3): 30}以 Symbol 值作为键名,不会被常规方法遍历得到(见7.3拓展)。我们可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法。
实例: 消除魔术字符串
魔术字符串 在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或数值
1
2
3
4
5
6
7
8
9
10
11
12
13
14function getArea(shape, options) {
let area = 0;
switch (shape) {
case 'Triangle': // 魔术字符串
area = .5 * options.width * options.height;
break;
/* ... more code ... */
}
return area;
}
getArea('Triangle', { width: 100, height: 100 }); // 魔术字符串上面代码中,字符串
Triangle就是一个魔术字符串。它多次出现,与代码形成“强耦合”,不利于将来的修改和维护。用Symbol值解决
1
2
3
4
5
6
7
8
9
10
11
12
13
14const shapeType = {
triangle: Symbol()
};
function getArea(shape, options) {
let area = 0;
switch (shape) {
case Symbol():
area = .5 * options.width * options.height;
break;
}
return area;
}
getArea(Symbol(), { width: 100, height: 100 });
Symbol的方法
s.description
获取Symbol()字符串的值
Symbol.for()
Symbol.for(‘’) 搜索是否有以该参数为名称的Symbol值
- 有则返回这个Symbol值,否则新建一个以该字符串为名称的Symbol值,并将其注册到全局
- 如果里面的字符串的内容相同,Symbol也是相同
1
2
3
4
5
6
7
8let s1 = Symbol.for("s1")
let s2 = Symbol.for("s1")
let s3 = Symbol.for('s4')
console.log(s1) //Symbol(s1)
console.log(s2) //Symbol(s1)
console.log(s3) //Symbol(s4)
console.log(s1 === s2) //trueSymbol.for()与Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,- 前者会被登记在全局环境中供搜索,后者不会
Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值- 比如,如果你调用
Symbol.for("cat")30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")30 次,会返回 30 个不同的 Symbol 值。
1
2
3
4
5
6
7Symbol.for("bar") === Symbol.for("bar")
// true
Symbol("bar") === Symbol("bar")
// false
//Symbol()写法没有登记机制,所以每次调用都会返回一个不同的值
Symbol.keyFor()
返回一个已登记的 Symbol 类型值的key
1
2
3
4
5
6let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined
//s2属于未登记的Symbol值
拓展 属性名的遍历
Symbol 作为属性名,遍历对象的时候,该属性不会出现在
for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const obj = {};
const obj1 = {'a':10, 'b':20}
let a = Symbol('a');
let b = Symbol('b');
obj[a] = 'Hello';
obj[b] = 'World';
for(let key in obj){
console.log(key) //无输出
}
for(let key in obj1){
console.log(key) //a b
}
const objectSymbols = Object.getOwnPropertySymbols(obj);
console.log(objectSymbols)// [Symbol(a), Symbol(b)]
const objNames = Object.getOwnPropertyNames(obj);
console.log(objNames) //[]
set和map
set
Set()本身是一个构造函数,用来实例化Set数据类型(类似于数组的数据结构,特点:不允许有重复项。
set实例的操作方法—用来操作数据
let s = new Set();
s.size : 返回长度
s.add() : 添加某个值,返回set结构本身
s.delete() : 删除值,删除成功返回true,否则返回false
s.has() : 判断是否是set的成员,返回布尔值
s.clear() : 清除所有的成员,没有返回值。
set实例的遍历方法—-遍历成员
map
Map对象保存键值对。是类似对象的数据解构。任何值(对象或者原始值) 都可以作为一个键或一个值
常用方法:
- m.size 成员的个数
- m.set([1, 2, 3], “content3”) 添加一个内容
- m.get(属性) 获取对应的值
- m.has(属性) 判断属性是否存在
- m.delete(属性) 删除属性
- m.clear() 删除所有的属性
Map和Object的区别
- 一个Object 的键只能是字符串或者 Symbols,但一个Map 的键可以是任意值。
- Map中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
- Map的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
- Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
promise
promise概念,then、catch方法
概念和理解:
- 是ES6解决异步回调问题的一种解决方案
由于js是单线程,很多异步操作都是依靠回调方法实现的,这种做法在逻辑比较复杂的回调嵌套中会相当复杂,也叫做回调地狱;
- Promise对象可以理解为一次执行的异步操作,使用Promise对象之后,使用一种链式调用的方式来组织代码,让程序更具备可读性,可维护性
=>可以将异步操作以同步的操作的流程表达出来,避免了层层嵌套的回调函数
- promise和回调函数一样,都是要解决数据的传递和消息发送问题,promise中的then一般对应成功后的数据处理,catch一般对应失败后的数据处理。
语法:
promise是一个构造函数,使用时一般都需要new 一个实例对象。
接受一个函数作为参数,函数中也有两个参数(resolve,reject),resolve将异步操作成功状态下的结果返回。reject将异步操作失败状态写的结果返回。
1 | let p = new Promise(function(resolve,reject)=>{ |
- then方法:接受两个回调函数作为参数,第一个回调函数是promise状态为resolve状态下调用,第二个时reject状态下调用。
1 | p.then(res=>{//第一个回调函数res是个参数,接收resolve括号里返回的结果 |
- catch方法:promise失败状态调用。 同时也可以捕获到一些编写错误的代码,让错误以字符串的形式体现,不报错影响后续代码执行。
1 | p.catch(err=>{ |
Promise特点
对象的状态不受外界影响
三 个状态:进行中pending,已成功fulfilled,已失败rejected
pending表示程序正在执行但未得到结果,即异步操作没有执行完毕,fulfilled表示程序执行完毕,且执行成功,rejected表示执行完毕但失败;
这里的成功和失败都是逻辑意义上的;并非是要报错
只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
一旦创建就会立即执行 ,处于进行中,内部直接执行的为同步,
resolve,reject—进行中转为了成功或失败,再使用then方法调用的是属于异步。
没有调用resolve,reject时处理进行中状态,进行中下没有状态返回。
一旦状态改变,就不会再变,任何时候都可以得到这个结果
- Promise状态改变只有两种可能: pending =>fulfilled pending=>rejected
- 一旦这两种情况发生,状态就凝固了,会一直保持这个结果,如果改变已经发生,再对Promise对象添加回调函数,也会立即得到这个结果
1 | let p1 = new Promise((resolve,reject)=>{}); |
Promise里面同步和异步的判定
- 里面是一个同步任务(状态为pending)
- 调用resolve/reject方法,状态就会发生改变
1
2
3
4
5
6
7
8
9
10
11
12
13console.log(1)
let p = new Promise((resolve, reject) => {
console.log("promise") //同步任务 => 进行中
resolve("成功")
})
p.then(res => {
console.log(123)
console.log(res) // 异步任务 (微任务)
})
console.log(2)
//输出顺序 1 promise 2 123 成功
promise的链式操作
- 必须在前一个then有return返回值,如果return是一个Promise对象,then 就接收的是promise对象中成功状态下的值。
1 | function fn(n) { |
Promise.all()和Promise.race()
- Promise.all() 处理并发 ,所有值就一起返回
- Promise.race() 返回请求完成最快的—–(一般用于设置请求超时提示)
1 | // 一个请求如果超过500ms,返回请求超时提示信息 |
- 错误信息的处理
- 直接在promise.all().catch(),只能返回错误信息,其他正确的信息无法返回。
- 谁报错谁自己处理,其他信息可以正确返回。
1 | let p1 = new Promise(resolve => { |
iterator和for…of循环
概念和作用
- 概念:
在集合的数据结构中,数组,对象,set,map 需要统一接口,来进行处理。Iterator就是这个机制,为不同的数据结构提供统一的接口,任何的数据结构,只要部署了iterator就可以进行循环遍历。
- 作用
- 1.为各种数据结构提供统一的、简便的访问接口,
- 2.使数据结构的成员能够按某种次序排列,
- 3.为for…of…提供消费
- Iterator的原理
- (1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
- (2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
- (3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
- (4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束,值为true的时候表示遍历结束
模拟Iterator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21let arr = [1,2,3];
function myIterator(arr){
let index = 0;
return {
next(){
return index < arr.length?{
value:arr[index++],//因为闭包了,所以index++会储存在这个函数的作用域中
done:false
}:{
value:undefined,
done:true
}
}
}
}
let arr_iter = myIterator(arr);
console.log(arr_iter.next()) //{value: 1, done: false}
console.log(arr_iter.next()) //{value: 2, done: false}
console.log(arr_iter.next()) //review.html:28 {value: 3, done: false}
console.log(arr_iter.next()) //review.html:29 {value: undefined, done: true}
部署Iterator接口
Symbol.iterator属性(默认Iterator接口)
在数据结构上部署iterator接口表现形式为,给对象或数组等集合增加Symbol.iterator属性,属性的内容是一个根据iterator接口规范自行实现的方法
=> 一个数据结构只要有了Symbol.iterator属性,即是“可遍历的”
1
2
3
4
5
6let arr = [1, 2, 3];
let arr_iterator = arr[Symbol.iterator]();
console.log(arr_iter.next()) //{value: 1, done: false}
console.log(arr_iter.next()) //{value: 2, done: false}
console.log(arr_iter.next()) //review.html:28 {value: 3, done: false}
console.log(arr_iter.next()) //review.html:29 {value: undefined, done: true}原生具备Iterator接口的数据结构
Array
Map
Set
String
TypedArray
函数的 arguments 对象
NodeList 对象调用这个属性,就能得到遍历器对象
对象部署Iterator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23obj[Symbol.iterator] = function(){
let index = 0;
return {
next: () =>{ //如果是用function()则this输出的是next方法
// console.log(this); //对象本身
// console.log(Object.keys(this)) //对象的所有属性 => 数组
// console.log(Object.keys(this)[index]) // 获取数组中对应索引下标的值
// console.log(obj[Object.keys(this)[index]]) //获取对应的属性名的值
if(index < Object.keys(this).length){
return {value: obj[Object.keys(this)[index++]],
done: false}
}else{
return {value: undefined,
done: true}
}
}
}
}
let y = obj[Symbol.iterator]()
console.log(y.next())
console.log(y.next())
console.log(y.next())
for…in 和 for…of循环
for…of
一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for…of循环遍历它的成员。
也就是说,for…of循环内部调用的是数据结构的Symbol.iterator方法。
for…of 和 for…in的区别
for…in
- 返回的是索引值,是以字符串的形式
- 不一定是按照一定的顺序返回的
- 不仅可以遍历本身的方法和属性,还可以遍历原型上的方法和属性
for…of
- 返回的是值
- 是按照一定的顺序进行遍历
- 只能遍历本身的属性和方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21var myObject = {name:'zhangsan',age:100};
Object.prototype.sayHello = function(){
console.log('Hello');
}
Object.prototype.str = 'World';
for(let key in myObject){
console.log(key) //name age sayHello str
}
let arr = [3, 5, 7];
arr.foo = 'hello';
for (let i in arr) {
console.log(i); // "0", "1", "2", "foo"
}
for (let i of arr) {
console.log(i); // "3", "5", "7"
//for...of方法不会返回arr的foo属性
}
generator
概念和语法
概念
- 语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
- 执行Generator函数会返回一个遍历器对象 => 是个遍历器对象生成函数。
返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态 - 形式上:Generator是一个普通函数
语法
function关键字与函数名之间有一个星号;
函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
1
2
3
4
5
6
7function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();调用该函数返回的是一个遍历器对象(指针指向内部对象),必须调用遍历器对象的next的方法,指针才能移向下一个对象;
每调用一次next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)1
2
3
4
5
6
7
8
9function* fn() {
console.log('a');
console.log('b')
yield 'hello';
yield 'world';
return 'ending';
}
let f_i = fn();
console.log((f_i).next()) // a b {value:"hello",done:false}
Yield表达式
暂停执行函数的关键字
由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。
yield表达式就是暂停标志。
运行逻辑(案例详见11.3的拓展题)
- (1)遇到
yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。 - (2)下一次调用
next方法时,再继续往下执行,直到遇到下一个yield表达式。 - (3)如果没有再遇到新的
yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。 - (4)如果该函数没有
return语句,则返回的对象的value属性值为undefined。
不用yield表达式时,就变成了一个单纯的暂缓执行函数(调用next方法才能执行)
- (1)遇到
next方法的参数
yield表达式本身没有返回值,或者说总是返回undefined
1
2
3
4
5function*fn(){
yield;
}
let fn_i = fn();
console.log(fn_i.next()); //{value: undefined, done: false}next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
1
2
3
4
5
6
7
8
9function* fn() {
let a = yield "hello";
console.log(a)
}
let f_i = fn();
console.log(f_i.next()) //{value: "hello", done: false}
f_i.next(10) //10拓展题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function* fn1(x) { //行1
let y = 20 / (yield x + 100) //行2
console.log(y) //5 //行3
let z = yield y / 2 //行4
console.log(z) //6 //行5
return x + y + z //行6
}
let f_i = fn1(5)
console.log(f_i.next()); //{value: 105, done: false}
//第一次调用next,返回第一个yield(行2)后面的表达式结果(一个遍历器对象) {value: 5+100, done: false}
console.log(f_i.next(4)); //{value: 2.5, done: false}
//第二次调用next,继续执行行3,此时由于next的参数4传给了上一个yield表达式(行2)的返回值,因此 y = 20 / 4 = 5。而console.log(f_i.next(4))打印的是行4中yield的返回值 {value: 5/2, done: false}
console.log(f_i.next(6)); //{value: 16, done: true}
//第三次调用next,同理继续执行 行5,此时由于next的参数是6,传给了行4中yield后的返回值,因此 z = 6;而console.log(f_i.next(6))打印的是最后return的遍历器对象 value = x+y+z = 5+5+6 = 16作用:可以在 Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。
async函数 async…await
概念
- async函数是 Generator 函数的语法糖
- 将generator函数的*换成了
async,将yield替换成await - async函数返回的是一个Promise对象,可以 使用promise对象的方法。可以看作多个异步操作,包装成的一个 Promise 对象
基本用法
async函数返回的是一个Promise对象,可以 使用promise对象的方法
- 使用then方法添加回调函数获取return后面的值
- 使用catch 来捕获错误信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20async function fn() {
return "hello"
}
console.log(fn()); //Promise {<resolved>: "hello"} //返回的是一个Promise对象
fn().then(res => {
console.log(res) //"hellp"
})
//捕获错误信息
async function fn() {
let obj = {};
obj.run()
return "hello"
}
fn().then(res => {
console.log(res)
}).catch(err => {
console.log(err)//TypeError: obj.run is not a function
})
await()方法:一般情况下是结合promise对象使用。
只能在async函数中使用;
函数外面访问不到await及后面的表达式,如果需要在函数外部获取就需要在await前面加return。
await后面如果接的是promise对象,它拿到的是promise成功状态下的值(不需要再使用then),如果接的是其他类型就直接返回。
await命令就是内部then命令的语法糖。必须等待内部所有
await命令后面的Promise对象执行完,才会发生状态改变,才会执行then方法指定的回调函数任何一个
await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。1
2
3
4
5async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
f().then(res=>{console.log(res)}).catch(err=>{console.log(err);}) //出错了如果希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个
await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。1
2
3
4
5
6
7
8
9
10async function f() {
try {
await Promise.reject('出错了');
} catch(er) {
}
return await Promise.resolve('hello world');
}
f().then(res => console.log(res))
// hello world
async函数内部代码时同步执行的,执行时如果碰到await,就会先返回;等待await后面的表达式出结果之后再继续执行函数体内后面的语句,但是await不会阻塞async函数外的代码,await等待过程中函数外的代码正常向下同步执行。
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
26let p = new Promise((resolve) => {
setTimeout(() => {
resolve("promise")
}, 2000)
})
console.log("async 开始")
async function fn() {
console.log(1);
let a = await p;
console.log(a);
console.log(2);
return 3;
}
fn().next(res=>{
console.log(res)
})
console.log("async 结束")
//async 开始
//1
//async 结束
//(两秒后)promise
//2
//3
实例: 解决回调地狱(按顺序完成异步操作)
1 | function http({ |
class类
概念和语法
概念
类和对象:
类:对象的类型模板,确定对象中共有的属性和行为
对象:类创建的实例,具体的某一个事物,一切皆为对象
ES6引入了Class(类)这个概念,通过
class关键字,可以定义类一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法
语法
class关键字
- 构造函数本身方法或者是属性要写在constructor方法里面
- 原型上的方法或者是属性直接写在class里面即可
1
2
3
4
5
6
7
8
9
10
11
12
13Class Person{//定义类的构造函数
construtor(name,age){
this.name = name;
this.age = age;
}
//定义一般的方法
eat(){
console.log('吃饭饭')
}
}
let P = new Person('xx','18');
p.eat()constructor方法
- 是类的默认方法,通过new命令生成对象实例时,自动调用该方法
- 一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加
- 默认返回实例对象(即this)
事实上,类的所有方法都定义在类的prototype属性上面。
取值函数(getter)和存值函数(setter)(了解)
与 ES5 一样,在“类”的内部可以使用
get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class MyClass {
constructor() {
// ...
}
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter: '+value);
}
}
let inst = new MyClass();
inst.prop = 123;
// setter: 123
inst.prop
// 'getter'
静态方法
实例化方法和属性:(成员)
类相当于实例的原型(实例化对象的__proto__指向类的prototype原型),所有在类中定义的方法,都会被实例继承
必须**通过实例化对象进行调用**静态方法和静态属性:
在方法和属性前面加关键字static,表示该方法不会被实例继承,直接通过类进行调用
对于一些通用性的属性或方法,可以考虑设置为静态
1
2
3
4
5
6
7
8
9
10
11class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function注意:如果静态方法包含
this关键字,这个this指的是类,而不是实例。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Foo {
static bar() {
this.baz();
}
static baz() {
console.log('hello');
}
baz() {
console.log('world');
}
}
Foo.bar() // hello
//静态方法bar调用了this.baz, 这里的this指的是Foo类,而不是Foo的实例;等同于调用Foo.baz
//静态方法可以与非静态方法重名
继承
语法
extends关键字子类里,如果不写constructor是可以,默认可以添加,但是如果写入constructor函数就必须要加入super();super调用父类的constructor;super必须写constructor的第一行;
不但可以继承实例化的方法和属性,静态的方法和属性也是可以继承;
super一种是方法,一种是对象;
- 作为方法是调用父类的constructor;
- 作为对象来说,就是在子类的方法里面调用父类的方法;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Parent {
constructor(name,age) {
this.name = name;
this.age = age
}
}
class Me extends Parent {
constructor(name,age,sex) {
super(name,age) // 调用父类的constructor
this.sex = sex
}
}
let p = new Parent("rose",40)
let m = new Me("joth",18,'male');1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class Parent {
constructor() {
this.x = 1;
}
print() {
console.log(this.x);
}
}
class Me extends Parent {
constructor() {
super();
this.x = 2;
}
m() {
super.print();//在子类的方法里调用父类的方法
}
}
let b = new Me();
b.m() //2父类的静态方法,可以被子类继承
1
2
3
4
5
6
7
8
9
10class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod() // 'hello'
拓展:类的prototype属性和__proto__属性
大多数浏览器的 ES5 实现之中,每一个对象都有
__proto__属性,指向对应的构造函数的prototype属性。Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。- 子类的
__proto__属性,表示构造函数的继承,总是指向父类。 - 子类
prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
1
2
3
4
5
6
7
8
9
10class A {
}
class B extends A {
}
B.__proto__ === A // true //作为一个对象,子类(B)的原型(__proto__属性)是父类(A)
console.log(B.prototype) //A {constructor: ƒ}
B.prototype.__proto__ === A.prototype // true //作为一个构造函数,子类(B)的原型对象(prototype属性)是父类的原型对象(prototype属性)的实例。- 子类的
实例的
__proto__属性子类实例的
__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Parent {
constructor(name,age) {
this.name = name;
this.age = age
}
}
class Me extends Parent {
constructor(name,age,sex) {
super(name,age) // 调用父类的constructor
this.sex = sex
}
}
let p = new Parent("rose",40)
let m = new Me("joth",18,'male');
console.log(m.__proto__);//Parent {constructor: ƒ}
console.log(m.__proto__ === Me.prototype); //true
console.log(m.__proto__.__proto__ === p.__proto__); //true