JavaScript is now a very popular programming language, based on which a large number of libraries and frameworks have been derived. But no matter how the upper ecosystem evolves, it can’t do without vanilla JavaScript. Here I’ve selected 4 JavaScript interview questions to test a programmer’s skill of vanilla JavaScript. Do you have the confidence to challenge it?
JavaScript现在是一种非常流行的编程语言,基于该语言,派生了大量库和框架。 但是,无论高层生态系统如何发展,离不开原始JavaScript。 在这里,我选择了4个JavaScript面试问题,以测试程序员使用普通JavaScript的技能。 您有挑战的信心吗?
How to implement an Array.prototype.map method by hand?
如何手动实现Array.prototype.map方法?
Arrays are one of the most common and important data structures, and in JavaScript, there are many built-in methods to manipulate arrays, such as Map, Filter, Reduce, Splice, and so on. Every JavaScript developer should be familiar with these built-in methods, so I won’t go into them. If you are not familiar with these methods, go to the MDN for guidance.
数组是最常见,最重要的数据结构之一,在JavaScript中,有许多内置方法可以操作数组,例如Map,Filter,Reduce,Splice等。 每个JavaScript开发人员都应该熟悉这些内置方法,因此我不再赘述。 如果您不熟悉这些方法,请转到MDN以获取指导。
But a good developer should not be satisfied with just using these built-in functions, but should also have the ability to implement them manually. Can you implement a map method by hand?
但是,优秀的开发人员不应仅使用这些内置函数就感到满意,还应具有手动实现它们的能力。 您可以手动实现地图方法吗?
The first thing we know is that Array.protype.map takes two parameters, which marks as Array.prototype.map(callbackfn [ , thisArg ] ) .
我们知道的第一件事是Array.protype.map具有两个参数,它们标记为Array.prototype.map(callbackfn [ , thisArg ] ) 。
This method returns a new array, and the new array is the same length as the original array.
此方法返回一个新数组,并且新数组的长度与原始数组的长度相同。
Based on these conditions, we can write a code framework:
基于这些条件,我们可以编写一个代码框架:
Array.prototype._map = function(callbackfn, thisArg) { let result = new Array(this.length); // ... return result}this in the _map function refers to the original array, so this.length is the length of the original array. We use new Array(this.length) to create a new array as long as the original array to store the results of subsequent callbackfn runs.
this在_map功能是指原来数组,所以this.length是原始阵列的长度。 我们使用new Array(this.length)创建一个新数组,只要原始数组存储了随后的callbackfn运行的结果即可。
Our next task is to fill in the rest of the code.
我们的下一个任务是填写其余代码。
If you now search the Internet for ‘How to implement an Array.prototype.map’, you’ll see a lot of articles about it. But most of them are implemented in a sloppy or even wrong way.
如果现在在Internet上搜索“ 如何实现Array.prototype.map” ,则会看到很多有关它的文章。 但是其中大多数都是以草率甚至错误的方式实现的。
The most standard implementations should strictly adhere to the ECMAScript specification, so here is the definition and description of the map method from the ECMAScript:
最标准的实现应严格遵守ECMAScript规范,因此以下是ECMAScript中map方法的定义和说明:
callbackfn should be a function that accepts three arguments. map calls callbackfn once for each element in the array, in ascending order, and constructs a new Array from the results. callbackfn is called only for elements of the array which actually exist; it is not called for missing elements of the array.
callbackfn应该是一个接受三个参数的函数。 map对数组中的每个元素按升序调用一次callbackfn,然后从结果中构造一个新的Array。 仅对实际存在的数组元素调用callbackfn。 它不要求缺少数组元素。
If a thisArg parameter is provided, it will be used as the this value for each invocation of callbackfn. If it is not provided, undedefined is used instead.
如果提供了thisArg参数,它将用作每次调用callbackfn的this值。 如果未提供,则使用undedefined。
callbackfn is called with three arguments: the value of the element, the index of the element, and the object being traversed.
使用三个参数调用callbackfn:元素的值,元素的索引和要遍历的对象。
map does not directly mutate the object on which it is called but the object may be mutated by the calls to callbackfn.
map不会直接改变其调用对象,但是可以通过对callbackfn的调用来改变该对象。
The range of elements processed by map is set before the first call to callbackfn. Elements which are appended to the array after the call to map begins will not be visited by callbackfn. If existing elements of the array are changed, their value as passed to callbackfn will be the value at the time map visits them; elements that are deleted after the call to map begins and before being visited are not visited.
在第一次调用callbackfn之前设置map处理的元素范围。 调用map开始后追加到数组的元素将不会被callbackfn访问。 如果更改了数组的现有元素,则传递给callbackfn的值将是映射访问它们时的值; 在开始调用地图之后且被访问之前删除的元素不会被访问。
This definition is a little long, but don’t worry, let’s explore it together.
这个定义有点长,但是不用担心,让我们一起探讨一下。
The first thing we notice in the specification has the following sentence:
我们在规范中注意到的第一件事具有以下句子:
The range of elements processed by map is set before the first call to callbackfn. Elements which are appended to the array after the call to map begins will not be visited by callbackfn.
在第一次调用callbackfn之前设置map处理的元素范围。 调用map开始后追加到数组的元素将不会被callbackfn访问。
We know that our map method needs to traverse this array, and during the traversal process, callbackfn may add or remove elements from the original array. The specification requires us to save a backup of the array before traversing the array. Even if we change the original array during the execution of callbackfn, it should not affect our subsequent traversal operations.
我们知道我们的map方法需要遍历此数组,并且在遍历过程中, callbackfn可能会在原始数组中添加或删除元素。 该规范要求我们在遍历数组之前保存数组的备份。 即使我们在执行callbackfn过程中更改了原始数组,也不应影响我们随后的遍历操作。
So we can add the code as follows:
因此,我们可以添加如下代码:
let archiveArray = Array.prototype.slice.call(this)We use Array.prototype.slice.call(this) to create a shallow copy of the original array for subsequent traversal operations. If the original array is later modified, it will not affect our traversal operation.
我们使用Array.prototype.slice.call(this)为后续遍历操作创建原始数组的浅表副本。 如果后来修改了原始数组,则不会影响我们的遍历操作。
Then it’s time to complete the core feature of the map method: creates a new array with the results of calling a function for every array element.
然后是时候完成map方法的核心功能了: 创建一个新数组,并为每个数组元素调用一个函数。
So we can write code like this:
因此我们可以编写如下代码:
for (let index = 0; index < archiveArray.length; index++) { let newElement = callbackfn(archiveArray[index], index, this) result[index] = newElement;}But the code above ignores thisArg, which is said in the specification.
但是上面的代码忽略了规范中提到的thisArg 。
If a thisArg parameter is provided, it will be used as the this value for each invocation of callbackfn. If it is not provided, undedefined is used instead.
如果提供了thisArg参数,它将用作每次调用callbackfn的this值。 如果未提供,则使用undedefined。
So when we call the callbackfn, we should bind this to it.
所以,当我们调用callbackfn ,我们应该结合this吧。
let newElement = callbackfn.call(thisArg, archiveArray[index], index, this)Ok, now that the code is almost complete, let’s test it:
好的,现在代码已经差不多完成了,让我们对其进行测试:
let arr = [1, 2, 3]arr.map(element => element * 2)arr._map(element => element * 2)Well, this code looks like it’s working, but there’s actually a bug.
好的,这段代码看起来像在工作,但实际上存在一个错误。
Let’s now modify the previous example:
现在让我们修改前面的示例:
As you can see, when there are empty elements in the array, our manual implementation of the _map method will have different results than the built-in map method.
如您所见,当数组中有空元素时,我们手动实现的_map方法的结果将与内置map方法的结果不同。
This is because of the omission of this sentence in ECMAScript:
这是因为在ECMAScript中省略了此语句:
callbackfn is called only for elements of the array which actually exist; it is not called for missing elements of the array.
仅对实际存在的数组元素调用callbackfn。 它不要求缺少数组元素。
If there are empty elements in the array, we should skip the current callbackfn. So before we execute the callbackfn, we should make a judgment.
如果数组中有空元素,则应跳过当前的callbackfn 。 因此,在执行callbackfn之前,我们应该做出判断。
if(!archiveArray.hasOwnProperty(i)) continue;Let’s test the code again:
让我们再次测试代码:
It is fine. 没事。This should be the implementation of the map method that most conforms to the ECMAScript specification.
这应该是最符合ECMAScript规范的map方法的实现。
By the same token, in addition to being able to write the Array.prototype.map method, you should also be able to write the Array.prototype.filter method by hand, and the principle is the same.
同样,除了能够编写Array.prototype.map方法之外,您还应该能够手动编写Array.prototype.filter方法,并且原理是相同的。
Array.prototype._filter = function (callbackfn, thisArg) { let archiveArray = Array.prototype.slice.call(this) let filteredArr = [] for (let i = 0; i < archiveArray.length; i++) { if(!archiveArray.hasOwnProperty(i)) continue; if(callbackfn.call(thisArg, archiveArray[i], i, this)){ filteredArr.push(archiveArray[i]) } } return filteredArr}Because the length of the array after the filter is not necessarily the same as the original array, I do not specify the length of the new array when I create it.
由于过滤器后面的数组长度不一定与原始数组相同,因此在创建新数组时,我不指定其长度。
Now let’s take a quick test:
现在让我们进行快速测试:
let arr = [1, 2, 3, 4, 5]delete arr[2]arr.filter(element => element % 2 === 1)arr._filter(element => element % 2 === 1) It is fine. 没事。And here is the implementation of Array.prototype.some :
这是Array.prototype.some的实现:
Array.prototype._some = function (callbackfn, thisArg) { let arr = Array.prototype.slice.call(this) if(!arr.length) return false for (let i = 0; i < arr.length; i++) { if(!arr.hasOwnProperty(i)) continue; let res = callbackfn.call(thisArg,arr[i],i,this) if(res)return true } return false}Many other implementations on the Internets often have the step Array.prototype.slice.call(this) or the step if(!arr.hasOwnProperty(i)) continue; missing.
Internet上的许多其他实现通常具有Array.prototype.slice.call(this)步骤或if(!arr.hasOwnProperty(i)) continue; 失踪。
Familiarity with these built-in methods is not a difficult job for any JavaScript programmer. It’s nice for a programmer to be able to simply implement a built-in method.
对于任何JavaScript程序员来说,熟悉这些内置方法都不是一件容易的事。 对于程序员来说,能够简单地实现内置方法是很好的。
If a coder can implement a standard map method strictly according to the ECMAScript specification, this he must be a good JavaScript programmer.
如果编码人员可以严格按照ECMAScript规范实施标准map方法,那么他必须是一名优秀JavaScript程序员。
How to achieve this code effect?
如何实现这种编码效果?
We can see that when we try to print obj.a three times in a row, we get three different results. How incredible it seems!
我们可以看到,当我们尝试连续打印obj.a三次时,会得到三种不同的结果。 看起来多么不可思议!
Can you create a mysterious object obj to achieve this effect?
您可以创建一个神秘的对象obj来实现此效果吗?
In fact, there are three solutions to this question:
实际上,此问题有三种解决方案:
accessor property 访问者属性 Object.defineProperty Object.defineProperty Proxy 代理According to ECMAScript, an object’s properties can take two forms:
根据ECMAScript,对象的属性可以采用两种形式:
An Object is logically a collection of properties. Each property is either a data property or an accessor property:
从逻辑上讲,对象是属性的集合。 每个属性都是数据属性或访问器属性:
A data property associates a key value with an ECMAScript language value and a set of Boolean attributes.
数据属性将键值与ECMAScript语言值和一组布尔属性相关联。
An accessor property associates a key value with one or two accessor functions, and a set of Boolean attributes. The accessor functions are used to store or retrieve an ECMAScript language value that is associated with the property.
访问器属性将键值与一个或两个访问器函数以及一组布尔属性相关联。 访问器函数用于存储或检索与属性关联的ECMAScript语言值。
The so-called data property is what we usually write:
我们通常写的是所谓的数据属性:
let obj = { a: 1, b: 2}We have no more than two operations on the property of an object: reading the property and setting the property. For accessor property, we use get and set methods to define properties, which are written as follows:
我们对一个对象的属性只有两个操作:读取属性和设置属性。 对于访问器属性,我们使用get和set方法定义属性,其编写方式如下:
let obj = { get a(){ console.log('triggle get a() method') console.log('you can do anything as you want') return 1 }, set a(value){ console.log('triggle set a() method') console.log('you can do anything as you want') console.log(`you are trying to assign ${value} to obj.a`) }}Access properties give us powerful metaprogramming abilities, so we can accomplish our requirements in this way:
访问属性为我们提供了强大的元编程能力,因此我们可以通过以下方式满足我们的要求:
let obj = { _initValue: 0, get a() { this._initValue++; return this._initValue }}console.log(obj.a, obj.a, obj.a)The second way is to use Object.defineProperty, which works the same way we used to access properties, except instead of declaring access properties directly, we configure access properties via Object.defineProperty.
第二种方法是使用Object.defineProperty ,它的工作方式与访问属性相同,除了直接声明访问属性,我们通过Object.defineProperty配置访问属性。
This is a little bit more flexible to use, so we can write it like this:
这使用起来更加灵活,因此我们可以这样编写:
let obj = {}Object.defineProperty(obj, 'a', { get: (function(){ let initValue = 0; return function(){ initValue++; return initValue } })()})console.log(obj.a, obj.a, obj.a)In the get method here, we use a closure so that the variable initValue that we need to use is hidden in the closure and does not contaminate the other scope.
在这里的get方法中,我们使用了一个闭包,以便我们需要使用的变量initValue隐藏在闭包中,并且不会污染其他范围。
The third way is to use Proxy. If you don’t already know about Proxy, you can refer to an article I wrote earlier:
第三种方法是使用Proxy 。 如果您还不了解Proxy,可以参考我之前写的文章:
With Proxy, we can intercept access to object properties. As long as we use Proxy to intercept the access to obj.a, and then return 1, 2, and 3 in turn, we can complete the requirements before:
使用代理,我们可以拦截对对象属性的访问。 只要我们使用Proxy拦截对obj.a的访问,然后依次返回1、2和3,我们就可以完成以下要求:
let initValue = 0;let obj = new Proxy({}, { get: function(item, property, itemProxy){ if(property === 'a'){ initValue++; return initValue } return item[property] }})console.log(obj.a, obj.a, obj.a)Why is it important to understand this question? Because Object.defineProperty and Proxy gave us powerful metaprogramming abilities, we could modify our object appropriately to do something special.
为什么理解这个问题很重要? 因为Object.defineProperty和Proxy给了我们强大的元编程能力,所以我们可以适当地修改对象以做一些特殊的事情。
In the Vue, a well-known front-end framework, one of its core mechanisms is the two-way binding of data. In Vue2.0, Vue implemented this mechanism by using Object.defineProperty; in Vue3.0, Proxy is used to accomplish this mechanism.
在著名的前端框架Vue中,其核心机制之一是数据的双向绑定。 在Vue2.0中,Vue通过使用Object.defineProperty实现了此机制; 在Vue3.0中,代理用于完成此机制。
You can’t really understand how a framework like Vue works if you don’t master this. If you master these principles, you will get twice the result with half the effort in learning Vue.
如果您不掌握Vue这样的框架,您将无法真正理解。 如果您掌握了这些原则,则只需学习Vue的一半,就可以获得两倍的结果。
What is the result of running this code?
运行此代码的结果是什么?
function foo(a,b) { console.log(b) return { foo:function(c){ return foo(c,a); } };}let res = foo(0); res.foo(1); res.foo(2); res.foo(3);The above code has multiple nested functions and three foo in nested functions at the same time, which at first glance looks very tedious. So how do we make sense of this?
上面的代码同时具有多个嵌套函数和三个foo嵌套函数,乍一看看起来非常乏味。 那么,我们如何理解这一点呢?
First, let’s make sure how many functions are there in the above code? We can see that the keyword function is used in two places in the code above, so there are two functions in the code above, namely the first line function foo(a,b) { and the fourth line foo:function(c){. And these two functions have the same name.
首先,让我们确保上面的代码中有多少个函数? 我们可以看到在上面的代码中两个地方都使用了关键字function ,因此上面的代码中有两个函数,即第一行function foo(a,b) {和第四行foo:function(c){ 。 并且这两个函数具有相同的名称。
Second question: Which function is called by foo (c, a) on line 5? If you’re not sure, let’s take a look at a simpler example:
第二个问题:第5行的foo (c, a)调用哪个函数? 如果不确定,让我们看一个简单的例子:
var obj={ fn:function (){ console.log(fn); }};obj.fn()Does this code throw an exception if we run it? The answer is yes.
如果我们运行该代码,是否会引发异常? 答案是肯定的。
This is because the upper scope of the obj.fn() method is global and the fn method inside obj cannot be accessed.
这是因为obj.fn()方法的上限是全局的,并且无法访问obj内部的fn方法。
Going back to our previous example, by the same logic, when we call foo(c, a), we’re actually calling the foo function on the first line.
回到前面的示例,以同样的逻辑,当我们调用foo(c, a) ,实际上是在第一行上调用foo函数。
And when we call res.foo(1), which foo is called? Obviously, the foo function on the 4th line is called.
当我们调用res.foo(1) ,将调用哪个foo ? 显然,第4行的foo函数被调用。
Because the two foo functions do not work the same way, we can change the name of one of them to bar to make it easier for us to understand the code.
由于这两个foo函数的工作方式不同,因此我们可以将其中一个的名称更改为bar以使我们更容易理解代码。
function foo(a,b) { console.log(b) return { bar:function(c){ return foo(c,a); } };}let res = foo(0); res.bar(1); res.bar(2); res.bar(3);This change will not affect the final result but will make it easier for us to understand the code. Try this tip if you meet a similar question in the future.
此更改不会影响最终结果,但会使我们更容易理解代码。 如果将来遇到类似的问题,请尝试此技巧。
Each time a function is called, a new scope is created, so we can draw the diagram to help us understand the logic of how the code works.
每次调用一个函数时,都会创建一个新的作用域,因此我们可以绘制图表以帮助我们理解代码工作原理的逻辑。
When we execute let res = foo(0);, we’re actually executing foo(0, undefiend). At this point, a new scope is created in the program, in the current scope, a=0, b=undefined. So the diagram that I’ve drawn looks something like this.
当我们执行let res = foo(0); ,我们实际上正在执行foo(0, undefiend) 。 此时,将在程序中创建一个新作用域,在当前作用域中a=0 , b=undefined 。 因此,我绘制的图看起来像这样。
Then console.log(b) will be executed, so the first time it prints out ‘undefined’ in the console.
然后将执行console.log(b) ,因此它第一次在控制台中打印出“ undefined”。
Then res.bar(1) is executed, creating a new scope where c=1:
然后执行res.bar(1) ,创建一个新范围,其中c = 1:
And then foo(c, a) is called again from the above function, which is actually foo(1, 0), and the scope looks like this:
然后从上面的函数中再次调用foo(c, a) ,它实际上是foo(1, 0) ,作用域如下所示:
In the new scope, the value of a is 1 and the value of b is 0, so the console will print out the 0.
在新作用域中, a值为1, b值为0,因此控制台将打印出0。
Again, res.bar(2) is executed next. Notice that res.bar(2) and res.bar(1) are parallel relations, so we should draw the scope diagram like this:
同样, res.bar(2)执行res.bar(2) 。 注意, res.bar(2)和res.bar(1)是并行关系,因此我们应该像这样绘制范围图:
So in this code, the console prints out the value 0 too.
因此,在此代码中,控制台也会打印出值0。
The same goes for the process that executes res.bar(3), and the console still prints a 0.
执行res.bar(3)的过程也是如此,控制台仍显示0。
So the end result of the code above is:
因此,以上代码的最终结果是:
In fact, the above question can be changed in other ways. For example, it can be changed into the following:
实际上,上述问题可以用其他方式改变。 例如,可以将其更改为以下内容:
function foo(a,b) { console.log(b) return { foo:function(c){ return foo(c,a); } };}foo(0).foo(1).foo(2).foo(3);Before we solve this question, the first thing we need to do is distinguish between the two different foo functions, so the above code can be changed to look like this:
在解决此问题之前,我们需要做的第一件事就是区分两个不同的foo函数,因此可以将上面的代码更改如下:
function foo(a,b) { console.log(b) return { bar:function(c){ return foo(c,a); } };}foo(0).bar(1).bar(2).bar(3);When it executes foo(0), the scope is the same as before, and then the console will print out ‘undefined’.
当它执行foo(0) ,作用域与以前相同,然后控制台将打印出“ undefined”。
Then execute .bar(1) to create a new scope. This parameter 1 is actually the value of c.
然后执行.bar(1)创建一个新的作用域。 此参数1实际上是c的值。
Then the .bar(1) method calls foo(c, a) again, which is actually foo(1, 0). Parameter 1 here is actually going to be the value of a in the new scope, and 0 is going to be the value of b in the new scope.
然后.bar(1)方法再次调用foo(c, a) ,实际上是foo(1, 0) 。 这里的参数1实际上将是新作用域中a的值,而0将是新作用域中b的值。
So the console then prints out the value of b, which is 0.
因此,控制台然后打印出b的值,即0。
.bar(2) is called again, with 2 as the value of c in the new scope:
再次调用.bar(2) ,在新作用域中c的值为2:
And then .bar(2) calls foo(c, a), which is actually foo(2, 1), where 2 is the value of a in the new scope, and 1 is the value of b in the new scope.
然后.bar(2)调用foo(c, a)这实际上是foo(2, 1)其中,2是值a在新的范围,1是的值b在新的范围。
So the console then prints out the value of b, which is 0.
因此,控制台然后打印出b的值,即0 。
And then it will execute .bar(3), the process is the same as before, so I’m not going to expand the description, this step the console prints out the 2.
然后它将执行.bar(3) ,其过程与之前相同,因此我将不扩展描述,此步骤将控制台打印出2。
As mentioned above, the final result of the code running is:
如上所述,代码运行的最终结果是:
Well, after a long journey, we finally got the answer. This question is a good test of the interviewee’s understanding of closures and scopes.
好了,经过漫长的旅程,我们终于得到了答案。 这个问题很好地检验了受访者对闭包和范围的理解。
Let’s say we have a function that looks like this:
假设我们有一个看起来像这样的函数:
function compose (middleware) { // some code}The compose function accepts a function array middleware :
compose函数接受函数数组middleware :
let middleware = []middleware.push((next) => { console.log(1) next() console.log(1.1)})middleware.push((next) => { console.log(2) next() console.log(2.1)})middleware.push(() => { console.log(3)})let fn = compose(middleware)fn()When we try to execute fn, it calls the functions in the middleware and passes the next function as a parameter to each small function.
当我们尝试执行fn ,它将调用中间件中的函数,并将next函数作为参数传递给每个小函数。
If we execute next in a small function, the next function of this function in middleware is called. And if you don’t execute next, the program doesn’t go down.
如果我们在一个小函数中执行next ,则将调用中间件中该函数的next函数。 而且,如果您不执行next ,该程序也不会失败。
After executing the above code, we get the following result:
执行完上面的代码后,我们得到以下结果:
1232.11.1So how can we write a compose function to do that?
那么我们如何编写一个compose函数来做到这一点呢?
First of all, the compose function must return a composed function, so we can write code like this:
首先,compose函数必须返回一个composed函数,因此我们可以编写如下代码:
function compose (middleware) { return function () { }}Then, in the returned function, the first function from the middleware starts executing. We will also pass the next function as its argument. So let’s write it this way:
然后,在返回的函数中,中间件中的第一个函数开始执行。 我们还将传递next函数作为其参数。 所以让我们这样写:
function compose (middleware) { return function () { let f1 = middleware[0] f1(function next(){ }) }}The next function acts as a switch that continues through the middleware, which looks like this:
下一个功能充当继续在中间件中运行的开关,如下所示:
function compose (middleware) { return function () { let f1 = middleware[0] f1(function next(){ let f2 = middleware[1] f2(function next(){ ... }) }) }}Then proceed to call the third function in the next function… Wait, this looks like recursion! So we can write a recursive function to complete this nested call:
然后继续在下一个函数中调用第三个函数…等待,这看起来像递归! 因此,我们可以编写一个递归函数来完成此嵌套调用:
function compose (middleware) { return function () { dispatch(0) function dispatch (i) { const fn = middleware[i] if (!fn) return null fn(function next () { dispatch(i + 1) }) } }}Okay, so that’s our compose function, so let’s test it out:
好的,这就是我们的compose函数,所以让我们对其进行测试:
Well, this function does exactly what it needs to do. But we can also optimize that our compose function can support asynchronous functions. We can improve the following code:
好的,此功能完全可以完成所需的工作。 但是我们也可以优化我们的compose函数可以支持异步函数。 我们可以改进以下代码:
function compose (middleware) { return async function () { await dispatch(0) function async dispatch (i) { const fn = middleware[i] if (!fn) return null await fn(function next () { dispatch(i + 1) }) } }}Actually, the compose function above is the core mechanism of the well-known node framework koa.
实际上,以上的compose功能是众所周知的节点框架koa的核心机制。
When I choose a candidate, I accept that he/she is not familiar with some of the frameworks. After all, there are so many libraries and frameworks in the JavaScript ecosystem that no one can master them all. But I do want the candidate to be aware of these important vanilla JavaScript tricks because they are the foundation of all libraries and frameworks.
当我选择候选人时,我接受他/她对某些框架不熟悉。 毕竟,JavaScript生态系统中有太多的库和框架,没有人能完全掌握它们。 但是我确实希望候选人知道这些重要的原始JavaScript技巧,因为它们是所有库和框架的基础。
Actually, there are some other interview questions in my draft, but I will not continue to explain them here due to the limited length of the article. I’ll share it with you later.
实际上,我的草稿中还有其他一些面试问题,但由于本文篇幅有限,在此不再赘述。 稍后再与您分享。
This article mainly deals with vanilla JavaScript, not browsers, node, frameworks, algorithms, design patterns, etc. If you are also interested in these topics, feel free to leave a comment.
本文主要涉及普通JavaScript,而不涉及浏览器,节点,框架,算法,设计模式等。如果您对这些主题也感兴趣,请随时发表评论。
Thank you for reading!
感谢您的阅读!
Did you know that we have three publications and a YouTube channel? Find links to everything at plainenglish.io!
您知道我们有三个出版物和一个YouTube频道吗? 在plainenglish.io上找到所有内容的链接!
翻译自: https://medium.com/javascript-in-plain-english/i-use-these-4-questions-to-find-outstanding-javascript-developers-4a468ea17155
相关资源:四史答题软件安装包exe