js高频面试题
本文中讲述到的面试题:
- 说说对闭包的认识, 它解决了什么问题?
- 跨域问题有哪些处理方式?
- for...in 和 for...of 的区别?
- new 一个对象, 这个过程中发生了什么?
- js 的防抖和节流是什么?
- 数组中常用的方法有哪些?
- 怎么判断一个 object 是否是数组?
- 继承有哪些方式?
- 说说 js 中 call,apply,bind 之间的关系?
- 你了解 promise 吗?
# 1.说说你对闭包的认识
“请讲一下你对闭包的认识”——这道题几乎是前端面试必问的问题,今天我试着总结一下如何优雅的回答这道题
# 什么是闭包
一句话解释:
能够读取其他函数内部变量的函数。
稍全面的回答:
在 js 中变量的作用域属于函数作用域, 在函数执行完后,作用域就会被清理,内存也会随之被回收,但是由于闭包函数是建立在函数内部的子函数, 由于其可访问上级作用域,即使上级函数执行完, 作用域也不会随之销毁, 这时的子函数(也就是闭包),便拥有了访问上级作用域中变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。
这里涉及到对函数作用域的认识:js 变量分为: 全局变量和局部变量;函数内部可以直接读取全局变量,而在函数外部自然无法读取函数内的局部变量; 原来 JavaScript 的闭包是这么回事这篇文职中作者详细的剖析了闭包.
# 闭包解决了什么问题
- 可以读取函数内部的变量
- 让这些变量的值始终保持在内存中。不会在函数调用后被清除
可以通过下面的代码来帮助理解上面所说的:
function addCounter() {
let counter = 0
const myFunction = function() {
counter = counter + 1
return counter
}
return myFunction
}
const increment = addCounter()
const c1 = increment()
const c2 = increment()
const c3 = increment()
console.log('increment:', c1, c2, c3)
// increment: 1 2 3
2
3
4
5
6
7
8
9
10
11
12
13
14
在这段代码中increment
实际上就是闭包函数myFunction
, 它一共运行了三次,第一次的值是 1,第二次的值是 2,第三次的值是 3。这证明了,函数addCounter
中的局部变量counter
一直保存在内存中,并没有在addCounter
调用后被自动清除。
# 闭包的应用场景
在开发中, 其实我们随处可见闭包的身影, 大部分前端 JavaScript 代码都是“事件驱动”的,即一个事件绑定的回调方法; 发送 ajax 请求成功|失败的回调;setTimeout 的延时回调;或者一个函数内部返回另一个匿名函数,这些都是闭包的应用。
下面是具体应用的栗子:
- 老掉牙的取正确值问题
for (var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i) //10个10
}, 1000)
}
2
3
4
5
怎么取到每一次循环的正确值呢? 闭包这样用:
for (var i = 0; i < 10; i++) {
;((j) => {
setTimeout(function() {
console.log(j) //1-10
}, 1000)
})(i)
}
2
3
4
5
6
7
声明了 10 个自执行函数,保存当时的值到内部
2.使用闭包模拟私有变量
私有变量在 java 里使用 private 声明就可以了, 但是在 js 中还没有,但是我们可以使用闭包模拟实现。
var counter = (function() {
var privateCounter = 0
function changeBy(val) {
privateCounter += val
}
return {
increment: function() {
changeBy(1)
},
decrement: function() {
changeBy(-1)
},
value: function() {
return privateCounter
},
}
})()
counter.value() //0
counter.increment() //1
counter.increment() //2
counter.decrement() //1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
匿名函数已经定义就立即执行, 创建出一个词法环境包含counter.increment
、counter.decrement
、counter.value
三个方法,还包含了两个私有项:privateCounter
变量和changeBy
函数。这两个私有项无法在匿名函数外部直接访问,必须通过匿名包装器返回的对象的三个公共函数访问。
# 闭包的缺点
- 由于闭包会是的函数中的变量都被保存到内存中,滥用闭包很容易造成内存消耗过大,导致网页性能问题。解决方法是在退出函数之前,将不再使用的局部变量全部删除。
- 闭包可以使得函数内部的值可以在函数外部进行修改。所有,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
# 2. 跨域问题有哪些处理方式
# 跨域解决方案
- 通过 jsonp 跨域
- 跨域资源共享(CORS)
- nodejs 中间件代理跨域
- nginx 反向代理中设置 proxy_cookie_domain
# Ⅰ.通过 jsonp 跨域
通常为了减轻 web 服务器的负载,我们把js
、css
,img
等静态资源分离到另一台独立域名的服务器上,在 html 页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许,基于此原理,我们可以通过动态创建 script,再请求一个带参网址实现跨域通信。
- 原生实现
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = 'http://www.daxihong.com:8080/login?user=admin&callback=jsonCallback';
document.head.appendChild(script);
// 回调执行函数
function jsonCallback(res) {
alert(JSON.stringify(res));
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
服务器端返回如下(返回即执行全局函数)
jsonCallback({ 'status': 0, 'user': 'admin' })
- jquery 方式实现
$.ajax({
url: 'http://www.domain2.com:8080/login',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: 'handleCallback', // 自定义回调函数名
data: {},
})
2
3
4
5
6
7
# Ⅱ.跨域资源共享(CORS)
CORS 是一个 W3C 标准,全称是"跨域资源共享"(Cross-origin resource sharing)跨域资源共享 CORS 详解。看名字就知道这是处理跨域问题的标准做法。CORS 有两种请求,简单请求和非简单请求。
- 简单请求
只要同时满足以下两大条件,就属于简单请求:
- 请求方法是以下三种方法之一:
HEAD
GET
POST
- HTTP 请求头的信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain
如果是简单请求, 后端处理即可, 前端什么也不用干; 这里注意的是如果前端要带 cookie, 前端也需要单独设置
- 原生 ajax (前端)
var xhr = new XMLHttpRequest();
// 前端设置是否带cookie
xhr.withCredentials = true;
...
2
3
4
- jquery (前端)
$.ajax({
...
xhrFields: {
withCredentials: true // 前端设置是否带cookie
},
crossDomain: true, // 会让请求头中包含跨域的额外信息,但不会含cookie
...
});
2
3
4
5
6
7
8
- vue 中使用 axios (前端)
axios.defaults.withCredentials = true
- 后端 node
可以借助koa2-cors
快速实现
const path = require('path')
const Koa = require('koa')
const koaStatic = require('koa-static')
const bodyParser = require('koa-bodyparser')
const router = require('./router')
const cors = require('koa2-cors')
const app = new Koa()
const port = 9871
...
// 处理cors
app.use(cors({
origin: function (ctx) {
return 'http://localhost:9099'
},
credentials: true,
allowMethods: ['GET', 'POST', 'DELETE'],
allowHeaders: ['t', 'Content-Type']
}))
// 路由
app.use(router.routes()).use(router.allowedMethods())
// 监听端口
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Ⅲ.nodejs 中间件代理跨域
跨域原理: 同源策略是浏览器的安全策略, 不是 HTTP 协议的一部分。服务器端调用 HTTP 接口只是使用 HTTP 协议, 不会执行 js 脚本, 不需要检验同源策略,也就不存在跨域问题。
实现思路:通过起一个代理服务器, 实现数据的转发,也可以通过设置 cookieDomainRewrite 参数修改响应头 cookie 中域名,实现当前域下 cookie 的写入
- 在 vue 框架下实现跨域
利用 node + webpack + webpack-dev-server 代理接口跨域。在开发环境下,由于 vue 渲染服务和接口代理服务都是 webpack-dev-server 同一个,所以页面与代理接口之间不再跨域,无须设置 headers 跨域信息了。后台可以不做任何处理。
webpack.config.js
部分配置
module.exports = {
entry: {},
module: {},
...
devServer: {
historyApiFallback: true,
proxy: [{
context: '/login',
target: 'http://www.daxihong.com:8080', // 代理跨域目标接口
changeOrigin: true,
secure: false, // 当代理某些https服务报错时用
cookieDomainRewrite: 'www.daxihong.com' // 可以为false,表示不修改
}],
noInfo: true
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Ⅳ.nginx 反向代理中设置
和使用 node 中间件跨域原理相似。前端和后端都不需要写额外的代码来处理, 只需要配置一下 Ngnix
server{
# 监听9099端口
listen 9099;
# 域名是localhost
server_name localhost;
#凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://localhost:9871
location ^~ /api {
proxy_pass http://localhost:9871;
}
}
2
3
4
5
6
7
8
9
10
对于跨域还有挺多方式可以实现, 这里就不一一列举了。
# 3. for...in 和 for...of 的区别
- for...of 是 ES6 新引入的特性,修复了 ES5 引入的 for...in 的不足
- for...in 循环出的是 key,for...of 循环出的是 value
- for...of 不能循环普通的对象,需要通过和 Object.keys()搭配使用
- 推荐在循环对象属性的时候,使用 for...in,在遍历数组的时候的时候使用 for...of
# 4. new 一个对象, 这个过程中发生了什么
var obj = new Object('name', 'sansan')
- 创建一个新对象,如:var obj = {};
- 新对象的proto属性指向构造函数的原型对象。
- 将构造函数的作用域赋值给新对象。(也所以 this 对象指向新对象)
- 执行构造函数内部的代码,将属性添加给 obj 中的 this 对象。
- 返回新对象 obj。
# 5. js 的防抖和节流是什么
- 防抖: 在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时。
使用场景:
- 给按钮加函数防抖防止表单多次提交。
- 对于输入框连续输入进行 AJAX 验证时,用函数防抖能有效减少请求次数。
简单的防抖(debounce)代码:
function debounce(fn, wait) {
var timeout = null
return function() {
if (timeout !== null) clearTimeout(timeout)
timeout = setTimeout(fn, wait)
}
}
// 处理函数
function handle() {
console.log(Math.random())
}
//滚动事件
window.addEventListener('scroll', debounce(handle, 2000))
2
3
4
5
6
7
8
9
10
11
12
13
- 节流: 就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。
function throttle(func, delay) {
var prev = Date.now()
return function() {
var context = this
var args = arguments
var now = Date.now()
if (now - prev >= delay) {
func.apply(context, args)
prev = Date.now()
}
}
}
function handle() {
console.log(Math.random())
}
window.addEventListener('scroll', throttle(handle, 2000))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
区别:
函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
# 6. 数组中常用的方法有哪些
开发中数组的使用场景非常多, 这里就简单整理总结一些常用的方法;从改变原有数据的方法、不改变原有数组的方法以及数据遍历的方法三方面总结。
- 改变原有数组的方法: (9 个)
- splice() 添加/删除数组元素
let a = [1, 2, 3, 4, 5, 6, 7]
let item = a.splice(0, 3) // [1,2,3]
console.log(a) // [4,5,6,7]
// 从数组下标0开始,删除3个元素
let item1 = a.splice(0, 3, '添加') // [4,5,6]
console.log(a) // ['添加',7]
// 从数组下标0开始,删除3个元素,并添加元素'添加'
2
3
4
5
6
7
- sort() 数组排序
var array = [10, 1, 3, 4, 20, 4, 25, 8]
// 升序 a-b < 0 a将排到b的前面,按照a的大小来排序的
array.sort(function(a, b) {
return a - b
})
console.log(array) // [1,3,4,4,8,10,20,25];
// 降序
array.sort(function(a, b) {
return b - a
})
console.log(array) // [25,20,10,8,4,4,3,1];
2
3
4
5
6
7
8
9
10
11
pop() 删除一个数组中的最后的一个元素
shift() 删除数组的第一个元素
push() 向数组的末尾添加元素
unshift()向数组开头添加元素
reverse()
let a = [1, 2, 3]
a.pop() // 3, 返回被删除的元素
console.log(a) // [1,2]
a.shift() // 1
console.log(a) // [2]
a.push('末尾添加') // 2 ,返回数组长度
console.log(a)
;[2, '末尾添加']
a.unshift('开头添加') // 3
console.log(a) //["开头添加", 2, "末尾添加"]
a.reverse() // ["末尾添加", 2, "开头添加"]
console.log(a) // ["末尾添加", 2, "开头添加"]
2
3
4
5
6
7
8
9
10
11
12
- ES6: copyWithin() 指定位置的成员复制到其他位置
let a = ['zhang', 'wang', 'zhou', 'wu', 'zheng']
// 1位置开始被替换, 2位置开始读取要替换的 5位置前面停止替换
a.copyWithin(1, 2, 5)
// ["zhang", "zhou", "wu", "zheng", "zheng"]
2
3
4
- ES6: fill() 填充数组
;['a', 'b', 'c'].fill(7)[
// [7, 7, 7]
('a', 'b', 'c')
].fill(7, 1, 2)
// ['a', 7, 'c']
2
3
4
5
以上是 9 种会改变原数组的方法, 接下来是 6 种常用的不会改变原数组的方法
- 不改变原数组的方法(6 种)
- join() 数组转字符串
let a = ['hello', 'world']
let str2 = a.join('+') // 'hello+world'
2
- cancat 合并两个或多个数组
let a = [1, 2, 3]
let b = [4, 5, 6]
//连接两个数组
let newVal = a.concat(b) // [1,2,3,4,5,6]
2
3
4
- ES6 扩展运算符...合并数组
let a = [2, 3, 4, 5]
let b = [4, ...a, 4, 4]
console.log(a, b)
//[2, 3, 4, 5] [4,2,3,4,5,4,4]
2
3
4
- indexOf() 查找数组是否存在某个元素,返回下标
let a = ['啦啦', 2, 4, 24, NaN]
console.log(a.indexOf('啦')) // -1
console.log(a.indexOf('啦啦')) // 0
2
3
- ES7 includes() 查找数组是否包含某个元素 返回布尔
- indexOf 方法不能识别 NaN
- indexOf 方法检查是否包含某个值不够语义化,需要判断是否不等于-1,表达不够直观
let a = ['OB', 'Koro1', 1, NaN]
a.includes(NaN) // true 识别NaN
a.includes('Koro1', 100) // false 超过数组长度 不搜索
a.includes('Koro1', -3) // true 从倒数第三个元素开始搜索
2
3
4
- slice() 浅拷贝数组的元素
字符串也有一个 slice() 方法是用来提取字符串的,不要弄混了。
let a = [{ name: 'OBKoro1' }, { name: 'zhangsan' }]
let b = a.slice(0, 1)
console.log(b, a)
// [{"name":"OBKoro1"}] [{"name":"OBKoro1"}]
a[0].name = '改变原数组'
console.log(b, a)
// [{"name":"改变原数组"}] [{"name":"改变原数组"}]
2
3
4
5
6
7
- 遍历方法
forEach:按升序为数组中含有效值的每一项执行一次回调函数。
1.无法中途退出循环,只能用 return 退出本次回调,进行下一次回调.
2.它总是返回 undefined 值,即使你 return 了一个值。every 检测数组所有元素是否都符合判断条件
如果数组中检测到有一个元素不满足, 则整个表达式返回 false,且元素不会再进行检测
function isBigEnough(element, index, array) {
return element >= 10 // 判断数组中的所有元素是否都大于10
}
;[12, 5, 8, 130, 44].every(isBigEnough) // false
;[12, 54, 18, 130, 44].every(isBigEnough) // true
// 接受箭头函数写法
;[12, 5, 8, 130, 44].every((x) => x >= 10) // false
;[12, 54, 18, 130, 44].every((x) => x >= 10) // true
2
3
4
5
6
7
8
some 数组中的是否有满足判断条件的元素
如果有一个元素满足条件,则表达式返回 true, 剩余的元素不会再执行检测
filter 过滤原始数组,返回新数组
map 对数组中的每个元素进行处理,返回新的数组
reduce 为数组提供累加器,合并为一个值
reduce() 方法对累加器和数组中的每个元素(从左到右)应用一个函数,最终合并为一个值。
// 数组求和
let sum = [0, 1, 2, 3].reduce(function(a, b) {
return a + b
}, 0)
// 6
// 将二维数组转化为一维 将数组元素展开
let flattened = [
[0, 1],
[2, 3],
[4, 5],
].reduce((a, b) => a.concat(b), [])
// [0, 1, 2, 3, 4, 5]
2
3
4
5
6
7
8
9
10
11
12
- ES6:find()& findIndex() 根据条件找到数组成员
这两个方法都可以识别 NaN,弥补了 indexOf 的不足.
;[1, 4, -5, 10, NaN].find((n) => Object.is(NaN, n))
// 返回元素NaN
;[1, 4, -5, 10].findIndex((n) => n < 0)
// 返回索引2
2
3
4
- ES6 keys()&values()&entries() 遍历键名、遍历键值、遍历键名+键值
for (let index of ['a', 'b'].keys()) {
console.log(index)
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem)
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem)
}
// 0 "a"
// 1 "b"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 7. 怎么判断一个 object 是否是数组
- 方法一
使用 Object.prototype.toString 来判断是否是数组
function isArray(obj) {
return Object.prototype.toString.call(obj) === '[object Array]'
}
2
3
这里使用 call 来使 toString 中 this 指向 obj。进而完成判断
- 方法二
使用 原型链 来完成判断
function isArray(obj) {
return obj.__proto__ === Array.prototype
}
2
3
基本思想: 实例如果是某个构造函数构造出来的那么 它的
__proto__
是指向构造函数的prototype
属性
- 方法 3
利用 JQuery, 利用 JQuery isArray 的实现其实就是方法 1。
function isArray(obj) {
return $.isArray(obj)
}
2
3
# 8. 继承有哪些方式
- ES6 中的 class 继承
- 原型继承
- 构造继承
- 寄生组合式继承
- 实例继承
简单介绍一下前两种方式, 后面几种继承方式大家可以自行上网查找.
- ES6 中的 class 继承
class Animal {
constructor(name) {
this.name = name
}
eat() {
console.log(this.name + '正在吃东西')
}
}
//继承动物类
class Cat extends Animal {
catchMouse() {
console.log(`${this.name}正在捉老鼠`)
}
}
var cat = new Cat('Tom猫')
cat.catchMouse() // Tom猫正在捉老鼠
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
细心的同学可能会发现, 在 Cat 类中没有构造函数, 这里有一个小的知识点,就是 ES6 的继承方法中如果子类没有写构造函数的话就一般默认添加构造。举个例子。
class Cat extends Animal {}
// 等同于
class Cat extends Animal {
constructor(name) {
super(name)
//super作为函数调用时,代表父类的构造函数。
}
}
2
3
4
5
6
7
8
注意:如果我写了构造函数但是没有写 super 的话,或者 super 方法的参数不对等等,编译器都会报错。
- 原型继承
在 ES6 之前,也有很多继承的方法,其中一个很常用的方法就是使用原型继承。其基本方法就是一个父类的实例赋值给子类的原型。这个继承方式是通过__proto__
建立和子类之间的原型链,当子类的实例需要使用父类的属性和方法的时候,可以通过__proto__
一级级向上找;
function Animal(name) {
this.name = name
}
Animal.prototype.eat = function() {
console.log(this.name + '正在吃东西')
}
function Cat(furColor) {
this.furColor = furColor
}
Cat.prototype = new Animal()
let tom = new Cat('black')
console.log(tom)
2
3
4
5
6
7
8
9
10
11
12
13
缺点: 1. 子类实例时,无法向父类构造函数传参。
- 父类的私有属性被所有实例共享
# 9. 说说 js 中 call,apply,bind 之间的关系
这又是一个面试经典问题, 也是 ES5 中众多坑中的一个,在 ES6 中可能会极大避免 this 产生的错误,但是为了一些老代码的维护,最好还是了解一下 this 的指向和 call、apply、bind 三者的区别.
bind,apply,call 三者都可以用来改变this
的指向, 下面分别对他们进行比较分析:
# apply 和 call
- 二者都是 Function 对象的方法, 每个函数都能调用
- 二者的第一个参数都是你要指定的执行上下文
- apply 和 call 的区别是: call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。
var a = {
name: 'Cherry',
fn: function(a, b) {
console.log(a + b)
},
}
var b = a.fn
b.apply(a, [1, 2]) // 3
b.call(a, 4, 5, 6) // 15
2
3
4
5
6
7
8
9
我们常常使用的验证是否是数组(前提是 toString()方法没有被重写过):
function isArray(obj) {
return Object.prototype.toString.call(obj) === '[object Array]'
}
2
3
# bind 与 apply、call 区别
var a = {
name: 'Cherry',
fn: function(a, b) {
console.log(a + b)
},
}
var b = a.fn
b.bind(a, 1, 2)() // 3
2
3
4
5
6
7
8
我们发现bind()
方法还需要调用一次; 是由于 bind()
方法创建一个新的函数,我们必须手动去调用。
# bind,apply,call 的共同和不同点:
- 三者都可以用来改变
this
的指向 - 三者第一个参数都是 this 要指向的对象,也就是想指定的上下文,上下文就是指调用函数的那个对象。(点前的那个对象,没有就是全局 window)
- 三者都可以传参,但是 apply 是数组,而 call 是有顺序的传入
- bind 是返回对应函数,便于稍后调用;apply 、call 则是立即执行
# 10. Promise
前端面试过程中,基本都会问到 Promise,如果你足够幸运,面试官问的比较浅,仅仅问 Promise 的使用方式,那么恭喜你。事实上,大多数人并没有那么幸运, 很多面试官在 promise 这块都是由浅入深的提问.
- 了解 Promise 吗?
- Promise 解决了什么问题?
- Promise 如何使用?
- Promise 常用的方法有哪些?它们的作用是什么?
- Promise 在事件循环中的执行过程是怎样的?
# 1. 了解 Promise 吗?
所谓 Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理,让开发者不用再关注于时序和底层的结果。Promise 的状态具有不受外界影响和不可逆两个特点。
# 2.Promise 解决了什么问题?
Promise 解决了回调地狱的问题, 提高代码的可读性以及解决信任度问题. 传统的回调有五大信任问题:
- 调用回调过早
- 调用回调过晚(或者没有被调用)
- 调用回调次数过多或过少
- 未能传递所需的环境和参数
- 涂掉可能出现的错误和异常
# 3. Promise 如何使用?
ES6 规定,Promise 对象是一个构造函数,用来生成 Promise 实例。 下面代码创造了一个 Promise 实例。
var promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
2
3
4
5
6
7
8
9
Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve
函数的作用是,将 Promise 对象的状态从“未完成”变为“成功”(即从Pending
变为Resolved
),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject
函数的作用是,将 Promise 对象的状态从“未完成”变为“失败”(即从Pending
变为Rejected
),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
# 4. Promise 常用的方法有哪些?它们的作用是什么?
- Promise.prototype.then
Promise 实例具有then
方法,也就是说,then
方法是定义在原型对象Promise.prototype
上的。它的作用是为 Promise 实例添加状态改变时的回调函数。then
方法的第一个参数是resolved
状态的回调函数,第二个参数(可选)是rejected
状态的回调函数。
- Promise.prototype.catch
Promise.prototype.catch
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。
getJSON('/posts.json')
.then(function(posts) {
// ...
})
.catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error)
})
2
3
4
5
6
7
8
上面代码中,getJSON
方法返回一个 Promise 对象,如果该对象状态变为resolved
,则会调用then
方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected
,就会调用catch
方法指定的回调函数,处理这个错误。
Promise.race
Promise.race
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例, 返回最先执行结束的 Promise 任务的结果,不管这个 Promise 结果是成功还是失败。Promise.all
Promise.all
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。如果全部成功执行,则以数组的方式返回所有 Promise 任务的执行结果。 如果有一个 Promise 任务rejected
,则只返回rejected
任务的结果。
# 5. Promise 在事件循环中的执行过程是怎样的
var promise = new Promise((resolve, reject) => {
console.log('我是promise任务')
resolve('resolved')
})
promise.then((res) => {
console.log(res)
})
console.log('我是同步任务')
setTimeout(() => {
console.log('我是延时任务')
}, 0)
2
3
4
5
6
7
8
9
10
11
上面代码的执行顺序是: 我是 promise 任务、我是同步任务、resolved、我是延时任务。
Promise 新建后立即执行,立即 resolve
的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时;setTimeout 在下一轮“事件循环”开始时执行。
# 总结
在面试中, 很多问题并没有真正的答案,至于知识点能掌握到什么样的程度,都需要靠自己不断的学习积累, 在开发中不断的使用也是加深对知识点理解的方式。由于个人精力有限,只是针对一些常遇到的面试题,做了一些浅显的答案解析,希望对大家有所帮助吧。
觉得本文对你有帮助?请分享给更多人
欢迎大家关注我的公众号——程序员成长指北。请自行微信搜索——“程序员成长指北”
上一篇文章: web 前端高频面试题--css 篇