JavaScript 问题
本章节是前端开发者面试问题 - JS 部分的参考答案。 欢迎提出 PR 进行建议和指正!
- 请解释事件委托(event delegation)。
- 请简述
JavaScript中的this。 - 请解释原型继承(prototypal inheritance)的工作原理。
- 说说你对 AMD 和 CommonJS 的了解。
- 请解释下面代码为什么不能用作 IIFE:
function foo(){ }();,需要作出哪些修改才能使其成为 IIFE? null、undefined和未声明变量之间有什么区别?如何检查判断这些状态值?- 什么是闭包(closure),为什么使用闭包?
- 请说明
.forEach循环和.map()循环的主要区别,它们分别在什么情况下使用? - 匿名函数的典型应用场景是什么?
- 你如何组织自己 的代码?(使用模块模式(module pattern)还是经典继承(classical inheritance)?)
- 宿主对象(host objects)和原生对象(native objects)的区别是什么?
- 下列语句有什么区别:
function Person(){}、var person = Person()和var person = new Person()? .call和.apply有什么区别?- 请说明
Function.prototype.bind的用法。 - 什么时候会用到
document.write()? - 功能检测(feature detection)、功能推断(feature inference)和使用 UA 字符串之间有什么区别?
- 请尽可能详细地解释 Ajax。
- 使用 Ajax 的优缺点分别是什么?
- 请说明 JSONP 的工作原理,它为什么不是真正的 Ajax?
- 你使用过 JavaScript 模板吗?用过什么相关的库?
- 请解释变量提升(hoisting)。
- 请描述事件冒泡。
- “attribute” 和 “property” 之间有什么区别?
- 为什么扩展 JavaScript 内置对象是不好 的做法?
- document 中的
load事件和DOMContentLoaded事件之间的区别是什么? ==和===的区别是什么?- 请解释关于 JavaScript 的同源策略。
- 请使下面的语句生效:
- 请说明三元表达式中“三元”这个词代表什么?
- 什么是
"use strict";?使用它有什么优缺点? - 创建一个循环,从 1 迭代到 100,
3的倍数时输出 "fizz",5的倍数时输出 "buzz",同时为3和5的倍数时输出 "fizzbuzz"。 - 为什么不要使用全局作用域?
- 为什么要使用
load事件?这个事件有什么缺点吗?你知道一些代替方案吗,为什么使用它们? - 请解释单页应用是什么,如何使其对 SEO 友好。
- 你对 Promises 及其 polyfill 的掌握程度如何?
Promise代替回调函数有什么优缺点?- 用 转译成 JavaScript 的语言写 JavaScript 有什么优缺点?
- 你使用什么工具和技巧调试 JavaScript 代码?
- 你使用什么语句遍历对象的属性和数组的元素?
- 请解释可变对象和不可变对象之间的区别。
- 请解释同步和异步函数之间的区别。
- 什么是事件循环?调用堆栈和任务队列之间有什么区别?
- 请解释
function foo() {}和var foo = function() {}之间foo的用法上的区别。 - 使用
let、var和const创建变量有什么区别? - ES6 的类和 ES5 的构造函数有什么区别?
- 你能给出一个使用箭头函数的例子吗,箭头函数与其他函数有什么不同?
- 在构造函数中使用箭头函数有什么好处?
- 高阶函数(higher-order)的定义是什么?
- 请给出一个解构(destructuring)对象或数组的例子。
- ES6 的模板字符串为生成字符串提供了很大的灵活性,你可以举个例子吗?
- 你能举出一个柯里化函数(curry function)的例子吗?它有哪些好处?
- 使用扩展运算符(spread)的好处是什么,它与使用剩余参数语句(rest)有什么区别?
- 如何在文件之间共用代码?
- 什么情况下会用到静态类成员?
- 其他答案
请解释事件委托(event delegation)。
事件委托是将事件监听器添加到父元素,而不是每个子元素单独设置事件监听器。当触发子元素时,事件会冒泡到父元素,监听器就会触发。这种技术的好处是:
- 内存占用减少,因为只需要一个父元素的事件处理程序,而不必为每个后代都添加事件处理程序。
- 无需从已删除的元素中解绑处理程序,也无需将处理程序绑定到新元素上。
参考
- https://davidwalsh.name/event-delegate
- https://stackoverflow.com/questions/1687296/what-is-dom-event-delegation
请简述JavaScript中的this。
JS 中的this是一个相对复杂的概念,不是简单几句能解释清楚的。粗略地讲,函数的调用方式决定了this的值。我阅读了网上很多关于this的文章,Arnav Aggrawal 写的比较清楚。this取值符合以下规则:
- 在调用函数时使用
new关键字,函数内的this是一个全新的对象。 - 如果
apply、call或bind方法用于调用、创建一个函数,函数内的 this 就是作为参数传入这些方法的对象。 - 当函数作为对象里的方法被调用时,函数内的
this是调用该函数的对象。比如当obj.method()被调用时,函数内的 this 将绑定到obj对象。 - 如果调用函数不符合上述规则,那么
this的值指向全局对象(global object)。浏览器环境下this的值指向window对象,但是在严格模式下('use strict'),this的值为undefined。 - 如果符合上述多个规则,则较高的规则(1 号最高,4 号最低)将决定
this的值。 - 如果该函数是 ES2015 中的箭头函数,将忽略上面的所有规则,
this被设置为它被创建时的上下文。
想获得更深入的解释,请查看他在 Medium 上的文章。
参考
- https://codeburst.io/the-simple-rules-to-this-in-javascript-35d97f31bde3
- https://stackoverflow.com/a/3127440/1751946
请解释原型继承(prototypal inheritance)的工作原理。
这是一个非常常见的 JavaScript 问题。所有 JS 对象都有一个__proto__属性,指向它的原型对象。当试图访问一个对象的属性时,如果没有在该对象上找到,它还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。这种行为是在模拟经典的继承,但是与其说是继承,不如说是委托(delegation)。
参考
- https://www.quora.com/What-is-prototypal-inheritance/answer/Kyle-Simpson
- https://davidwalsh.name/javascript-objects
说说你对 AMD 和 CommonJS 的了解。
它们都是实现模块体系的方式,直到 ES2015 出现之前,JavaScript 一直没有模块体系。CommonJS 是同步的,而 AMD(Asynchronous Module Definition)从全称中可以明显看出是异步的。CommonJS 的设计是为服务器端开发考虑的,而 AMD 支持异步加载模块,更适合浏览器。
我发现 AMD 的语法非常冗长,CommonJS 更接近其他语言 import 声明语句的用法习惯。大多数情况下,我认为 AMD 没有使用的必要,因为如果把所有 JavaScript 都捆绑进一个文件中,将无法得到异步加载的好处。此外,CommonJS 语法上更接近 Node 编写模块的风格,在前后端都使用 JavaScript 开发之间进行切换时,语境的切换开销较小。
我很高兴看到 ES2015 的模块加载方案同时支持同步和异步,我们终于可以只使用一种方案了。虽然它尚未在浏览器和 Node 中完全推出,但是我们可以使用代码转换工具进行转换。
参考
- https://auth0.com/blog/javascript-module-systems-showdown/
- https://stackoverflow.com/questions/16521471/relation-between-commonjs-amd-and-requirejs
请解释下面代码为什么不能用作 IIFE:function foo(){ }();,需要作出哪些修改才能使其成为 IIFE?
IIFE(Immediately Invoked Function Expressions)代表立即执行函数。 JavaScript 解析器将 function foo(){ }();解析成function foo(){ }和();。其中,前者是函数声明;后者(一对括号)是试图调用一个函数,却没有指定名称,因此它会抛出Uncaught SyntaxError: Unexpected token )的错误。
修改方法是:再添加一对括号,形式上有两种:(function foo(){ })()和(function foo(){ }())。以上函数不会暴露到全局作用域,如果不需要在函数内部引用自身,可以省略函数的名称。
你可能会用到 void 操作符:void function foo(){ }();。但是,这种做法是有问题的。表达式的值是undefined,所以如果你的 IIFE 有返回值,不要用这种做法。例如:
const foo = void (function bar() {
return 'foo';
})();
console.log(foo); // undefined
参考
- http://lucybain.com/blog/2014/immediately-invoked-function-expression/
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void