【JavaScript 相关知识】 - 前端面试必备 -复习专用

在这里插入图片描述

1、数据类型和变量

1.1 JS有哪些数据类型(区别)

基本数据类型:字符串string、数字number、布尔boolean、未定义undefined、空null、符号symbol、大整数bigint
引用数据类型:Object对象:数组Array、函数Function、正则RegExp、日期Date等

  • 两种类型的区别在于存储位置不同

基本数据类型存储在中,占据空间小,大小固定,变量中存储的是值本身,变量赋值时是把变量的值复制一份去赋值。
引用数据类型存储在中,占据空间大,大小不固定,变量存储的是指向堆内存的地址,变量赋值是把变量内存地址复制一份去赋值

1.2 判断数据类型方式

  • typeof(返回值是字符串类型)

typeof 判断基本数据类型时,除了 null 的输出结果为object,其它类型判断正确;
typeof 判断引用数据类型时,除了判断函数正确会输出function,其它都输出object。 typeof并不能区分应用数据类型。

  • instanceof(可以正确判断对象类型,返回是一个布尔值) 常用来判断两个对象是否属于实例关系;
    原理是:检测构造函数的 prototype 属性是否在某个实例对象的原型链上

在这里插入图片描述

  • Constructor() 两个作用,一是判断数据的类型,二是对象实例通过 constrcutor 对象访问它的构造函数
    在这里插入图片描述
  • Object. prototype. toString. call( ) 使用 Object 对象的原型方法 toString
    来判断数据类型
    在这里插入图片描述

1.3 判断数组的方式有哪些

  • instanceof
  • Object. prototype. toString. call()
  • ES6的Array.isArray()
  • 通过原型链做判断 obj.proto === Array.prototype

1.4 null和undefined区别

在这里插入图片描述

1.5 typeof(null) 结果是什么,为什么?

1.6 typeof(NaN) 结果是什么,为什么?

在这里插入图片描述

1.7 0.1 + 0.2 !== 0.3,如何让其相等 ?

在这里插入图片描述

1.8 == 操作符的强制类型转换规则 ===

在这里插入图片描述

1.9 Js 中隐式类型转换

在这里插入图片描述

2、ES6

2.1 var let const 区别

变量提升:是指使用 var 关键字声明的变量会自动提升到当前作用域的最前面。不过只会提升声明,不会提升其初始化(变量声明初始化,默认值是undefined)

var(ES5变量)、 let(ES6变量) 、 const(ES6常量)
推荐使用const,比如声明的变量用来保存对象、数组函数等引用类型 + 不变的数值; 其他情况使用let(一般计算的变量)
在这里插入图片描述

const 定义的值一定不变吗

在这里插入图片描述

2. 模板字符串

在ES6之前,拼接字符串非常麻烦。ES6新增了模板语法, tab键 上面 那个键 `` 使用起来非常方便。

模板字符串:运行使用 ${ } 的方式嵌入变量 ,关键优势如下:

  • 模板字符串的空格,换行,缩进都会被保留
  • 模板字符串完全支持运算式的表达式,可以在 ${ } 里完成一些计算

除了模板语法外,ES6新增了一系列字符串方法用于提升开发效率

  • 存在性判定
    includes:判断字符串与子串的包含关系
    startsWith:判断字符串是否以某个/某串字符开头
    endsWith:判断字符串是否以某个/某串字符结尾
  • 自动重复
    使用repeat方法使同一个字符输出多次

3. 箭头函数

function(){}  //普通函数
() => {}  //箭头函数
// ( )中定义参数: 如果参数只有一个参数, 可以不用写小括号
// { }中定义函数体,如果函数体中只有一行返回值, 可以不写 return 和 { }

//注意!!
//如果箭头函数返回的是一个字面量对象,则需要用括号包裹该字面量对象返回
let foo = (name) => ({
    name,
    job: 'front end'
})
箭头函数和普通函数 区别

函数内部有一个特殊的对象叫 arguments, 这个对象类似数组,这样给函数传入参数的时候,就可以像数组一样调用数组的元素和属性了。

  • 语法格式
    普通函数:格式用 function,可以是函数声明或者是函数表达式
    箭头函数:格式用箭头,只能是函数表达式

  • new和原型prototype
    普通函数:可以用作构造函数,可以调用 new ,有原型prototype
    箭头函数:是匿名函数,不是构造函数,不能调用new ,没有原型prototype

  • arguments
    普通函数:有arguments对象
    箭头函数:箭头函数处于全局作用域中,则没有arguments,但是可以调用外层普通函数的arguments对象;

  • super new.target
    普通函数:有
    箭头函数:没有super 和 new.target绑定,和arguments一样,super 和 new.target的值由最近那一层包围的普通函数所决定,可以间接调用

  • this 指向
    普通函数: this 值是动态的; 谁调用这个函数,this 指向谁
    箭头函数: this 值是固定的;箭头函数没有自己的this, 一般是全局对象 window, 如果被普通函数包围,this指向最近普通函数的 this 指向

  • call apply bind
    普通函数:可以用 call apply bind 来修改 this 指向
    箭头函数:不受这三个方法的控制,不可修改this的值
    在这里插入图片描述

箭头函数可以new吗

箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数

3. 数组字符串相关

3.1 数组去重

方法一:ES6 Set去重

function unique(arr){
    //Set去重后转换成真正的数组
    //return Array.from(new Set(arr))
    return [...new Set(arr)]
}
const arr = [1,2,2,2,6,19,10,8,6];
console.log(unique(arr)) //[1,2,6,19,10,8]

方法二:indexOf / includes去重

function unique(arr){
    //Array.isArray()判断是否属于数组类型; 传进来数组,返回true,否则返回false
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    //准备结果数组
    const result=[];
    //循环
    //indexOf返回某个指定的字符串值在字符串中首次出现的位置。没找到匹配的字符串返回 -1
    arr.forEach((item) => {
        //if(!result.includes(item)){ result.push(item)}
        if(result.indexOf(item) === -1){
            result.push(item)
        }
    });
    return result; //返回结果数组
}
const arr = [1,2,2,NaN,6,NaN,19,10,8,6,{},{}];
// [1, 2, NaN, 6, NaN, 19, 10, 8, {}, {}] 
// indexOf方法  NaN、{}没有去重
// includes方法 {}没有去重
console.log(unique(arr))

3.2 手写深拷贝

function deepClone(obj, map=new WeakMap()){
    if(typeof obj!=='object' || obj==null)  return obj; //判断是不是对象
    if(obj instanceof Date) return new Date(obj); //日期值
    if(obj instanceof RegExp) return new RegExp(obj); //正则
    let newObj = (obj instanceof Array) ? [] : {};
    //let newObj = new obj.constructor() //创建一个和obj类型一样的对象

    if(map.get(obj)) return map.get(obj); //防止循环引用
    map.set(obj,newObj);//放入缓存中

    //循环+递归
    for (let i in obj) {
        if(obj.hasOwnProperty(i)){
            newObj[i] = deepClone(obj[i],map);
        }
    }
    return newObj;
}

4. 防抖和节流

防抖和节流都是在高频事件中防止函数被多次调用,是一种性能优化的方案。

防抖是某一段时间只执行一次,而函数节流是间隔时间执行。

辅助理解: 节流防抖就好比乘电梯,比如delay是10秒,防抖就是电梯每进来一个人就要等10秒再运行,而节流就是电梯保证每10秒可以运行一次。

4.1 防抖(debouce)

原理:
通过setTimeout的方式,在一定事件间隔内(n秒之后),将多次触发变成一次触发。在事件触发n秒后再执行回调,如果在n秒内再次触发,则重新计算时间。

主要应用场景:

  • 搜索框输入查询:防止用户不断输入过程中,不断请求资源,等用户停止输入的时候,再调用,节约请求资源
  • 按钮提交事件/表单验证:比如点赞,表单提交,防止多次提交
  • 浏览器窗口缩放,resize事件:不断调整浏览器窗口大小会不断的触发这个事件,用防抖让其只触发一次
  • scroll事件滚动触发

4.2 节流(throttle)

原理:
规定在一个单位时间内,只能触发一次函数。 如果这个单位时间内触发多次函数,只有一次生效。

主要应用场景

  • 监听滚动scroll事件,是否滑到底部自动加载更多
  • 鼠标不断点击触发,mousedown(单位时间内只触发一次)
  • 计算鼠标移动的距离(mousemove)
  • DOM元素的拖拽功能实现(mousemove)
  • 搜索联想(keyup)

5. 原型和原型链

5.1 解释原型和原型链

  • 创建的每一个函数都有一个prototype(原型)属性,被称为显示原型, 这个属性是一个指针,指向一个对象(原型对象)。 原型对象默认拥有一个constructor 属性,指向它的构造函数,原型的属性值是一个普通对象。

  • 每个对象实例都有一个隐藏的属性__proto__,被称为隐式原型, 指向它的构造函数的原型

  • 对象实例可以共享原型上面的所有属性和方法

  • prototype应用:扩展对象

  • 每个构造函数都有显式原型 prototype

  • 每个实例对象都有隐式原型 __proto__

  • 实例对象的__proto__指向函数的prototype

实例对象.__proto__ ===  函数对象.prototype   结果为true
  • 原型链:实例对象在访问一个对象的属性和方法时,先在自身找,找不到就去隐式原型上面找(即它的构造函数的prototype),如果还是找不到,就会往原型的原型上去找(即构造函数的prototype的__proto__),以此类推,一直到最顶层 Object.prototype.__proto__为null结束(终点)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

如何获取对象非原型链上的属性:hasOwnProperty
在这里插入图片描述

在这里插入图片描述

5.2 JS继承

在这里插入图片描述
JavaScript中如何实现继承

  • 实现继承的关键在于:子类必须拥有父类的全部属性和方法,同时子类还应该能定义自己独有的属性和方法
  • 在ES5及之前,使用原型链特性来实现继承,是普遍的做法
  • 在ES6中,使用class实现继承, extends super
class Person {
    constructor(name){
        this.name=name
    }
    drink(){
        console.log('喝水')
    }
}
class Student extends Person{
    constructor(name,score){
        super(name)
        this.score=score
    }
    introduce(){
        console.log(`我是${this.name},考了${this.score}`)
    }
}
class Teacher extends Person{
    constructor(name,subject){
        super(name)
        this.subject=subject
    }
    teach(){
        console.log(`我是${this.name},教${this.subject}`)
    }
}
const student=new Student('zy',100)
student.introduce() //我是zy,考了100分
//不仅可以继承属性还可以继承方法
student.drink() //喝水
const teacher=new Teacher('zy','前端学习')
teacher.teach() //我是zy,教前端学习
teacher.drink()

6. 作用域和闭包

6.1 作用域

作用域(Scope)就是变量与函数的可访问范围,作用域控制着变量与函数的可见性和生命周期。

1. 全局作用域
全局作用域在页面打开时创建,页面关闭时销毁。

  • 最外层函数和最外层函数外面定义的变量
  • 所有未定义直接赋值的变量
  • 所有全局对象window的属性
  • 弊端:过多的全局作用域变量会污染全局命名空间,容易引起命名冲突

2. 函数作用域

  • 声明在函数内部的变量
  • 函数被调用时创建函数作用域,函数执行完毕后,函数作用域被销毁
  • 函数里能访问函数外变量,反之不行(闭包除外)

3. 块级作用域

  • ES6中新增的 let和 const指令可以声明块级作用域
  • 块级作用域可以在函数内部创建也可以在一个代码块内部(由一对花括号包裹)创建
  • let和const声明的变量不会有变量提升,也不可以重复声明
  • 在循环中比较适合绑定块级作用域,在if或者for等语句执行完后,变量就会销毁,不占用内存

6.2 作用域链

作用域链:变量查找的机制
当所需变量在自己当前作用域中查找不到的时候,它会一层一层向上父级作用域查找,直到找到全局作用域window还没有找到的时候,就会放弃查找。这一层层关系就是作用域链。

作用域链作用:保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数。

  • 作用域最为重要的一点是安全。变量只能在特定的区域内才能被访问,外部环境不能访问内部环境的任何变量和函数,即可以向上搜索,但不可以向下搜索
  • 作用域能够减轻命名的压力。我们可以在不同的作用域内定义相同的变量名,并且这些变量名不会产生冲突

6.3 闭包

闭包是指有权访问另一个函数作用域中变量的函数,
创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。
闭包:函数嵌套函数 + 内部函数就是闭包
闭包的形成条件:函数嵌套 + 内部函数引用外部函数的局部变量

function outerFun(){
	let a=10
	function innerFun(){
	   console.log(a)
	}
	return innerFun()
}
闭包的用途(读取函数内部变量、让变量始终保存在内存中)
  • 使我们在函数外部能够访问到函数内部的变量,可以使用这种方法创建私有变量
  • 使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。
    正常情况下,函数执行完成,内部变量会销毁(释放内存空间)。(闭包 --> 内部函数没有执行完成,外部函数变量不会被销毁)
  • 闭包可以实现模块化:封装一段代码
闭包的缺点
  • 不能滥用闭包,否则会造成网页的性能问题,严重时可能会导致内存泄漏
    内存泄漏是指程序中已动态分配的内存由于某种原因未释放或无法释放;
    所谓内存泄漏在很多低级别的浏览器(比如 IE 中)中才会出现,高版本的浏览器基本不存在。
闭包里面的变量为什么不会被垃圾回收
  • JS会自动回收不再使用的变量,释放其所占的内存,开发人员不需要手动做垃圾回收的处理。
    垃圾回收机制,其工作是跟踪内存的分配和使用,以便发现不再需要的内存并对齐进行释放。目前浏览器基本使用标记清除和引用计数两种垃圾回收策略。

  • 如果闭包函数的引用计数为 0 时,函数就会释放,它引用的变量也会被释放

  • 当闭包函数的引用计数不为 0 时,说明闭包函数随时有可能被调用,他被调用后,就会引用他在定义时所处的环境的变量。闭包中的变量就得一直需要在内存中,就不会被垃圾回收掉。

经典面试题:循环中使用闭包解决 var 定义函数的问题

for (var i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}
//因为setTimeout是异步函数,所以会先把循环全部执行完毕,这时候 i=6 ,所以会输出一堆 6

//解决办法有三种
//1. 使用闭包
for (var i = 1; i <= 5; i++) {  
   (function(j) {  
	    setTimeout(function timer() {      
			   console.log(j)    
			  }, j * 1000) 
			    })(i)
			  }
//首先使用了立即执行函数将 i 传入函数内部,这个时候值就被固定在了参数 j 上面不会改变
//当下次执行 timer 这个闭包的时候,就可以使用外部函数的变量 j,从而达到目的

//2.使用 setTimeout 的第三个参数,这个参数会被当成 timer 函数的参数传入
for (var i = 1; i <= 5; i++) {
  setTimeout(
    function timer(j) {
      console.log(j)
    },
    i * 1000,
    i
  )
}

//3.使用let 定义 i 来解决问题(最为推荐)
for (let i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}

7. 事件运行机制

7.1. 事件 + 事件模型

事件是用户操作网页时发生的交互动作(比如 click/move) / 网页本身的一些操作(文档加载,窗口滚动和大小调整)
事件被封装成一个 event 对象,包含了该事件发生时的所有相关信息( event 的属性)以及可以对事件进行的操作( event 的方法)。

事件是用户操作网页时发生的交互动作或者网页本身的一些操作,现代浏览器一共有三种事件模型:

  • DOM0 级事件模型
  • IE 事件模型:事件处理阶段 + 事件冒泡阶段
  • DOM2 级事件模型:事件捕获阶段 + 事件处理阶段 + 事件冒泡阶段

如何阻止事件冒泡:event.stopPropagation()
在这里插入图片描述

7.2 事件委托

事件委托的核心原理:给父节点添加侦听器,利用事件冒泡影响每一个子节点。

  1. 事件委托的概念
    本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点。
    父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件委托(事件代理)

  2. 事件委托的特点
    不必为每一个子元素都绑定一个监听事件,绑定到他的父层,这样减少内存消耗;
    动态新增的元素无需重新绑定事件;

  3. 事件委托的局限性
    比如 focus、blur 之类的事件没有事件冒泡机制,所以无法实现事件委托;
    mousemove、mouseout 这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,也不适合事件委托的;

  4. 事件委托的使用场景
    场景:给页面的所有的a标签添加click事件

document.addEventListener("click", function(e) {
	var node = e.target;
	while (node.parentNode.nodeName != "BODY") {
		if (node.nodeName == "A") {
			console.log("a");
			break;
		}
		node = node.parentNode;
	}
}, false);

7.3 同步和异步

同步指的前一个任务结束后再执行后一个任务,程序的执行顺序与任务的排列顺序是一致的、同步的;
异步指的是当一个进程在执行某个请求时,如果这个请求需要等待一段时间才能返回,这个时候进程会继续往下执行,不会阻塞等待消息的返回,当消息返回时系统再通知进程进行处理。

同步任务: 都在主线程上执行,形成一个执行栈(先进先出)。
异步任务: JS 的异步是通过回调函数实现的,相关回调函数添加到任务队列中(任务队列也称为消息队列),如计时器(setTimeout, setInterval)、ajax请求、读取文件。

7.4 事件循环

  • 为什么会有事件循环
    js 是单线程运行的,一个任务完成之后才能执行另一个任务,也就是说,同一个时间只能做一件事。 单线程会导致有很多任务需要排队一个个执行,如果某个任务执行时间太长,就会出现阻塞,为了解决这个问题,JS引入事件循环机制

  • 什么是事件循环
    主线程不断从任务队列中读取事件,这个过程是循环不断的,这种运行机制就叫事件循环(Event Loop)。任务队列可以分为宏任务队列和微任务队列

Event Loop 执行顺序

  • 首先执行同步代码(属于宏任务)

  • 当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行

  • 执行所有微任务

  • 当执行完所有微任务后,如有必要会渲染页面

  • 然后开始下一轮 Event Loop,执行宏任务中的异步代码

同步 --> process. nextTick --> 微任务 --> 宏任务 --> setImmediate(当前事件循环结束执行)

7.5 宏任务和微任务

  • 宏任务: script 脚本的执行、ajax、读取文件、setTimeout ,setInterval ,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲染等
  • 微任务: promise.then、await/async、node 中的 process.nextTick 、对 Dom 变化监听的MutationObserver
console.log(1)  //同步任务

setTimeout(()=>{ //异步任务宏任务
    console.log(2)
}, 0)

new Promise((resolve, reject)=>{
    console.log('new Promise')  // new Promise同步任务
    resolve()
}).then(()=>{  // //.then 异步任务微任务
    console.log('then')
})

console.log(3) //同步任务
// 同步任务 --> 异步任务(微任务 --> 渲染 --> 宏任务) 
// 1=> 

new Promise => 3 => then => 2
// 1 3 5 8 2 6 7 4
console.log(1) //同步代码
async function async1(){
	await async2()
	console.log(2) //await下面这个代码,可以当成then执行的代码
}
async function async2(){
	console.log(3)  //同步代码
}
asunc1()
setTimeout(function() {
	console.log(4)
},0)
new Promise(resolve => {
	console.log(5) //同步代码
	resolve()
}).then(function(){
	console.log(6)
	}).then(function(){
		console.log(7)
	})
console.log(8) //同步代码

8. 异步编程、Promise

8.1 异步编程的实现方式

JS中的异步机制

  • 回调函数: 多个回调函数嵌套的时候会造成回调地狱,函数间代码耦合度太高,不利于代码维护。
  • Promise: 可以将嵌套的回调函数作为链式调用,但是有时候会造成多个then的链式调用,会造成代码的语义不够明确。
  • generator:在函数的执行过程中,将函数的执行权转移出去,在函数外部还可以将执行权转移回来 。
  • async函数:是 generator 和 promise 实现的一个自动执行的语法糖,内部自带执行器。当函数内部执行到一个await语句的时候,如果语句返回一个promise对象,那么函数会等待promise对象的状态变成 resolve 后再继续往下执行。因此可以将异步逻辑转化成同步的顺序来书写,并且这个函数可以自动执行。

8.2 对Promise的理解

promise使得代码更简洁,解决了回调地狱。

  1. 抽象表达
    Promise 对象是JS进行异步编程新的解决方案,避免了回调地狱,比传统解决方案更合理更强大(旧方案是单纯使用回调函数)

  2. 具体表达
    语法:pomise是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例(自身有all reject resolve 这几个方法,原型上有then catch 等方法)
    功能: pomise对象用来封装一个异步操作并可以获取其 成功/ 失败的结果值

  3. 阮一峰解释
    所谓promise,简单来说就是一个容器 ,里面保存着某个未来才会结束的事件(通常是个异步操作)的结果。 从语法上说,promise是一个对象 ,可以获取异步操作的消息; Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

Promise的实例有三个状态

  • Pending(进行中) 不会触发then和catch
  • Resolved(已完成) 会触发后续的then回调函数
  • Rejected(已拒绝) 会触发后续的catch回调函数

Promise 的实例有两个过程

  • pending -> fulfilled : Resolved(已完成)
  • pending -> rejected:Rejected(已拒绝)

当把一件事情交给promise时候,它的状态就是pending,任务完成状态变成resolved,没有完成状态变成rejected。 注意:一旦从pending状态成为其他状态就永远不能更改状态了。

then正常返回 resolved,里面有报错则返回 rejected
catch正常返回 resolved,里面有报错则返回 rejected

总结:
Promise 对象是异步编程的一种解决方案,最早由社区提出。Promise 是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。一个 Promise 实例有三种状态,分别是pending、resolved 和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由 pending 转变 resolved 或者rejected 状态,并且状态一经改变,就凝固了,无法再被改变了

8.3 Pomise的基本用法

1. 创建Promise对象

ES5用 回调函数拿异步数据;
ES6用 .then拿异步数据,这样易于维护,不会出现回调地狱;

  • Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject
  • 使用new Promise()来创建promise对象,或者使用promise.resolve和promise.reject这两个方法
  • Promise.resolve(value)的返回值也是一个promise对象,resolve可以将异步数据传递出来,再通过 .then拿到异步数据
  • Promise.reject 也是new Promise的快捷形式,创建一个promise对象
const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
})

在这里插入图片描述

2. Promise方法

Promise有五个常用的方法:then()、catch()、all()、race()、finally

  • then()
    then方法返回的是一个新的Promise实例(不是原来那个Promise实例),因此可以采用链式写法,即then方法后面再调用另一个then方法,then可以拿异步数据。只有调用resolve才会执行then
  • catch()
    该方法指向reject的回调函数。catch方法还有一个作用,就是在执行resolve回调函数时,如果出现错误,抛出异常,不会停止运行,而是进入catch方法中
  • all()
    all方法可以完成并行任务, 它接收一个数组,数组的每一项都是一个promise对象。当数组中所有的promise的状态都达到resolved,all方法的状态就会变成resolved,如果有一个状态变成了rejected,那么all方法的状态就会变成rejected
  • race()
    race方法和all一样,接受的参数是一个每项都是promise的数组,但是与all不同的是,当最先执行完的事件执行完之后,就直接返回该promise对象的值。如果第一个promise对象状态变成resolved,那自身的状态变成了resolved;反之第一个promise变成rejected,那自身状态就会变成rejected
  • finally()
    该方法用于指定不管 Promise 对象最后状态如何,都会执行的操作

8.4 async / await

async/await 是基于Promise解决异步的最终方案, 以同步代码的方式执行异步;

从字面上来看,async是异步的简写,await则为等待,所以很好理解,async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成,语法上强制规定await只能出现在asnyc函数中。

async 用于声明异步函数。这个关键字可以用在函数声明、函数表达式、箭头函数和方法上。如果异步函数使用 return 关键字有返回值(没有 return 会返回 undefined),这个值会被 Promise.resolve() 包装成一个 Promise 对象(async 函数调用之后,返回一个 Promise 对象)。

await 可以暂停异步函数代码的执行,等待 Promise 解决。
await + promise对象 —> 可以直接拿到resolve传出来的数据(可以让异步的代码看起来像同步代码)

9. this/ call/ apply/ bind

9.1 this关键字

  • 全局作用域或者普通函数(全局函数)中this指向全局对象window(定时器计时器里面this指向window)(全局函数是window全局对象的方法)
  • 谁调用这个方法,这个方法的this指向谁(全局函数是window全局对象的方法)
  • 构造函数中的this,指向构造函数的实例(new创建的对象)
  • 箭头函数中的this,指向上下文函数的this指向(箭头函数没有this)
  • DOM事件中的this,指向触发事件的DOM对象
<body>
    <button>按钮</button>
    <script>
        console.log(this) //window  全局作用域this指向window
        function fn() {
            console.log(this) //window  全局函数/普通函数this指向window
        }
        fn()

        let student = {
            name: 'zy',
            sayName() {
                console.log('我是' + this.name) 
                //我是zy  方法中this指向调用者student
            }
        }
        student.sayName()

        const btn = document.querySelector("button")
        btn.onclick = function() {
            console.log(this) 
            //<button>按钮</button>   事件中this指向触发DON对象
        }

        //构造函数
        function F() {
            this.name = "zy" //this指向f    构造函数this指向构造实例
        }
        //new会创建对象,将构造函数中的this指向创建出来的对象
        let f = new F()
        console.log(f) // F {name: 'zy'}


        let cat = {
            name: '喵喵',
            sayName() {
            	console.log(this) //{name: '喵喵', sayName: ƒ}
            } 
            sayName() {
                console.log(this) //{name: '喵喵', sayName: ƒ}
                setTimeout(function() {
                	console.log(this) //window 计时器中this指向window
                }, 1000) 
                setTimeout(() => {
                	console.log(this)  //{name: '喵喵', sayName: ƒ}
                	}, 1000)
            }
        }
        cat.sayName()
    </script>
</body>

9.2 call

call是一个方法,函数的方法。call可以调用函数 + call可以改变函数中this指向

call 和 apply 作用一模一样,区别在于传参的形式不同
call 传入的参数不固定,第一个参数代表函数体内的this指向,从第二个参数开始往后,每个参数被依次传入函数。

9.2 apply

apply接受两个参数,第一个参数指定函数体内this对象指向,第二个参数为一个带下标的集合,这个集合可以为数组,可以为类数组,apply方法把这个集合中的元素作为参考传递给被调用的函数。

9.2 bind

语法和call一样,区别在于bind不会执行函数,只会返回

总结:

  • call、apply、bind的作用都可以改变函数this指向
  • call、apply区别在于传参形式不同,都是立即调用
  • call、bind区别在于,call会直接调用函数执行,bind不会立即执行,而是返回一个改变上下文this的函数(便于之后调用)

10. 垃圾回收

垃圾回收:JS代码运行时,需要分配内存空间来储存变量和值,当变量不再参与运行,就需要系统收回被占用的内存空间。JS具有自动垃圾回收机制,定期对那些不再使用的变量、对象占用的内存进行释放,原理就是找到不再使用的变量,然后释放内存。

回收方式:标记清除、引用计数

减少垃圾回收:虽然浏览器可以进行垃圾自动回收,但是当代码比较复杂,垃圾回收所带来的代价较大,所以应该尽量减少垃圾回收。

  • 对数组进行优化:清空数组,给其赋值[ ]
  • 对object进行优化:对象尽量复用,对于不再使用的对象,设置为null
  • 对函数进行优化:在循环中的函数表达式,如果可以复用,尽量放在函数的外面

内存泄漏

  • 意外的全局变量:使用未声明的变量,而意外创建一个全局变量
  • 被遗忘的计时器或回调函数:设置定时器忘了取消
  • 脱离DOM的引用:获取一个DOM元素的引用,而后面这个元素被删除
  • 闭包:不合理的使用闭包
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值