javascript回调地狱_由浅⼊深,66条JavaScript⾯试知识点
(五)
作者:Jake Zhang
⽬录
由浅⼊深,66条JavaScript⾯试知识点(⼀)
由浅⼊深,66条JavaScript⾯试知识点(⼆)
由浅⼊深,66条JavaScript⾯试知识点(三)
由浅⼊深,66条JavaScript⾯试知识点(四)
由浅⼊深,66条JavaScript⾯试知识点(五)本篇
由浅⼊深,66条JavaScript⾯试知识点(六)
由浅⼊深,66条JavaScript⾯试知识点(七)
⼩编建议⼩伙们从第⼀篇开始,按照顺序来看,更清晰明了。
55. ⼿写call、apply及bind函数
call 函数的实现步骤:鲜艳拼音
1.判断调⽤对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使⽤ call 等⽅式调⽤的情况。
2.判断传⼊上下⽂对象是否存在,如果不存在,则设置为 window 。
3.处理传⼊的参数,截取第⼀个参数后的所有参数。激光换肤
4.将函数作为上下⽂对象的⼀个属性。
5.使⽤上下⽂对象来调⽤这个⽅法,并保存返回结果。
6.删除刚才新增的属性。
7.返回结果。
// call函数实现Call = function(context) { // 判断调⽤对象 if (typeof this !== "function") { ("type error"); } // 获取参数 let args =
apply 函数的实现步骤:
1. 判断调⽤对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使⽤ call 等⽅式调⽤的情况。
2. 判断传⼊上下⽂对象是否存在,如果不存在,则设置为 window 。
3. 将函数作为上下⽂对象的⼀个属性。
4. 判断参数值是否传⼊
4. 使⽤上下⽂对象来调⽤这个⽅法,并保存返回结果。
5. 删除刚才新增的属性
6. 返回结果
// apply 函数实现Apply = function(context) { // 判断调⽤对象是否为函数 if (typeof this !== "function") { throw new TypeError("Error"); } let bind 函数的实现步骤:
1.判断调⽤对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使⽤ call 等⽅式调⽤的情况。
2.保存当前函数的引⽤,获取其余传⼊参数值。
3.创建⼀个函数返回
4.函数内部使⽤ apply 来绑定函数调⽤,需要判断函数作为构造函数的情况,这个时候需要传⼊当前函数的 this 给 apply 调⽤,其余
情况都传⼊指定的上下⽂对象。
// bind 函数实现Bind = function(context) { // 判断调⽤对象是否为函数 if (typeof this !== "function") { throw new TypeError("Error"); } // 获参考⽂章:《⼿写 call、apply 及 bind 函数》
《JavaScript 深⼊之 call 和 apply 的模拟实现》
56. 函数颗粒化的实现
// 函数柯⾥化指的是⼀种将使⽤多个参数的⼀个函数转换成⼀系列使⽤⼀个参数的函数的技术。function curry(fn, args) { // 获取函数需要的参数长度 let length = fn.le
吐泡泡的小鱼
饺子的材料57. js模拟new操作符的实现
这个问题如果你在掘⾦上搜,你可能会搜索到类似下⾯的回答:
说实话,看第⼀遍,我是不理解的,我需要去理⼀遍原型及原型链的知识才能理解。所以我觉得MDN对new的解释更容易理解:
new 运算符创建⼀个⽤户定义的对象类型的实例或具有构造函数的内置对象的实例。new 关键字会进⾏如下的操作:
1. 创建⼀个空的简单JavaScript对象(即{});
2. 链接该对象(即设置该对象的构造函数)到另⼀个对象 ;
3. 将步骤1新创建的对象作为this的上下⽂ ;
4. 如果该函数没有返回对象,则返回this。
接下来我们看实现:
function Dog(name, color, age) { this.name = name; lor = color; this.age = age;}Dog.prototype={ getName: function() { return this.name }}var d
上⾯的代码相信不⽤解释,⼤家都懂。我们来看最后⼀⾏带new关键字的代码,按照上述的1,2,3,4步来解析new背后的操作。
第⼀步:创建⼀个简单的对象
var obj = {}
第⼆步:链接该对象到另⼀个对象(原型链)
// 设置原型链obj.__proto__ = Dog.prototype
第三步:将步骤1新创建的对象作为 this 的上下⽂
// this指向obj对象Dog.apply(obj, ['⼤黄', 'yellow', 3])
第四步:如果该函数没有返回对象,则返回this
// 因为 Dog() 没有返回值,所以返回objvar dog = Name() // '⼤黄'
需要注意的是如果 Dog() 有 return 则返回 return的值
var rtnObj = {}function Dog(name, color, age) { // ... //返回⼀个对象 return rtnObj}var dog = new Dog('⼤黄', 'yellow', 3)console.log(dog === rtnObj) // true
接下来我们将以上步骤封装成⼀个对象实例化⽅法,即模拟new的操作:
function objectFactory(){ var obj = {}; //取得该⽅法的第⼀个参数(并删除第⼀个参数),该参数是构造函数 var Constructor = [].shift.apply(arguments); //将新对
58. 什么是回调函数?回调函数有什么缺点
回调函数是⼀段可执⾏的代码段,它作为⼀个参数传递给其他的代码,其作⽤是在需要的时候⽅便调⽤这段(回调函数)代码。
在JavaScript中函数也是对象的⼀种,同样对象可以作为参数传递给函数,因此函数也可以作为参数传递给另外⼀个函数,这个作为参数的
函数就是回调函数。
const btnAdd = ElementById('btnAdd');btnAdd.addEventListener('click', function clickCallback(e) { // do something uless});
在本例中,我们等待id为btnAdd的元素中的click时间,如果它被单击,则执⾏clickCallback函数。回调函数向某些数据或事件添加⼀些功
能。
回调函数有⼀个致命的弱点,就是容易写出回调地狱(Callback hell)。假设多个事件存在依赖性:
tTimeout(() => { console.log(1) tTimeout(() => { console.log(2) tTimeout(() => { console.log(3) },3000) },2000)},1000)
这就是典型的回调地狱,以上代码看起来不利于阅读和维护,事件⼀旦多起来就更是乱糟糟,所以在es6中提出了Promi和async/await
来解决回调地狱的问题。当然,回调函数还存在着别的⼏个缺点,⽐如不能使⽤ try catch 捕获错误,不能直接 return。接下来的两条就
是来解决这些问题的,咱们往下看。
59. Promi是什么,可以⼿写实现⼀下吗?
Promi,翻译过来是承诺,承诺它过⼀段时间会给你⼀个结果。从编程讲Promi 是异步编程的⼀种解决⽅案。下⾯是Promi在MDN
的相关说明:
Promi 对象是⼀个代理对象(代理⼀个值),被代理的值在Promi对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定
相应的处理⽅法(handlers)。这让异步⽅法可以像同步⽅法那样返回值,但并不是⽴即返回最终执⾏结果,⽽是⼀个能代表未来出现的结果
的promi对象。
⼀个 Promi有以下⼏种状态:
pending: 初始状态,既不是成功,也不是失败状态。
fulfilled: 意味着操作成功完成。
rejected: 意味着操作失败。
这个承诺⼀旦从等待状态变成为其他状态就永远不能更改状态了,也就是说⼀旦状态变为 fulfilled/rejected 后,就不能再次改变。可能光
看概念⼤家不理解Promi,我们举个简单的例⼦;
假如我有个⼥朋友,下周⼀是她⽣⽇,我答应她⽣⽇给她⼀个惊喜,那么从现在开始这个承诺就进⼊等待状态,等待下周⼀的到来,然后状
态改变。如果下周⼀我如约给了⼥朋友惊喜,那么这个承诺的状态就会由pending切换为fulfilled,表⽰承诺成功兑现,⼀旦是这个结果
了,就不会再有其他结果,即状态不会在发⽣改变;反之如果当天我因为⼯作太忙加班,把这事给忘了,说好的惊喜没有兑现,状态就会由pending切换为rejected,时间不可倒流,所以状态也不能再发⽣变化。
上⼀条我们说过Promi可以解决回调地狱的问题,没错,pending 状态的 Promi 对象会触发 fulfilled/rejected 状态,⼀旦状态改
变,Promi 对象的 then ⽅法就会被调⽤;否则就会触发 catch。我们将上⼀条回调地狱的代码改写⼀下:
小星星简笔画new Promi((resolve,reject) => { tTimeout(() => { console.log(1) resolve() },1000)}).then((res) => { tTimeout(() => { console.l 其实Promi也是存在⼀些缺点的,⽐如⽆法取消 Promi,错误需要通过回调函数捕获。
promi⼿写实现,⾯试够⽤版:晋朝多少年历史
function myPromi(constructor){ let lf=this; lf.status="pending" //定义状态改变前的初始状态 lf.value=undefined;//定义状态为resolved的时候的状态 s
关于Promi还有其他的知识,⽐如Promi.all()、Promi.race()等的运⽤,由于篇幅原因就不再做展开,想要深⼊了解的可看下⾯的
⽂章。
相关资料:
60. `Iterator`是什么,有什么作⽤?
Iterator是理解第24条的先决知识,也许是我IQ不够,Iterator和Generator看了很多遍还是⼀知半解,即使当时理解了,过⼀阵⼜忘得⼀
⼲⼆净。。。
Iterator(迭代器)是⼀种接⼝,也可以说是⼀种规范。为各种不同的数据结构提供统⼀的访问机制。任何数据结构只要部署Iterator接⼝,就
可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator语法:
const obj = { [Symbol.iterator]:function(){}}
[Symbol.iterator]属性名是固定的写法,只要拥有了该属性的对象,就能够⽤迭代器的⽅式进⾏遍历。
齐字成语迭代器的遍历⽅法是⾸先获得⼀个迭代器的指针,初始时该指针指向第⼀条数据之前,接着通过调⽤ next ⽅法,改变指针的指向,让其指
向下⼀条数据每⼀次的 next 都会返回⼀个对象,该对象有两个属性
value 代表想要获取的数据
done 布尔值,fal表⽰当前指针指向的数据有值,true表⽰遍历已经结束
Iterator 的作⽤有三个:
1. 为各种数据结构,提供⼀个统⼀的、简便的访问接⼝;
2. 使得数据结构的成员能够按某种次序排列;
3. ES6 创造了⼀种新的遍历命令for…of循环,Iterator 接⼝主要供for…of消费。
遍历过程:
1. 创建⼀个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是⼀个指针对象。
2. 第⼀次调⽤指针对象的next⽅法,可以将指针指向数据结构的第⼀个成员。
3. 第⼆次调⽤指针对象的next⽅法,指针就指向数据结构的第⼆个成员。
4. 不断调⽤指针对象的next⽅法,直到它指向数据结构的结束位置。
每⼀次调⽤next⽅法,都会返回数据结构的当前成员的信息。具体来说,就是返回⼀个包含value和done两个属性的对象。其中,value属
性是当前成员的值,done属性是⼀个布尔值,表⽰遍历是否结束。
let arr = [{num:1},2,3]let it = arr[Symbol.iterator]() // 获取数组中的迭代器console.()) // { value: Object { num: 1 }, done: fal }console.()) //
61. `Generator`函数是什么,有什么作⽤?
Generator函数可以说是Iterator接⼝的具体实现⽅式。Generator 最⼤的特点就是可以控制函数的执⾏。
function *foo(x) { let y = 2 * (yield (x + 1)) let z = yield (y / 3) return (x + y + z)}let it = foo(5)console.()) // => {value: 6, done: fal}console.log(it
上⾯这个⽰例就是⼀个Generator函数,我们来分析其执⾏过程:
⾸先 Generator 函数调⽤时它会返回⼀个迭代器
当执⾏第⼀次 next 时,传参会被忽略,并且函数暂停在 yield (x + 1) 处,所以返回 5 + 1 = 6
当执⾏第⼆次 next 时,传⼊的参数等于上⼀个 yield 的返回值,如果你不传参,yield 永远返回 undefined。此时 let y = 2 * 12,所以第⼆个 yield 等于 2 * 12 / 3 = 8
当执⾏第三次 next 时,传⼊的参数会传递给 z,所以 z = 13, x = 5, y = 24,相加等于 42
Generator 函数⼀般见到的不多,其实也与他有点绕有关系,并且⼀般会配合 co 库去使⽤。当然,我们可以通过 Generator 函数解决回
调地狱的问题。
62. 什么是 `async/await` 及其如何⼯作,有什么优缺点?
async/await是⼀种建⽴在Promi之上的编写异步或⾮阻塞代码的新⽅法,被普遍认为是 JS异步操作的最终且最优雅的解决⽅案。相对
于 Promi 和回调,它的可读性和简洁度都更⾼。毕竟⼀直then()也很烦。
手串打结图解
async 是异步的意思,⽽ await 是 async wait的简写,即异步等待。
所以从语义上就很好理解 async ⽤于声明⼀个 function 是异步的,⽽await ⽤于等待⼀个异步⽅法执⾏完成。
⼀个函数如果加上 async ,那么该函数就会返回⼀个 Promi
async function test() { return "1"}console.log(test()) // -> Promi {: "1"}