所面临的挑战
在今天的科技领域, JavaScript已经成为客户端web开发的同义词, 随着像Node这样的技术和JavaScript框架的出现.js, Vue.js和React.js, JavaScript也正在成为一个占主导地位的服务器端技术.
相应的, 在软件开发社区中,引用至少某种程度的JavaScript经验的全栈开发人员简历基本上已经成为普遍现象. 这使得定位JavaScript web开发人员相当容易, 但这也让筛选他们以找到“少数精英”更具挑战性. 找到他们需要一个高效的招聘过程,正如我们在帖子中描述的那样 在寻找精英少数-寻找和雇用最好的开发人员在行业 . 这样的过程可以通过问题(比如本文中提出的问题)进行扩充,以确定那些分散在全球各地的候选人,谁是真正的JavaScript专家.
是的,我懂JavaScript…
与任何技术一样,首先要了解JavaScript开发,然后才有 really 知道JavaScript. 在我们寻找真正的语言大师的过程中, 我们需要一个面试过程,能够按照JavaScript专业水平连续体准确量化候选人的职位.
为了这个目标, 这篇文章提供了一些问题样本,这些问题是评估应聘者对JavaScript掌握程度的广度和深度的关键. 但是,重要的是要记住,这些示例问题仅仅是作为一个指南. 不是每个值得雇佣的“A”级求职者都能正确地回答所有问题, 也不能保证全部回答都能得“A”. 说到底,招聘既是一门科学,也是一门艺术.
评估基金会
我们很容易遇到“有经验的”JavaScript程序员,他们对语言基础的掌握不是很薄弱就是很混乱.
JavaScript是一种基于原型的动态类型脚本语言. JavaScript可以, at first, 对于熟悉基于类的语言(如Java或c++)的开发人员来说有点困惑, 因为它是动态的,不提供传统的类实现. 因此,经常会遇到“有经验的”JS开发人员,他们对语言基础的掌握不是很弱就是很混乱.
这些问题可以帮助评估开发人员对JavaScript基础知识的掌握程度, 包括一些更微妙的细微差别, 因此是面试过程的重要组成部分. 下面是一些例子。
问:用JavaScript描述一下继承和原型链. 举个例子.
虽然JavaScript是面向对象的语言, 它是基于原型的,没有实现传统的基于类的继承系统.
在JavaScript中,每个对象都在内部引用另一个对象,称为its 原型 . 该原型对象又有一个对其原型对象的引用,依此类推. 在这个原型链的末尾是一个以null为原型的对象. 原型链是用于继承的机制 原型继承 准确地说,是在JavaScript中实现的. 特别是, 当引用对象本身不包含的属性时, 遍历原型链,直到找到引用的属性(或者直到到达链的末尾), 在这种情况下,属性是未定义的).
这里有一个简单的例子:
function Animal() {this.eatsVeggies = true; this.eatsMeat = false; }
函数食草动物() {}
食草动物.原型 =新动物();
function食肉动物(){this.eatsMeat = true; }
食肉动物.原型 =新动物();
var rabbit = new草食动物();
var bear =新食肉动物();
console.日志(兔子.eatsMeat); // logs "false"
console.log(bear.eatsMeat); // logs "true"
Q:比较JavaScript中的对象和哈希表.
This is somewhat of a trick question since, in JavaScript, objects essentially are hashtables; i.e.,名称-值对的集合. 在这些名称-值对中,需要注意的关键点是名称(a.k.a.,键)总是字符串. 这实际上把我们引向了下一个问题……
问:考虑下面的代码片段(source ). 警报会显示什么? 解释你的答案.
var foo = new Object();
var bar = new Object();
var map = new Object();
Map [foo] = "foo";
Map [bar] = "bar";
alert(map[foo]); // what will this display??
很少有人能正确回答这是在提醒字符串" bar ". 大多数人会错误地回答它会提醒字符串" foo ". 那么,让我们来理解为什么“酒吧”确实是正确的答案,尽管令人惊讶……
正如在前一个问题的答案中所提到的, JavaScript对象本质上是一个名称-值对哈希表,其中名称(i.e.,键)是字符串. 确实是这样 always strings. In fact, 当JavaScript中使用字符串以外的对象作为键时, no error occurs; rather, JavaScript silently 将其转换为字符串并使用该值作为键. 这可能会产生令人惊讶的结果,如上面的代码所示.
要理解上面的代码片段,首先必须认识到 map
所显示的对象 not 映射对象 foo
到字符串“foo”,也不映射对象 bar
到字符串bar. 因为对象 foo
and bar
当它们被用作键时,不是字符串吗 map
, JavaScript使用每个对象的键值自动将键值转换为字符串 toString ()
method. 既然都不是 foo
nor bar
定义自己的自定义 toString ()
方法,它们都使用相同的默认实现. 该实现在调用时只生成字面字符串“[object object]”. 记住这个解释, 让我们重新检查上面的代码片段, 但这一次附带了解释性的评论:
var foo = new Object();
var bar = new Object();
var map = new Object();
Map [foo] = "foo"; // --> map["[object Object]"] = "foo";
Map [bar] = "bar"; // --> map["[object Object]"] = "bar";
//替换第一个映射!
alert(map[foo]); // --> alert(map["[object Object]"]);
// and since map["[object object]"] = "bar",
//这将提示"bar"而不是"foo"!!
/ /惊讶! ;-)
问:用JavaScript解释闭包. 它们是什么? 它们有哪些独特之处呢? 如何以及为什么要使用它们? 提供一个例子.
闭包是一个函数, 以及闭包创建时范围内的所有变量或函数. In JavaScript, a closure is implemented as an “inner function”; i.e.,定义在另一个函数体内的函数. 下面是一个简单的例子:
(函数outerFunc(outerArg) {
var outerVar = 3;
(函数middleFunc(middlelearg) {
var middlelevar = 4;
(函数innerFunc(innerArg) {
var innerVar = 5;
//闭包中scope的例子:
//从innerFunc, middleFunc和outerFunc的变量
//以及全局命名空间都在这里的作用域内.
console.日志(" outerArg = " + outerArg +
“middleArg = " + middleArg +
“innerArg = " + innerArg + +“\ n”
“outerVar = " + outerVar +
“middleVar = " + middleVar +
“innerVar = " + innerVar);
// ---------------这将记录:---------------
// outerArg=123 middlelearg =456 innerArg=789
// outerVar=3 middleVar=4 innerVar=5
})(789);
})(456);
})(123);
闭包的一个重要特性是,内部函数仍然可以访问外部函数的变量 after 外部函数已经返回. 这是因为,当JavaScript中的函数执行时,它们使用的是有效的作用域 当它们被创造的时候 .
这导致了一个常见的困惑点, though, 是基于这样一个事实,内部函数访问的值,外部函数的变量的时间 invoked (而不是当时的情况 created ). 测试应聘者对这种细微差别的理解, 演示以下代码片段,动态创建5个按钮,并询问当用户单击第三个按钮时将显示什么:
函数addButtons(numButtons) {
for (var i = 0; i < numButtons; i++) {
Var按钮=文档.createElement(“输入”);
button.Type = 'button';
button.value = '按钮' + (i + 1);
button.Onclick = function() {
alert('Button ' + (i + 1) + ' clicked');
};
document.body.列表末尾(按钮);
document.body.列表末尾(文档.createElement (br));
}
}
window.onload = function() { addButtons(5); };
许多人会错误地回答,当用户点击第三个按钮时,将显示“按钮3单击”. In fact, 上面的代码包含一个错误(基于对闭包工作方式的误解),当用户单击时将显示“按钮6单击” any 五个按钮中的一个. 这是因为,onclick方法是 invoked (for any 对于按钮),for循环已经完成,变量 i
值已经是5.
一个重要的后续问题是询问应聘者如何修复上面代码中的错误, 从而产生预期的行为(i.e.,因此,点击按钮n将显示“按钮n已点击”). 正确的答案,演示了闭包的正确使用,如下所示:
函数addButtons(numButtons) {
for (var i = 0; i < numButtons; i++) {
Var按钮=文档.createElement(“输入”);
button.Type = 'button';
button.value = '按钮' + (i + 1);
//这里是修复:
//使用立即调用函数表达式
//模式来实现所需的行为:
button.onclick = function(buttonIndex) {
返回函数(){
alert('Button ' + (buttonIndex + 1) + ' clicked');
};
}(i);
document.body.列表末尾(按钮);
document.body.列表末尾(文档.createElement (br));
}
}
window.onload = function() { addButtons(5); };
尽管它绝不是JavaScript独有的, 闭包对于许多现代JavaScript编程范例来说是一个特别有用的构造. 它们被一些最流行的JavaScript库广泛使用,比如jQuery和Node.js.
拥抱多样性
JavaScript容纳了大量不同寻常的编程技术和设计模式. JavaScript高手会很清楚选择一种方法和选择另一种方法的意义和后果. another.
多范式语言, JavaScript支持面向对象, 必要的, 函数式编程风格. As such, JavaScript容纳了大量不同寻常的编程技术和设计模式. JavaScript高手会很清楚这些替代方案的存在, 更重要的是, 选择一种方法而不是另一种方法的意义和后果. 下面是几个示例问题,可以帮助评估应聘者在这方面的专业知识:
问:请描述一下创建对象的不同方法以及每种方法的分支. 提供的例子.
下图对比了JavaScript中创建对象的各种方法,以及每种方法产生的原型链的差异.
问:将函数定义为函数表达式(e.g., Var foo = function(){}
)或作为函数语句(e.g., 函数foo () {}
)? 解释你的答案.
是的,根据函数值的赋值方式和赋值时间的不同,会有区别.
当一个函数语句(e.g., 函数foo () {}
)被使用,函数 foo
可以在定义之前引用,通过一种称为“提升”的技术。. 提升的一个分支是,函数的最后一个定义将被使用, 不管它是什么时候被引用的(如果不清楚的话), 下面的示例代码应该有助于澄清一些事情).
相反,当函数表达式(e.g., Var foo = function(){}
)被使用,函数 foo
在定义之前不能被引用,就像任何其他赋值语句一样. 正因为如此, 函数的最新定义是将要使用的定义, 定义必须在引用之前, 否则函数将是未定义的).
下面是一个简单的示例,演示了两者之间的实际差异. 考虑下面的代码片段:
function foo() { return 1; }
alert(foo()); // what will this alert?
function foo() { return 2; }
许多JavaScript开发人员会错误地回答上面的警告将显示“1”,并惊讶地发现它实际上将显示“2”. 如上所述,这是由于吊装. 因为函数语句是用来定义函数的, 函数的最后一个定义是在调用它时被提升的定义(即使它是在代码中调用它之后的定义)!).
现在考虑下面的代码片段:
var foo = function() { return 1; }
alert(foo()); // what will this alert?
foo = function() { return 2; }
在本例中,答案更加直观,警报将如预期的那样显示“1”. 因为使用了函数表达式来定义函数, 函数的最新定义是在调用时使用的定义.
细节决定成败
除了到目前为止讨论的高级JavaScript概念, 有许多底层的语法细节是真正的JavaScript大师应该非常熟悉的. 下面是一些例子。
问:有什么意义, 原因是, 将JavaScript源文件的全部内容包装在一个函数块中?
这是一种越来越普遍的做法,许多流行的JavaScript库(jQuery, Node.js, etc.). 该技术在文件的全部内容周围创建一个闭包, 也许最重要的是, 创建私有名称空间,从而帮助避免不同JavaScript模块和库之间潜在的名称冲突.
这种技术的另一个特点是允许全局变量的别名易于引用(可能更短). 这是经常使用的,例如,在jQuery插件. jQuery允许您禁用 $
引用jQuery命名空间,使用 jQuery.noConflict ()
. 如果已经这样做了,你的代码仍然可以使用这种闭包技术,如下所示:
(function($) {/* jQuery插件代码引用$ */})(jQuery);
问:两者有什么区别 ==
and ===
? Between !=
and !==
? 举个例子.
“三重”比较运算符之间的区别(===
, !==
)和双重比较运算符(==
, !=
)在JavaScript中,双比较操作符在比较操作数之前执行隐式类型转换, 使用三重比较运算符, 不执行类型转换(i.e.,值必须相等,类型必须相同,操作数才被认为相等).
作为一个简单的例子,表达式 123 == '123'
求值是否为真,而 123 === '123'
会计算为false.
问:包容的意义是什么 使用严格的
在JavaScript源文件的开头?
虽然关于这个话题还有很多可说的,但这里最简短和最重要的答案是 使用严格的
是一种在运行时对JavaScript代码强制执行更严格的解析和错误处理的方法吗. 原本会被忽略或默默失败的代码错误现在将生成错误或抛出异常. 总的来说,这是一种很好的做法.
Wrap Up
JavaScript可能是当今存在的最被误解和低估的编程语言之一. 一个人剥JavaScript的洋葱皮剥得越多,就越能意识到什么是可能的. JavaScript足够通用,前端和后端开发人员都可以使用. 相应的, 在美国或国外找到真正精通这门语言的人担任全职或兼职工作是一项挑战. 我们希望您能发现本文中提出的问题是一个有用的基础,在您寻找最好的JavaScript开发人员中“少数精英”加入您的开发团队时,可以“去芜有貉”.