深入前端面试题-JS、ES6

174 阅读28分钟

一、JS

1. 节流和防抖

(1) 是什么

本质上是优化高频率执行代码的一种手段

如:浏览器的 resizescrollkeypressmousemove 等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能

为了优化体验,需要对这类事件进行调用次数的限制,对此我们就可以采用throttle(节流)和debounce(防抖)的方式来减少调用频率。

  • 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次执行
  • 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时

image.png

image.png

(2) 区别

相同点:

  • 都可以通过使用 setTimeout 实现
  • 目的都是,降低回调执行频率。节省计算资源

不同点:

  • 函数防抖,在一段连续操作结束后,处理回调,利用clearTimeout和 setTimeout实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能
  • 函数防抖关注一定时间连续触发的事件,只在最后执行一次;而函数节流一段时间内只执行一次

(3) 应用场景

防抖在连续的事件,只需触发一次回调的场景有:

  • 搜索框搜索输入。只需用户最后一次输入完,再发送请求
  • 手机号、邮箱验证输入检测
  • 窗口大小resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。

节流在间隔一段时间执行一次回调的场景有:

  • 滚动加载,加载更多或滚到底部监听
  • 搜索框,搜索联想功能
2 变量类型

(1) 基本数据 和 引用数据

  • 基本数据类型: 只包括自身一般存储在栈内存中,任何方法都不能修改基本数据类型本身的值,都是返回了一个新的。

  • 引用数据类型: 包括指针和内容,指针存储在栈内存,内容存储在堆内存。

(2) JS的内存空间 JS的内存空间分为栈(stack)、堆(heap)、池(一般也会归类为栈中)。

其中栈存放变量,堆存放复杂对象,池存放常量,所以也叫常量池。

  • 基本数据类型保存在 栈内存 中,因为基本数据类型占用空间小、大小固定,通过按值来访问,属于被频繁使用的数据。

  • 引用数据类型存储在 堆内存 中,因为引用数据类型占据空间大、大小不固定。

image.png

3 类型判断

(1) typeof

typeof 对于基本类型而言,可以正常检测其类型;

对于引用类型,只能具体判断函数的类型为 function;

typeof null 的值为 object,这是因为一个久远的 Bug。如果想具体判断 null 类型的话直接 xxx === null 即可。

image.png

(1.2) null

  • undefined 的含义是 未被赋值 ;如果一个变量已经被声明,但未被赋值,那么它的值就是 undefined。

image.png

  • JS中的 null 仅仅是一个代表 无 、 空 或 值未知的 特殊值。
  • null 表示空,它是空对象
  • 当需要对数组销毁、对象销毁,或者删除时间监听,通常将他们设置为 null
box.onclick = null
typeof null       // "object"
let b = null
b == null   // true
b === null  // true

(1.3) NaN

  • 不是一个数,是一个数字类型的值
typeof NaN // "number"
  • 0除以0的结果是NaN;在数学运算中,若结果不能得到数字,其结果往往都是NaN。
0 / 0 // NaN '哈' - 3 // NaN 4 / 0 // Infinity
  • NaN不自等
NaN == NaN // false
  • 类型判断 isxxx API image.png

(2) Array.isArray()方法检测数组

var a = [1,2,3];
Array.isArray(a)		// true
Array.isArray([1,2,378])	// true
Array.isArray({a:1,d:55})	// false

(3) instanceof

  • 虽然 typeof 运算符在检测基本类型的时候非常好用,但检测引用类型的时候,就不行了。

通常并不想知道它是不是对象,而是想知道它到底是什么类型的对象,因为 数组是 object ,null 也是 object ,正则也是 object 。

这时应该用 instanceof 运算符来检测

instanceof 内部通过原型链的方式来判断是否为构建函数的实例,常用于判断具体的对象类型。

image.png

  • 当使用 instanceof 检查基本类型的值时,它会放回 false

image.png

(4) Object.prototype.toString

image.png

4 深拷贝浅拷贝

(1) 拷贝类型为基本类型

如果属性是基本类型,拷贝的就是基本类型的值。

如果属性是引用类型,拷贝的就是内存地址

(2) 拷贝类型为引用类型

  • 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
  • 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址

(3) 浅拷贝

浅拷贝只复制属性指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存,修改对象属性会影响原对象 在JavaScript中,存在浅拷贝的现象有:

  • Object.assign
  • Array.prototype.slice()Array.prototype.concat()
  • 使用拓展运算符实现的复制

(3.1)浅拷贝—— Object.assign

Object.assign(dest,[src1,src2,src3...])

dest:目标对象

src1,src2,src3...:源对象

将所有源对象的属性拷贝到目标对象dest中。即从第二个开始的所有参数的属性都被拷贝到第一个参数的对象中

调用结果返回dest

用Object.assign 替代 for .. in 循环来进行简单克隆,它将 user 中的所有属性拷贝到一个空对象中,并返回这个新的对象。

var obj = {
    age: 18,
    nature: ['smart''good'],
    names: {
        name1: 'fx',
        name2: 'xka'
    },
    love: function () {
        console.log('fx is a great girl')
    }
}
var newObj = Object.assign({}, fxObj);

(3.2)浅拷贝—— slice()

slice()方法返回一个含有被提取元素的新数组,原数组不会被改变

arr.slice(begin,end)

begin :可选;提取起始处的索引(从0开始),从该索引开始提取原数组元素

end :可选;提取终止处的索引(从0开始)。slice会提取原数组中索引从 begin 到 end 的所有元素( 包括 begin,但不包括 end )

image.png

// 浅拷贝
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.slice(0)
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]

(3.3)浅拷贝—— concat()

concat()方法,合并2个或多个数组,此方法不会改变原数组,返回新的数组

var new_array = old_array.concat(value1 [ ,value2 [ ,...[ valueN ] ] ])

valueN :可选。将数组和/或值连接成新的数组

const fxArr = ["One""Two""Three"]
const fxArrs = fxArr.concat()
fxArrs[1] = "love";
console.log(fxArr) // ["One""Two""Three"]
console.log(fxArrs) // ["One""love""Three"]

(3.4)浅拷贝—— ### 拓展运算符

const fxArr = ["One""Two""Three"]
const fxArrs = [...fxArr]
fxArrs[1] = "love";
console.log(fxArr) // ["One""Two""Three"]
console.log(fxArrs) // ["One""love""Three"]

(4) 深拷贝

深拷贝开辟一个新的栈,两个对象属性完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

常见的深拷贝方式有:

  • JSON.stringify()
  • 手写循环递归

(4.1) 深拷贝—— JSON.stringify

const obj2=JSON.parse(JSON.stringify(obj1));

但是这种方式存在弊端,会忽略undefinedsymbol函数

const obj = {
    name'A',
    name1undefined,
    name3function() {},
    name4:  Symbol('A')
}

const obj2 = JSON.parse(JSON.stringify(obj));

console.log(obj2);       // {name: "A"}

JSON.parse(JSON.stringify(null))  // null

(4.2) 深拷贝—— 手写循环递归

function deepFun(obj) {
  if(typeof obj !== 'object' || obj == null) {
    return obj
  }

  let result
  if(obj instanceof Array) {
    result = []
  } else {
    result = {}
  }

  for(let key in obj) {
    if(obj.hasOwnProperty(key)) {
      result[key] = deepFun(obj[key])
    }
  }

  return result
}
5 类型转换

(1) 常见的类型转换有:

  • 强制转换(显示转换)
  • 自动转换(隐式转换)

(2) 显示转换

显示转换,即我们很清楚可以看到这里发生了类型的转变,常见的方法有:

  • Number()
  • parseInt()
  • String()
  • Boolean()

Number()

Number转换的时候是很严格的,只要有一个字符无法转成数值,整个字符串就会被转为NaN

image.png

Number(324) // 324

// 字符串:如果可以被解析为数值,则转换为相应的数值
Number('324') // 324

// 字符串:如果不可以被解析为数值,返回 NaN
Number('324abc') // NaN

// 空字符串转为0
Number('') // 0

// 布尔值:true 转成 1,false 转成 0
Number(true) // 1
Number(false) // 0

// undefined:转成 NaN
Number(undefined) // NaN

// null:转成0
Number(null) // 0

// 对象:通常转换成NaN(除了只包含单个数值的数组)
Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5

Number(NaN)  // NaN

parseInt()

parseInt相比Number,就没那么严格了,parseInt函数逐个解析字符,遇到不能转换的字符就停下来

parseInt('32a3') //32

String()

可以将任意类型的值转化成字符串

// 数值:转为相应的字符串
String(1) // "1"

//字符串:转换后还是原来的值
String("a") // "a"

//布尔值:true转为字符串"true",false转为字符串"false"
String(true) // "true"

//undefined:转为字符串"undefined"
String(undefined) // "undefined"

//null:转为字符串"null"
String(null) // "null"

//对象
String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"

Boolean()

可以将任意类型的值转为布尔值

Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true

(3) 隐式转换

在隐式转换中,我们可能最大的疑惑是 :何时发生隐式转换?

我们这里可以归纳为两种情况发生隐式转换的场景:

  • 比较运算(==!=><)、ifwhile需要布尔值地方
  • 算术运算(+-*/%

除了上面的场景,还要求运算符两边的操作数不是同一类型

自动转换为布尔值

在需要布尔值的地方,就会将非布尔值的参数自动转为布尔值,系统内部会调用Boolean函数

可以得出个小结:

  • undefined
  • null
  • false
  • +0
  • -0
  • NaN
  • ""

除了上面几种会被转化成false,其他都换被转化成true

自动转换成字符串

遇到预期为字符串的地方,就会将非字符串的值自动转为字符串

具体规则是:先将复合类型的值转为原始类型的值,再将原始类型的值转为字符串

常发生在+运算中,一旦存在字符串,则会进行字符串拼接操作

'5' + 1 // '51'
'5' + true // "5true"
'5' + false // "5false"
'5' + {} // "5[object Object]"
'5' + [] // "5"
'5' + function (){} // "5function (){}"
'5' + undefined // "5undefined"
'5' + null // "5null"

自动转换成数值

除了+有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值

'5' - '2' // 3
'5' * '2' // 10
true - 1  // 0
false - 1 // -1
'1' - 1   // 0
'5' * []    // 0
false / '5' // 0
'abc' - 1   // NaN
null + 1 // 1
undefined + 1 // NaN

null转为数值时,值为0 。undefined转为数值时,值为NaN

6 使用 class 实现继承

(1.1) 实现继承

image.png

class Student {
    constructor(name,age) {
        this.name = name
        this.age = age
    }
    fun() {
        console.log(`我是${this.name},年龄是${this.age}`)
    }
}
const xiaoming = new Student('xiaomiing',20)
console.log('xiaoming',xiaoming)
xiaoming.fun()


class Teacher {
    constructor(name,subject) {
        this.name = name
        this.subject = subject
    }
    fun() {
        console.log(`我是${this.name},教的课程是${this.subject}`)
    }
}
const xiaoha = new Teacher('xiaoha','前端')
console.log('xiaoha',xiaoha)
xiaoha.fun()

(1.2) 相同的代码进行整合

在类继承的时候,不仅可以继承属性,也可以继承方法,extend;

super一定要写在最前面

image.png

class Person{
    constructor(name) {
        this.name = name
    }
    drink() {
        console.log('aaaa')
    }
}

class Student extends Person {        // extends 继承 属性name
    constructor(name,age) {
        // this.name = name
        super(name)                 // super(name) super一定要写在最前面
        this.age = age
    }
    fun() {
        console.log(`我是${this.name},年龄是${this.age}`)
    }
}
const xiaoming = new Student('xiaomiing',20)
console.log('xiaoming',xiaoming)
xiaoming.fun()
xiaoming.drink()                // 继承Person 的方法drink()


class Teacher extends Person{         // extends 继承 属性name
    constructor(name,subject) {
        // this.name = name
        super(name)
        this.subject = subject
    }
    fun() {
        console.log(`我是${this.name},教的课程是${this.subject}`)
    }
}
const xiaoha = new Teacher('xiaoha','前端')
console.log('xiaoha',xiaoha)
xiaoha.fun()
xiaoha.drink()

(2) 原型链继承

  function Parent() {
    this.name = 'parent1';
    this.play = [1, 2, 3]
  }
  function Child() {
    this.type = 'child2';
  }
  Child.prototype = new Parent();
  console.log(new Child())

上面代码看似没问题,实际存在潜在问题

var s1 = new Child();
var s2 = new Child();
s1.play.push(4);
console.log(s1.play, s2.play); // [1,2,3,4]

改变s1play属性,会发现s2也跟着发生变化了,这是因为两个实例使用的是同一个原型对象,内存空间是共享的

7 原型

当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾

准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非实例对象本身

image.png

8 原型链

当访问对象属性或方法时,

=> 首先从自身开始找,如果找不到;

=> 就往原型上去找,如果原型上还是找不到;

=> 就往原型的原型上去找;

这样一来就形成一条链式结构,把它称做原型链。

__proto__作为不同对象之间的桥梁,用来指向创建它的构造函数的原型对象的

image.png

每个对象的__proto__都是指向它的构造函数的原型对象prototype

person1.__proto__ === Person.prototype

构造函数是一个函数对象,是通过 Function构造器产生的

Person.__proto__ === Function.prototype

原型对象本身是一个普通对象,而普通对象的构造函数都是Object

Person.prototype.__proto__ === Object.prototype

刚刚上面说了,所有的构造器都是函数对象,函数对象都是 Function构造产生的

Object.__proto__ === Function.prototype

Object的原型对象也有__proto__属性指向nullnull是原型链的顶端

Object.prototype.__proto__ === null
9 Javscript数组的常用方法

(1) 操作方法

数组基本操作可以归纳为 增、删、改、查,需要留意的是哪些方法会对原数组产生影响,哪些方法不会

(1.1 )增

下面前三种是对原数组产生影响的增添方法,第四种则不会对原数组产生影响

  • push()
  • unshift()
  • splice()
  • concat() 不会对原数组产生影响

push()方法接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度

let colors = []; // 创建一个数组
let count = colors.push("red", "green"); // 推入两项
console.log(count) // 2
console.log(colors) // ["red", "green"]

unshift() 在数组开头添加任意多个值,然后返回新的数组长度

let colors = new Array(); // 创建一个数组
let count = colors.unshift("red""green"); // 从数组开头推入两项
alert(count); // 2
console.log(colors) // ["red", "green"]

splice() 传入三个参数,分别是开始位置、0(要删除的元素数量)、插入的元素,返回空数组。

splice(下标值,数量,添加值)

数量=0,无删除

数量=1,删除1个

let colors = ["red""green""blue"];
let removed = colors.splice(10"yellow""orange")
console.log(colors) // red,yellow,orange,green,blue
console.log(removed) // []

concat() 首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组,不会影响原始数组

let colors = ["red""green""blue"];
let colors2 = colors.concat("yellow", ["black""brown"]);
console.log(colors); // ["red""green","blue"]
console.log(colors2); // ["red""green""blue""yellow""black""brown"]

(1.2) 删

下面三种都会影响原数组,最后一项不影响原数组:

  • pop()
  • shift()
  • splice()
  • slice() 不影响原数组

pop() 方法用于删除数组的最后一项,同时减少数组的length 值,返回被删除的项

let colors = ["red""green"]
let item = colors.pop(); // 取得最后一项
console.log(item) // green
console.log(colors.length// 1

shift()方法用于删除数组的第一项,同时减少数组的length 值,返回被删除的项

let colors = ["red""green"]
let item = colors.shift(); // 取得第一项
console.log(item) // red
console.log(colors.length// 1

splice() 传入两个参数,分别是开始位置,删除元素的数量,返回包含删除元素的数组

let colors = ["red""green""blue"];
let removed = colors.splice(0,1); // 删除第一项
console.log(colors); // green,blue
console.log(removed); // red,只有一个元素的数组

slice()用于创建一个包含原有数组中一个或多个元素的新数组,不会影响原始数组

slice(a,b)截取的子数组从下标a开始,到下标b(但不包括b)结束;

slice()如果不提供第二个参数,则表示从指定项开始,提取所有后续所有项作为子数组;

let colors = ["red""green""blue""yellow""purple"];
let colors2 = colors.slice(1);
let colors3 = colors.slice(14);
console.log(colors)   // red,green,blue,yellow,purple
concole.log(colors2); // green,blue,yellow,purple
concole.log(colors3); // green,blue,yellow

(1.2) 改

即修改原来数组的内容,常用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,只有一个元素的数组

splice 的总结:

splice(下标值,数量,添加值),会改变原数组

数量=0,无删除

数量=1,删除1个

  • splice()方法用来替换数组中的指定项。
var arr = ['a','b','c','d','e','f']
arr.splice(3,2,'F','G','Y')	// 下标为3开始,连续替换2项
console.log(arr)			//  ["a", "b", "c", "F", "G", "Y", "f"]
  • splice()方法用于在指定的位置插入新项。
var arr = ['a','b','c','d']
arr.splice(2,0,'F','G','Y') // 第二个参数为0,即不删除原数组的任何一项,在下标为2的位置添加添加新项
console.log(arr)		//  ["a", "b", "F", "G", "Y", "c", "d"]
  • splice()方法用于删除指定项。
var arr = ['a','b','c','d','e','f']
arr.splice(3,2)	// 没有第三个参数,即没有设置替换的新项,
console.log(arr)	// ["a", "b", "c", "f"]
  • splice()方法以数组形式返回被删除的项。
var arr = ['a','b','c','d','e','f']
var newArr = arr.splice(3,2)
console.log(newArr)	// ["d", "e"]

(1.3) 查

即查找元素,返回元素坐标或者元素值

  • indexOf()
  • includes()
  • find()

indexOf()返回要查找的元素在数组中的位置,如果没找到则返回-1

let numbers = [123454321];\
numbers.indexOf(4) // 3

includes()返回要查找的元素在数组中的位置,找到返回true,否则false

let numbers = [123454321];\
numbers.includes(4) // true

find()返回第一个匹配的元素

const people = [
    {
        name: "Matt",
        age: 27
    },
    {
        name: "Nicholas",
        age: 29
    }
];
people.find((element, index, array) => element.age < 28) // // {name: "Matt", age: 27}

(2) 排序方法

数组有两个方法可以用来对元素重新排序:

  • reverse()
  • sort()

reverse()顾名思义,将数组元素方向排列

let values = [1, 2, 3, 4, 5];
values.reverse();
alert(values); // 5,4,3,2,1

sort()对一个数组进行排序,[2,8,11,6],已升序排序输出,已降序排序输出 image.png

(3) 转换方法

join()方法接收一个参数,即字符串分隔符,返回包含所有项的字符串

let colors = ["red""green""blue"];
alert(colors.join(",")); // red,green,blue
alert(colors.join("||")); // red||green||blue

image.png

(4) 迭代方法 常用来迭代数组的方法(都不改变原数组)有如下:

  • some()
  • every()
  • forEach()
  • filter()
  • map() some()对数组每一项都运行传入的函数,如果有一项函数返回 true ,则这个方法返回 true
let numbers = [123454321];
let someResult = numbers.every((item, index, array) => item > 2);
console.log(someResult) // true

every()对数组每一项都运行传入的函数,如果对每一项函数都返回 true ,则这个方法返回 true

let numbers = [123454321];
let everyResult = numbers.every((item, index, array) => item > 2);
console.log(everyResult) // false

forEach()对数组每一项都运行传入的函数,没有返回值

let numbers = [123454321];
numbers.forEach((item, index, array) => {
    // 执行某些操作
});

filter()对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回

let numbers = [123454321];
let filterResult = numbers.filter((item, index, array) => item > 2);
console.log(filterResult); // 3,4,5,4,3

map()对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组

let numbers = [123454321];\
let mapResult = numbers.map((item, index, array) => item * 2);\
console.log(mapResult) // 2,4,6,8,10,8,6,4,2
10Javscript字符串的常用方法

(1) 操作方法 字符串常用的操作方法归纳为增、删、改、查

(1.1) 增

这里增的意思并不是说直接增添内容,而是创建字符串的一个副本,再进行操作

除了常用+以及${}进行字符串拼接之外,还可通过concat

concat用于将一个或多个字符串拼接成一个新字符串

let stringValue = "hello ";
let result = stringValue.concat("world");
console.log(result); // "hello world"
console.log(stringValue); // "hello"

(1.2) 删 这里的删的意思并不是说删除原字符串的内容,而是创建字符串的一个副本,再进行操作

常见的有:

  • slice()
  • substr()
  • substring()

slice()

  • slice(a,b)方法方法从a开始到b结束(不包括b)的子串。
"我喜欢JS,我也喜欢HTML".slice(3,5) // "JS"
  • slice(a,b)中a可以是负数
"我喜欢JS,我也喜欢HTML".slice(-4,-1) // "HTM"
  • slice(a,b)中a必须小于b
"我喜欢JS,我也喜欢HTML".slice(5,2) // ""

substr()

  • substr(a,b)中,得到从a开始的长度为b的子串
"我喜欢JS,我也喜欢HTML".substr(3,2) // "JS"
  • substr(a,b)中,可以b省略,表示到字符串的结尾。
"我喜欢JS,我也喜欢HTML".substr(3) // "JS,我也喜欢HTML"
  • substr(a,b)中,a可以是负数,表示倒数位置。
"我喜欢JS,我也喜欢HTML".substr(-4,2) // "HT"

substring()

  • substring(a,b)方法从a开始到b结束(不包括b)的子串。
"我喜欢JS,我也喜欢HTML".substring(3,5)	// "JS"
"我喜欢JS,我也喜欢HTML".substring(10,14)	// "HTML"
  • substring(a,b)忽略第二个参数,返回的子串一直到字符串的结尾。
"我喜欢JS,我也喜欢HTML".substring(6) // "我也喜欢HTML"
  • substring(a,b)中a可以大于b,数字顺序将自动调整小数在前。
"我喜欢JS,我也喜欢HTML".substring(5,3) // "JS"

对比总结:

substring(a,b)和slice(a,b)功能基本一致,都是从a开始到b结束(不包括b)的子串,

区别一:substring(a,b)可以自动交换2个参数的位置,而slice(a,b)不行;

区别二:slice(a,b)的参数a可以是负数,而substring(a,b)不行。

substr(a,b)中参数b是子串长度,而不是位置编号。

(1.3) 改 这里改的意思也不是改变原字符串,而是创建字符串的一个副本,再进行操作

常见的有:

  • trim()、trimLeft()、trimRight()
  • repeat()
  • padStart()、padEnd()
  • toLowerCase()、 toUpperCase()

trim()、trimLeft()、trimRight()

删除前、后或前后所有空格符,再返回新的字符串

let stringValue = " hello world ";
let trimmedStringValue = stringValue.trim();
console.log(stringValue); // " hello world "
console.log(trimmedStringValue); // "hello world"

repeat() 接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果

let stringValue = "na ";
let copyResult = stringValue.repeat(2) // na na

padStart()、padEnd() 复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件

let stringValue = "foo";
console.log(stringValue.padStart(6)); // " foo"
console.log(stringValue.padStart(9, ".")); // "......foo"

toLowerCase()、 toUpperCase() 大小写转化

let stringValue = "hello world";
console.log(stringValue.toUpperCase()); // "HELLO WORLD"
console.log(stringValue.toLowerCase()); // "hello world"

(1.4) 查 除了通过索引的方式获取字符串的值,还可通过:

  • chatAt()
  • indexOf()
  • startWith()
  • includes()

chatAt() 返回给定索引位置的字符,由传给方法的整数参数指定

let message = "abcde";
console.log(message.charAt(2)); // "c"

indexOf() 从字符串开头去搜索传入的字符串,并返回位置(如果没找到,则返回 -1 )

let stringValue = "hello world";
console.log(stringValue.indexOf("o")); // 4

startWith() includes() 从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值

let message = "foobarbaz";
console.log(message.startsWith("foo")); // true
console.log(message.startsWith("bar")); // false
console.log(message.includes("bar")); // true
console.log(message.includes("qux")); // false

(2) 转换方法 split 把字符串按照指定的分割符,拆分成数组中的每一项

let str = "12+23+34"
let arr = str.split("+") // [12,23,34]

(3) 模板匹配方法

针对正则表达式,字符串设计了几个方法:

  • match()
  • search()
  • replace()

match() 接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象,返回数组

let text = "cat, bat, sat, fat";
let pattern = /.at/;
let matches = text.match(pattern);
console.log(matches[0]); // "cat"

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

let text = "cat, bat, sat, fat";
let pos = text.search(/at/);
console.log(pos); // 1

replace() 接收两个参数,第一个参数为匹配的内容,第二个参数为替换的元素(可用函数)

let text = "cat, bat, sat, fat";
let result = text.replace("at", "ond");
console.log(result); // "cond, bat, sat, fat"
11 闭包

闭包:一个函数和它的周围状态的引用捆绑在一起的组合

函数可以访问到其他函数作用域当中的数据;

子函数可以访问其他函数作用域中的数据,称之为闭包

内存泄漏:子函数一直 引用 父函数 里的变量,可以一直访问,不会消耗。

(1)函数作为返回值

function test() {
  const a = 1
  return function () {
    console.log('a',a)
  }
}
const fn = test()
const a = 2
fn()     // a 1

(2) 函数作为参数

function test(fn) {
  const a = 1
  fn()
}
const a = 2
function fn() {
  console.log('a',a)
}
test(fn)   // a 2
12 作用域、作用域链

(1) 作用域

作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合

我们一般将作用域分成:

  • 全局作用域
  • 函数作用域
  • 块级作用域

全局作用域

任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问

// 全局变量
var greeting = 'Hello World!';
function greet() {
  console.log(greeting);
}
// 打印 'Hello World!'
greet();

函数作用域

函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问

function greet() {
  var greeting = 'Hello World!';
  console.log(greeting);
}
// 打印 'Hello World!'
greet();
// 报错:Uncaught ReferenceError: greeting is not defined
console.log(greeting);

块级作用域

ES6引入了letconst关键字,和var关键字不同,在大括号中使用letconst声明的变量存在于块级作用域中。在大括号之外不能访问这些变量

{
  // 块级作用域中的变量
  let greeting = 'Hello World!';
  var lang = 'English';
  console.log(greeting); // Prints 'Hello World!'
}
// 变量 'English'
console.log(lang);
// 报错:Uncaught ReferenceError: greeting is not defined
console.log(greeting);

(2) 作用域链

当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域

如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错

13 Javascript中this

(1) this

this:当前环境下,当前对象的引用

如果这个函数是对象的属性,或者叫类方法,这时的this就是当前的对象;

如果这个是普通的函数,这时的 this 就是全局对象,即Window

image.png

(2) 通过常量改变 this 指针 image.png

(3) 箭头函数中的this

image.png

image.png

14 # bind、call、apply

callapplybind作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this指向

(1) apply

apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入

改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次

function fn(...args){
    console.log(this,args);
}
let obj = {
    myname:"张三"
}

fn.apply(obj,[1,2]); // this会变成传入的obj,传入的参数必须是一个数组;
fn(1,2) // this指向window

当第一个参数为nullundefined的时候,默认指向window(在浏览器中)

fn.apply(null,[1,2]); // this指向window
fn.apply(undefined,[1,2]); // this指向window

(2) call call方法的第一个参数也是this的指向,后面传入的是一个参数列表

apply一样,改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次

function fn(...args){
    console.log(this,args);
}
let obj = {
    myname:"张三"
}

fn.call(obj,1,2); // this会变成传入的obj,传入的参数必须是一个数组;
fn(1,2) // this指向window

同样的,当第一个参数为nullundefined的时候,默认指向window(在浏览器中)

fn.call(null,[1,2]); // this指向window
fn.call(undefined,[1,2]); // this指向window

(3) bind

bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)

改变this指向后不会立即执行,而是返回一个永久改变this指向的函数

function fn(...args){
    console.log(this,args);
}
let obj = {
    myname:"张三"
}

const bindFn = fn.bind(obj); // this 也会变成传入的obj ,bind不是立即执行需要执行一次
bindFn(1,2) // this指向obj
fn(1,2) // this指向window

image.png

从上面可以看到,applycallbind三者的区别在于:

  • 三者都可以改变函数的this对象指向
  • 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefinednull,则默认指向全局window
  • 三者都可以传参,但是apply是数组,而call是参数列表,且applycall是一次性传入参数,而bind可以分为多次传入
  • bind是返回绑定this之后的函数,applycall 则是立即执行
15 new操作符到底做了什么

new操作符用于创建一个给定构造函数的实例对象

  • a.创建一个空对象
  • b. 把空对象的原型指向构造函数的原型
  • c.改变this指向
  • d.返回这个对象
16 Ajax

image.png

image.png

17 JavaScript中本地存储的方式有哪些?

javaScript本地缓存的方法我们主要讲述以下四种:

  • cookie
  • sessionStorage
  • localStorage
  • indexedDB

(1) cookie

Cookie,类型为「小型文本文件」,指某些网站为了辨别用户身份而储存在用户本地终端上的数据。是为了解决 HTTP无状态导致的问题

作为一段一般不超过 4KB 的小型文本数据,它由一个名称(Name)、一个值(Value)和其它几个用于控制 cookie有效期、安全性、使用范围的可选属性组成

但是cookie在每次请求中都会被发送到服务器,如果不使用 HTTPS并对其加密,其保存的信息很容易被窃取,导致安全风险。举个例子,在一些使用 cookie保持登录态的网站上,如果 cookie被窃取,他人很容易利用你的 cookie来假扮成你登录网站

  • Cookie 的基本用法 写入cookie

不能一起设置,只能一个一个设置: document.cookie="name=aaa;age=13" //这样是错误的

document.cookie="name=aaa" 
document.cookie="age=13"

读取cookie

读取的是一个由名值对构成的字符串,每个名值对之间由“;”(一个分号和一个空格)隔开

console.log(document.cookie) // name=aaa; age=13
  • Cookie 的属性1——失效时间

对于失效的Cookie,会被浏览器清除;

对于没有设置失效的Cookie,称为活Cookie,当会话结束,也就是浏览器关闭时,Cookie消失。

长时间存在,需要设置 Expires 或 Max-Age

Expires 值为 Date 类型

document.cookie="name=aaa;expires=${new Date('2100-1-01 00:00:00')}" 

Max-Age 用于设置在 Cookie 失效之前需要经过的秒数(优先级比Expires高);

Max-Age 值为数字,表示当前时间 + 多少秒后过期,单位是秒;

如果 Max-Age 的值为0 或是负数,则 Cookie 会被删除。

document.cookie="name=aaa;max-age=5" 
  • Cookie 的属性2——Domain 域

Domain指定了 Cookie 可以送达的主机名

只能读写当前的父域的 Cookie,无法读写其他域的 Cookie

document.cookie="name=aaa;domain=www.imooc.com" 
  • Cookie 的属性3——Path 路径

Path 路径 限定了访问 Cookie 的 范围 (同一个域名下)

只能读写当前路径 和上级路径的 Cookie,无法读写下级路径的 Cookie

当 Name、Domain、Path 这3个字段都相同的时候,才是同一个 Cookie

document.cookie="name=aaa;path=/course/list" 
  • Cookie 的属性4——HttpOnly

设置了 HttpOnly 属性的 Cookie 不能通过JS去访问

  • Cookie 的属性5——Secure Secure 限定了 只有 在使用 https 而不是 http 的情况下才可以发送到服务器。

(2) localStorage

(2.1)特点

  • 生命周期:持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的
  • 存储的信息在同一域中是共享的
  • 当本页操作(新增、修改、删除)了localStorage的时候,本页面不会触发storage事件,但是别的页面会触发storage事件。
  • 大小:5M(跟浏览器厂商有关系)
  • localStorage本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡
  • 受同源策略的限制
  • 它只是储存在本地,不会发送到服务器端。

(2.2)localStorage的基本用法:

1)设置setItem() ;2)获取 getItem() ;3)删除removeItem() ;4)一键删除全部clear()

//1)设置setItem()
localStorage.setItem('username','alex')

//有长度 length
console.log(localStorage.length)

//获取 getItem()
console.log(localStorage.getItem('username'))

//获取不存在的返回 null
console.log(localStorage.getItem('ftyj')) // null

//删除removeItem()
localStorage.removeItem('username')

//删除不存在的key,不报错
localStorage.removeItem('8952vfjjvj')

//一键删除全部clear()
 localStorage.clear()

(2.3)localStorage 也不是完美的,它有两个缺点:

  • 无法像Cookie一样设置过期时间
  • localStorage存储的键和值只能是字符串类型;不是字符串类型,也会自动先转换为字符串再存进去。
localStorage.setItem({},18)
console.log(typeof localStorage.getItem('[object Object]'))
console.log(localStorage.getItem('[object Object]'))
console.log({}.toString())

(3) sessionStorage

sessionStorage和 localStorage使用方法基本一致,唯一不同的是生命周期,一旦页面(会话)关闭,sessionStorage 将会删除数据

(4) 区别

关于cookiesessionStoragelocalStorage三者的区别主要如下:

  • 存储大小:cookie数据大小不能超过4ksessionStoragelocalStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大

  • 有效时间:localStorage存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;sessionStorage数据在当前浏览器窗口关闭后自动删除;cookie设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭

  • 数据与服务器之间的交互方式,cookie的数据会自动的传递到服务器,服务器端也可以写cookie到客户端;sessionStoragelocalStorage不会自动把数据发给服务器,仅在本地保存

(5) 应用场景

  • 标记用户与跟踪用户行为的情况,推荐使用cookie
  • 适合长期保存在本地的数据(令牌),推荐使用localStorage
  • 敏感账号一次性登录,推荐使用sessionStorage
  • 存储大量数据的情况、在线文档(富文本编辑器)保存编辑历史的情况,推荐使用indexedDB
18 DOM

文档对象模型 (DOM) 是 HTML 和 XML 文档的编程接口

下面就来分析DOM常见的操作,主要分为:

  • 创建节点
  • 查询节点
  • 更新节点
  • 添加节点
  • 删除节点

(1) 创建节点 createElement

createElement: 创建新元素,接受一个参数,即要创建元素的标签名

注意事项:

  • 新创建出的节点是“孤儿节点”,这意味着它并没有被挂载到DOM树上,我们无法看到它。

  • 必须继续使用 appendChild() 或 insertBefore() 方法将孤儿节点插入DOM数上。

appendChild() 追加: 任何已经在DOM树上的节点,都可以调用appendChild()方法,它可以将孤儿节点挂载到它的内部,成为它的最后一个子节点。

<div id="box1">
    <p>我是原本的段落0</p>
    <p>我是原本的段落1</p>
    <p>我是原本的段落2</p>
</div>
<script>
    var box = document.getElementById('box1')
    // 创建节点,孤儿节点
    var op = document.createElement('p')
    // 设置内部文字
    op.innerText = "我是新来的段落"
    // 上树
    box.appendChild(op)
</script>

insertBefore(): 任何已经在DOM树上的节点,都可以调用insertBefore()方法,它可以将孤儿节点挂载到它的内部,成为它的“标杆子节点”之前的节点。

父节点.insertBefore(孤儿节点,标杆子节点 )

<div id="box1">
    <p>我是原本的段落0</p>
    <p>我是原本的段落1</p>
    <p>我是原本的段落2</p>
</div>
<script>
    var box = document.getElementById('box1')
    var p = document.getElementsByTagName('p')
    
    // 创建节点,孤儿节点
    var op = document.createElement('p')
    // 设置内部文字
    op.innerText = "我是新来的段落"
    
    // 上树
    box.insertBefore(op,p[1])
</script>

(2) 查询节点

image.png

querySelector

  • 传入任何有效的css 选择器,即可选中单个 DOM元素(首个);
  • 如果页面上没有指定的元素时,返回 null
document.querySelector('.element')
document.querySelector('#element')
document.querySelector('div')
document.querySelector('[name="username"]')
document.querySelector('div + p > span')

querySelectorAll

  • 返回一个包含节点子树内所有与之相匹配的Element节点列表,如果没有相匹配的,则返回一个空节点列表

  • 需要注意的是,该方法返回的是一个 NodeList的静态实例,它是一个静态的“快照”,而非“实时”的查询

const notLive = document.querySelectorAll("p");

(3) 更新节点

innerHTML不但可以修改一个DOM节点的文本内容,还可以直接通过HTML片段修改DOM节点内部的子树

// 获取<p id="p">...</p>
var p = document.getElementById('p');
// 设置文本为abc:
p.innerHTML = 'ABC'; // <p id="p">ABC</p>
// 设置HTML:
p.innerHTML = 'ABC <span style="color:red">RED</span> XYZ';
// <p>...</p>的内部结构已修改

innerText、textContent自动对字符串进行HTML编码,保证无法设置任何HTML标签;

两者的区别在于读取属性时,innerText不返回隐藏元素的文本,而textContent返回所有文本;

innerHTML属性能以HTML语法设置节点中的内容

innerText属性只能以纯文本的形式设置节点中的内容

// 获取<p id="p-id">...</p>
var p = document.getElementById('p-id');
// 设置文本:
p.innerText = '<script>alert("Hi")</script>';
// HTML被自动编码,无法设置一个<script>节点:
// <p id="p-id">&lt;script&gt;alert("Hi")&lt;/script&gt;</p>

style

DOM节点的style属性对应所有的CSS,可以直接获取或设置。遇到-需要转化为驼峰命名

// 获取<p id="p-id">...</p>
const p = document.getElementById('p-id');
// 设置CSS:
p.style.color = '#ff0000';
p.style.fontSize = '20px'; // 驼峰命名
p.style.paddingTop = '2em';

(4) 删除节点

删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild把自己删掉;

删除后的节点虽然不在文档树中了,但其实它还在内存中,可以随时再次被添加到别的位置

// 拿到待删除节点:
const self = document.getElementById('to-be-removed');
// 拿到父节点:
const parent = self.parentElement;
// 删除:
const removed = parent.removeChild(self);
removed === self; // true
19 事件的传播

实际上,事件的传播:先从外到内,然后再从内到外。先捕获阶段,再冒泡阶段

(1) onxxx写法只能监听到冒泡阶段

// 运行后的结果是:从内到外的顺序,冒泡阶段
// box3事件
// box2事件
// box1事件
<div id="box1">
    <div id="box2">
        <div id="box3"></div>
    </div>
</div>
<script>
    var box1 = document.getElementById('box1')
    var box2 = document.getElementById('box2')
    var box3 = document.getElementById('box3')
    
    box1.onclick = function(){
        console.log("box1事件")
    }
    box2.onclick = function(){
        console.log("box2事件")
    }
    box3.onclick = function(){
        console.log("box3事件")
    }
</script>

(2) DOM2级事件监听:addEventListener

DOM0级事件监听,只能监听冒泡阶段。

box.onclick = function(){}

DOM2级事件监听:addEventListener

box.addEventListener('click',function(){},true)

addEventListener的参数 true表示监听捕获阶段,false表示监听冒泡阶段。

// 运行后的结果是:从外到内,捕获阶段
// box1事件
// box2事件
// box3事件
<div id="box1">
    <div id="box2">
        <div id="box3"></div>
    </div>
</div>
<script>
    var box1 = document.getElementById('box1')
    var box2 = document.getElementById('box2')
    var box3 = document.getElementById('box3')
    
    box1.addEventListener('click',function(){console.log("box1事件")},true)
    box2.addEventListener('click',function(){console.log("box2事件")},true)
    box3.addEventListener('click',function(){console.log("box3事件")},true)
</script>

(3)注意事项:

最内部的元素不再区分捕获和冒泡阶段,会先执行写在前面的监听,然后执行后写的监听。

如果给元素设置相同或多个同名事件,则 DOM0 级写法后面写的会覆盖先写的DOM2 级会按顺序执行

// 允许结果:依次弹出bb、C、DD
<div id="box1">
    <div id="box2">
        <div id="box3"></div>
    </div>
</div>
<script>
    var box1 = document.getElementById('box1')
    var box2 = document.getElementById('box2')
    var box3 = document.getElementById('box3')
    
    box3.onclick = function() {
        alert("a")
    }
    box3.onclick = function() {
        alert("bb")
    }
    
    box3.addEventListener('click',function(){alert("C")},false)
    box3.addEventListener('click',function(){alert("DD")},false)
</script>
20 web常见的攻击方式

Web攻击(WebAttack)是针对用户上网行为或网站服务器等设备进行攻击的行为

如植入恶意代码,修改网站权限,获取网站用户隐私信息等等

我们常见的Web攻击方式有

  • XSS (Cross Site Scripting) 跨站脚本攻击
  • CSRF(Cross-site request forgery)跨站请求伪造
  • SQL注入攻击

(1.1) XSS

XSS,跨站脚本攻击,允许攻击者将恶意代码植入到提供给其它用户使用的页面中

XSS涉及到三方,即攻击者、客户端与Web应用

XSS的攻击目标是为了盗取存储在客户端的cookie或者其他网站用于识别客户端身份的敏感信息。一旦获取到合法用户的信息后,攻击者甚至可以假冒合法用户与网站进行交互

(1.2) XSS 预防

XSS攻击的两大要素:

  • 攻击者提交而恶意代码
  • 浏览器执行恶意代码

针对第一个要素,我们在用户输入的过程中,过滤掉用户输入的恶劣代码,然后提交给后端,但是如果攻击者绕开前端请求,直接构造请求就不能预防了

而如果在后端写入数据库前,对输入进行过滤,然后把内容给前端,但是这个内容在不同地方就会有不同显示

二、ES6

1 var、let、const之间的区别

(1) var

  • 在ES5中,顶层对象的属性和全局变量是等价的,用var声明的变量既是全局变量,也是顶层变量

注意:顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象

var a = 10;
console.log(window.a// 10
  • 使用var声明的变量存在变量提升的情况
console.log(a) // undefined
var a = 20

在编译阶段,编译器会将其变成以下执行

var a
console.log(a)
a = 20
  • 使用var,我们能够对一个变量进行多次声明,后面声明的变量会覆盖前面的变量声明
var a = 20 
var a = 30
console.log(a) // 30
  • 在函数中使用使用var声明变量时候,该变量是局部的
var a = 20
function change(){
    var a = 30
}
change()
console.log(a) // 20

如果在函数内不使用var,该变量是全局

var a = 20
function change(){
   a = 30
}
change()
console.log(a) // 30

(2) let

letES6新增的命令,用来声明变量

  • 用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效
{
    let a = 20
}
console.log(a) // ReferenceError: a is not defined.
  • 不存在变量提升

这表示在声明它之前,变量a是不存在的,这时如果用到它,就会抛出一个错误

console.log(a) // 报错ReferenceError
let a = 2
  • 使用let声明变量前,该变量都不可用,也就是大家常说的“暂时性死区

只要块级作用域内存在let命令,这个区域就不再受外部影响

var a = 123
if (true) {
    a = 'abc' // ReferenceError
    let a;
}
  • let不允许在相同作用域中重复声明
let a = 20
let a = 30
// Uncaught SyntaxError: Identifier 'a' has already been declared

注意的是相同作用域,下面这种情况是不会报错的

let a = 20
{
    let a = 30
}

因此,我们不能在函数内部重新声明参数

function func(arg) {
  let arg;
}
func()
// Uncaught SyntaxError: Identifier 'arg' has already been declared

(3) const

  • const声明一个只读的常量,一旦声明,常量的值就不能改变
const a = 1
a = 3
// TypeError: Assignment to constant variable.
  • 这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值
const a;
// SyntaxError: Missing initializer in const declaration
  • 如果之前用varlet声明过变量,再用const声明同样会报错
var a = 20
let b = 20
const a = 30
const b = 30
// 都会报错
  • const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动

对于简单类型的数据,值就保存在变量指向的那个内存地址,因此等同于常量

对于复杂类型的数据,变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的,并不能确保改变量的结构不变

const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

其它情况,constlet一致

(4) 区别

varletconst三者区别可以围绕下面五点展开:

  • 变量提升
  • 暂时性死区
  • 块级作用域
  • 重复声明
  • 修改声明的变量
  • 使用

(4.1) 变量提升

var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined

letconst不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错

(4.2) 暂时性死区

var不存在暂时性死区

letconst存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量

(4.3) 块级作用域

var不存在块级作用域

letconst存在块级作用域

(4.4) 复声明变量

var允许重复声明变量

letconst在同一作用域不允许重复声明变量

(4.5) 修改声明的变量

varlet可以

const声明一个只读的常量。一旦声明,常量的值就不能改变

(4.6)使用

能用const的情况尽量使用const,其他情况下大多数使用let,避免使用var

2 Promise,async和await

(1) Promise状态

promise对象仅有三种状态

  • pending(进行中)
  • fulfilled(已成功)
  • rejected(已失败)

Promise特点

  • 对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态
  • 一旦状态改变(从pending变为fulfilled和从pending变为rejected),就不会再变,任何时候都可以得到这个结果

Promise用法

Promise对象是一个构造函数,用来生成Promise实例

const promise = new Promise(function(resolve, reject) {});

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject

  • resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”
  • reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”

(2) 这个方式的缺点是: 代码的耦合程度太高,connected 和 disconnected 这两个函数的名称是固定的,而且直接写在 callSteven() 函数里面

image.png

(3) 虽然这种写法解耦了,但是可读性差

image.png

(4) 接下来改用 Promise 写法

.then 、 .catch() 写法 image.png

(5) async、await

try 里面是 resolve()的结果;

catch 里面 是 reject() 的结果

  try {

  } catch (e) {
    
  }

image.png

await 这个关键词的代码,只能在一个定义为 async的函数内执行

  async function action(params) {
    try {

    } catch (e) {
      
    }
  }

  action()

最后代码:

  function callSteven(success) {
    return new Promise((resolve,reject) => {
      console.log('Calling')
      setTimeout(() => {
        if(success) {
          resolve()
        }else {
          reject()
        }
      }, 3000)
    })
  }


  async function action(params) {
    try {
      await callSteven(true)
      console.log('Steven pick up the phone')
    } catch (e) {
      console.log('Steven reject the phone')
    }
  }

  action()

改为IIFE的写法,匿名函数然后立即执行

  (async () => {
    try {
      await callSteven(true)
      console.log('Steven pick up the phone')
    } catch (e) {
      console.log('Steven reject the phone')
    }
  })()
3 ES6中新增的Set

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

集合:是由一堆无序的、相关联的,且不重复的内存结构【数学中称为元素】组成的组合;

集合是以 [值,值] 的形式存储元素

Set本身是一个构造函数,用来生成 Set 数据结构

const s = new Set();

(1) 增删改查

Set的实例关于增删改查的方法:

  • add()
  • delete()
  • has()
  • clear()

add() 添加某个值,返回 Set 结构本身; 当添加实例中已经存在的元素,set不会进行处理添加

s.add(1).add(2).add(2); // 2只被添加了一次

delete()删除某个值,返回一个布尔值,表示删除是否成功

s.delete(1)

has()返回一个布尔值,判断该值是否为Set的成员

s.has(2)

clear()清除所有成员,没有返回值

s.clear()

(2) 遍历

  • keys():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回键值对的遍历器
  • forEach():使用回调函数遍历每个成员

image.png

(3)扩展运算符和Set 结构相结合实现数组或字符串去重

// 数组
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)]; // [3, 5, 2]

// 字符串
let str = "352255";
let unique = [...new Set(str)].join(""); // '352'

(4) 类型转换

image.png

(5)交集 & 并集 & 差集

image.png

4 WeakSet

(1) WeakSet 语法

创建WeakSet实例

const ws = new WeakSet();

image.png

WeackSet只能成员只能是引用类型,而不能是其他类型的值

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 {{…}, {…}}

(2) 在APIWeakSetSet有两个区别

  • 没有遍历操作的API
  • 没有size属性

(3) 引用类型垃圾回收原理

image.png

(4) WeakSet弱引用特性

image.png

weakSet是弱引用,如果这个地址被垃圾回收了,那么weakSet是没有值的

image.png

5 # ES6中新增的 Map

Map是一种叫做字典的数据结构

字典: 是一些元素的集合。每个元素有一个称作key 的域,不同元素的key 各不相同

字典是以 [键,值] 的形式存储

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

Map本身是一个构造函数,用来生成 Map 数据结构

const m = new Map()

(1) 增删改查

  • size 属性
  • set()
  • get()
  • has()
  • delete()
  • clear()

(1.1) size 属性返回 Map 结构的成员总数

const map = new Map();
map.set('foo', true);
map.set('bar', false);

map.size // 2

(1.2) set()设置键名key对应的键值为value,然后返回整个 Map 结构

如果key已经有值,则键值会被更新,否则就新生成该键

同时返回的是当前Map对象,可采用链式写法

const m = new Map();

m.set('edition', 6)        // 键是字符串
m.set(262, 'standard')     // 键是数值
m.set(undefined, 'nah')    // 键是 undefined
m.set(1, 'a').set(2, 'b').set(3, 'c') // 链式操作

(1.3) get方法读取key对应的键值,如果找不到key,返回undefined

const m = new Map();

const hello = function() {console.log('hello');};
m.set(hello, 'Hello ES6!') // 键是函数

m.get(hello)  // Hello ES6!

(1.4) has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中

const m = new Map();

m.set('edition', 6);
m.set(262, 'standard');
m.set(undefined, 'nah');

m.has('edition')     // true
m.has('years')       // false
m.has(262)           // true
m.has(undefined)     // true

(1.5) delete方法删除某个键,返回true。如果删除失败,返回false

const m = new Map();
m.set(undefined, 'nah');
m.has(undefined)     // true

m.delete(undefined)
m.has(undefined)       // false

(1.6) clear方法清除所有成员,没有返回值

let map = new Map();
map.set('foo', true);
map.set('bar', false);

map.size // 2
map.clear()
map.size // 0

(2) 遍历

  • keys():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回所有成员的遍历器
  • forEach():遍历 Map 的所有成员

image.png

(3) Map类型转换

image.png

image.png

6 WeakMap

(1) WeakMap结构与Map结构类似,也是用于生成键值对的集合

APIWeakMapMap有两个区别:

  • 没有遍历操作的API
  • 没有clear清空方法

(2) WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名

image.png

(3) WeakMap是弱引用类型和前面的WeakSet一样

image.png