资讯专栏INFORMATION COLUMN

JS 面试题总结

TesterHome / 3605人阅读

摘要:即为被污染的命名,访问没有被声明的变量,会抛出异常,终止执行。请举出一个匿名函数的典型用例匿名函数可以用作回调函数执行,可以防止全局变量污染。在框架中常使用匿名函数来避免全局变量的污染。

请解释事件代理 (event delegation)

事件委托技术能让你避免对特定的每个节点添加事件监听器;相反,事件监听器是被添加到它们的父元素上。事件监听器会分析从子元素冒泡上来的事件,找到是哪个子元素的事件。

优点:

性能得到了优化(需要创建的以及驻留在内存中的事件处理器少了)

动态添加的元素也能绑定事件了

请解释 JavaScript 中 this 是如何工作的

this 永远指向函数运行时所在的对象,而不是函数被创建时所在的对象。

函数调用es3和非严格es5为全局对象,严格es5为undefined

方法调用this指向调用该方法的对象(调用上下文)

构造函数时,this指向新创建的对象

call() apply() 调用方法时,this指向调用方法的对象,而不是该方法拥有者对象

请解释原型继承 (prototypal inheritance) 的原理?

原型继承的基础是原型链查找。

原型链查找基本概念:
每一个函数 F 都有一个原型对象(prototype)F.prototype
每一个函数都可以通过 new 关键字化身成为一个类构造函数,new F 会产生一个对象 O
在调用对象的某个属性或者方法,比如 http://O.xxx 的时候,会首先查找对象自身是否有这个方法或者属性,如果没找到就会去对象的构造函数的原型对象中查找(注意有两个定语),也就是查找 O 的构造函数 F 的原型对象 http://F.prototype.xxx
F.prototype 也是一个对象,查找 http://F.prototype.xxx 的时候会重复第 3 步的过程

请解释为什么接下来这段代码不是 IIFE (立即调用的函数表达式):function foo(){ }();要做哪些改动使它变成 IIFE?

 这里只是声明一个叫foo的function,直接用()执行这样是不成功的,想要变成IIFE就要把声明变成表达式,就可以立即执行了,可以这样(function foo(){})()或者(function foo(){}()),这就是用括号把定义强转成表达式,当然还有其他方法,关键就是声明不可以执行,表达式才可以执行。

描述以下变量的区别:null,undefined 或 undeclared? 该如何检测它们?

undefined:未定义,在变量没有赋值的时候的值即为undefined。“缺少值”,就是此处应该有一个值,但是还没有定义。
underclared:即为被污染的命名,访问没有被声明的变量,会抛出异常,终止执行。尝试访问一个undeclared的变量,浏览器会报错,JS执行会中断。
null:是一个空的对象引用。“没有对象”,即该处不应该有值

区别:
undefined和null在if语句中,都会被自动转为false,相等运算符甚至直接报告两者相等。typeof undefined会返回undefined ,而typeof null 总返回 object(typeof有六种可能:“number”、“string”、“boolean”、“object”、“function”、“undefined”)

false == undefined;//false
false == null;//false
null == undefined;//true

该如何检测它们?

var obj;
obj ===undefined; //检测undfined 方法一
typeof obj === ‘undefined’;//检测undefined方法2
obj = null;
obj === null;//来检测null
typeof null;//‘object’
什么是闭包 (closure),如何使用它,为什么要使用它?

定义:闭包就是可以读取到其他函数内部变量的函数。
闭包的用途:

可以读取函数内部的变量。(外界无法访问函数的内部的私有方法和变量,只能通过提供的接口访问)

让变量的值始终保持在内存中。

可以避免污染全局变量,实现私有方法或者变量等

注意:

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

请举出一个匿名函数的典型用例?

匿名函数可以用作回调函数执行,可以防止全局变量污染。
在 JS 框架中常使用匿名函数来避免全局变量的污染。
$.(“input”).each(function(e){this.val(‘OK’)});
(function(){})();
$(document).ready(function(){ });
$(function() {})

你是如何组织自己的代码?是使用模块模式,还是使用经典继承的方法? 请指出 JavaScript 宿主对象 (host objects) 和原生对象 (native objects) 的区别?

原生对象:独立于宿主环境的 ECMAScript 实现提供的对象。为array obj regexp date function等可以new实例化的对象。

内置对象:为gload Math 等,开发者不必明确实例化内置对象,它已被实例化了。类似于isNaN()、parseInt()和parseFloat()方法等,看起来都是函数,而实际上,它们都是Global对象的方法。具体可以参考 JavaScript 全局对象

宿主对象:即由 ECMAScript 实现的宿主环境(操作系统和浏览器)提供的对象。所有的BOM和DOM对象都是宿主对象。因为其对于不同的“宿主”环境所展示的内容不同(这就是兼容性和特性检测的缘由)。ECMAScript官方未定义的对象都属于宿主对象。

请指出以下代码的区别:function Person(){}、var person = Person()、var person = new Person()?

第一个为函数声明,第二个将函数person()返回值赋值给person,第三个通过Person()的构造器创建了一个对象让person变量引用该对象;

.call 和 .apply 的区别是什么?

call和apply都是调用一个对象的一个方法,以另一个对象替换当前对象。它们都属于Function.prototype的一个方法,所以每个function实例都有call和apply属性。这两个方法可以用来代替另一个对象调用一个方法,可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。

区别:
两者传递的参数不同,虽然函数第一个参数都是要传入给当前对象的对象,但是,apply的第二个参数是一个参数数组,将多个参数组合成为一个数组传入;而call第二个参数则是直接的参数列表。

请解释 Function.prototype.bind?

Function.prototype.bind()其实就是函数绑定。函数的接收者取决于他是如何被调用,可以通过调用.bind()给函数绑定作用域上下文(this的值),即函数的接收者。

var foo = { x: 3} 
var bar = function(){console.log(    this.x);}

bar(); // undefinedvar
boundFunc = bar.bind(foo);//隐式看作是在foo作用域里调用bar方法
boundFunc(); // 3

.bind()创建了一个函数,当这个函数在被调用的时候,它的 this 关键词会被设置成被传入的值(这里指调用bind()时传入的参数)也就是我们传入想要的上下文。 简单的用法: 关于 Function.prototype.bind() 内部,这里有个非常简单的例子:

Function.prototype.bind = function (scope) {
    var fn = this;
    return function () {
        return fn.apply(scope);//使用call效果一样
    };
}  
在什么时候你会使用 document.write()?

document.write()方法可以用在两个方面:

页面载入过程中用实时脚本创建页面内容,该方法需要一个字符串参数,它是写到窗口或框架中的HTML内容。

以及用延时脚本创建本窗口或新窗口的内容。该方法需要一个字符串参数,它是写到窗口或框架中的HTML内容。

记住,在载入页面后,浏览器输出流自动关闭。在此之后,任何一个对当前页面进行操作的document.write()方法将打开—个新的输出流,它将清除当前页面内容(包括源文档的任何变量或值)。因此,假如希望用脚本生成的HTML替换当前页面,就必须把HTML内容连接起来赋给一个变量,使用一个document.write()方法完成写操作。不必清除文档并打开一个新数据流,一个document.write()调用就可完成所有的操作。

关于document.write()方法还有一点要说明的是它的相关方法document.close()。脚本向窗口(不管是本窗口或其他窗口)写完内容后,必须关闭输出流。在延时脚本的最后一个document.write()方法后面,必须确保含有document.close()方法,不这样做就不能显示图像和表单。并且,任何后面调用的document.write()方法只会把内容追加到页面后,而不会清除现有内容来写入新值。为了演示document.write()方法,我们提供了同一个应用程序的两个版本。

大多数生成的广告代码依旧使用 document.write(),虽然这种用法会让人很不爽。

请指出浏览器特性检测,特性推断和浏览器 UA 字符串嗅探的区别?

检测浏览器的特殊名称和版本(用户代理检测)即浏览器UA字符串嗅探。浏览器嗅探技术可以快捷的将代码进行分支,以便针对不同的浏览器应用不同的指令;针对特定浏览器的特定版本,超出范围之外都是不可靠的

请尽可能详尽的解释 Ajax 的工作原理? 使用 Ajax 都有哪些优劣?

优势:可以刷新局部页面,而不用整体页面都刷新
缺点:用户禁用javascript的情况

请解释 JSONP 的工作原理,以及它为什么不是真正的 Ajax。

工作原理:JSONP动态创建script标签,回调函数。Ajax是页面无刷新请求数据操作,动态添加一个<script>标签,而script标签的src属性是没有跨域的限制的。这样说来,这种跨域方式其实与ajax XmlHttpRequest协议无关了。
当GET请求从被调用页面返回时,可以返回一段JavaScript代码,这段代码会自动调用主页面中的一个callback函数。

优点:不受同源策略的影响,它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果   
缺点:只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。

请解释变量声明提升 (hoisting)

在JavaScript代码运行之前其实是有一个编译阶段的。编译之后才是从上到下,一行一行解释执行。变量提升就发生在编译阶段,它把变量和函数的声明提升至作用域的顶端。(编译阶段的工作之一就是将变量与其作用域进行关联)。

变量提升需要注意两点:

提升的部分只是变量声明,赋值语句和可执行的代码逻辑还保持在原地不动

提升只是将变量声明提升到变量所在的变量范围的顶端,并不是提升到全局范围

函数声明:

变量声明和函数声明都会得到变量提升,但函数声明会最先得到提升,然后是变量声明(函数是一等公民)

对于函数声明来说,如果定义了相同的函数变量声明,后定义的声明会覆盖掉先前的声明

请描述事件冒泡机制 (event bubbling)

从目标元素开始,往顶层元素传播。途中如果有节点绑定了相应的事件处理函数,这些函数都会被依次触发。如果想阻止事件起泡,可以使用e.stopPropagation()(Firefox)或者e.cancelBubble=true(IE)来组织事件的冒泡传播

“attribute” 和 “property” 的区别是什么?

DOM元素的attribute和property两者是不同的东西。attribute翻译为“特性”,property翻译为“属性”。

attribute是一个特性节点,每个DOM元素都有一个对应的attributes属性来存放所有的attribute节点,attributes是一个类数组的容器,说得准确点就是NameNodeMap,不继承于Array.prototype,不能直接调用Array的方法。attributes的每个数字索引以名值对(name=”value”)的形式存放了一个attribute节点。

property就是一个属性,如果把DOM元素看成是一个普通的Object对象,那么property就是一个以名值对(name=”value”)的形式存放在Object中的属性。要添加和删除property和普通的对象类似。

很多attribute节点还有一个相对应的property属性,比如上面的div元素的id和class既是attribute,也有对应的property,不管使用哪种方法都可以访问和修改。

总之,attribute节点都是在HTML代码中可见的,而property只是一个普通的名值对属性

为什么扩展 JavaScript 内置对象不是好的做法?

因为你不知道哪一天浏览器或javascript本身就会实现这个方法,而且和你扩展的实现有不一致的表现。到时候你的javascript代码可能已经在无数个页面中执行了数年,而浏览器的实现导致所有使用扩展原型的代码都崩溃了。

需要给Array原型添加一个distinct的方法,最好检查是否存在同名的方法,避免自定义方法覆盖原生方法:

Arrray.prototype.distinct = Arrray.prototype.distinct || function(){/…../}
请指出 document load 和 document DOMContentLoaded 两个事件的区别。

ready 表示文档的 DOM 已经加载完成(不包含图片、视频等资源);load 表示整个网页加载完成。可以看出,ready 事件发生在 load 事件之前。

== 和 === 有什么不同?

如果两边的操作数具有一致的类型且拥有相同的值时,=== 返回 true,!== 返回 false。

请解释 JavaScript 的同源策略 (same-origin policy)。

同源策略限制了一个源(origin)中加载文本或脚本与来自其它源(origin)中资源的交互方式。

同源策略出于安全,不允许源 A 的脚本读取(read)源 B 的资源的内容,但却允许执行(execute)源 B 的资源。这个概念也有些拗口。简单说,有一个页面调用了 Google CDN 提供的 jQuery,以及其它 CDN 上的 Bootstrap JS、CSS 代码,虽然它们与我的博客不同源,但我可以用它们来操作这个页面,并应用样式,这是执行的概念。

如何实现下列代码:[1,2,3,4,5].duplicator(); // [1,2,3,4,5,1,2,3,4,5]

将此方法添加至 Array.prototype 实现,代码如下:

Array.prototype.duplicator = function(){
  var l = this.length,i;
      for(i=0;i
什么是三元表达式 (Ternary expression)?“三元 (Ternary)” 表示什么意思?

一个运算符如果有一个操作数,为一元运算符,两个为二元,三个为三元运算符,三元表达式则为一个三元运算表达式!

什么是 “use strict”; ? 使用它的好处和坏处分别是什么?

ECMAScript5中引入的严格模式,通过让JavaScript运行环境对一些开发过程中最常见和不易发现的错误做出和当前不同的处理,来让开发者拥有一个”更好”的JavaScript语言。

好处:

消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;

消除代码运行的一些不安全之处,保证代码运行的安全;

提高编译器效率,增加运行速度;

为未来新版本的Javascript做好铺垫。

好处具体体现:

去除WITH关键词

防止意外为全局变量赋值

函数中的THIS不再默认指向全局

防止重名

安全的 EVAL()

对只读属性修改时抛出异常

坏处:同样的代码,在“严格模式”中,可能会有不一样的运行结果;一些在“正常模式”下可以运行的语句,在“严格模式”下将不能运行

总结:启用JavaScript严格模式,它能帮你发现代码中未曾注意到的错误。不要在全局环境中启用,但你能尽量多的使用IIFE(立即执行函数表达式)来把严格模式作用到多个函数范围内。一开始,你会遇到之前未曾碰到过的错误提示,这是正常的。当启用严格模式后,请确保在支持的浏览器中做了测试,以发现新的潜在问题。一定不要仅仅在代码中添加一行”use strict”就假定余下的代码能正常工作。

请实现一个遍历至 100 的 for loop 循环,在能被 3 整除时输出 “fizz”,在能被 5 整除时输出 “buzz”,在能同时被 3 和 5 整除时输出 “fizzbuzz”。
for (var i = 1; i <= 30; i++) {

    if (i % 3 === 0) {
        if (i % 5 === 0) {
            alert("fizzbuzz" + i);
            continue;
        }
        alert("fizz" + i);
        continue;
    } else if (i % 5 === 0) {
        if (i % 3 === 0) {
            alert("fizzbuzz" + i);
            continue;
        }
        alert("buzz" + i);
        continue;
    }
}
为何通常会认为保留网站现有的全局作用域 (global scope) 不去改变它,是较好的选择?

它的意思是: 尽量少在全局作用域定义变量。

目的:减少名称冲突 利于模块化

为何你会使用 load 之类的事件 (event)?此事件有缺点吗?你是否知道其他替代品,以及为何使用它们?

要等到等页面完全加载后(所有图像、javascript文件、CSS等外部文件)。替代:把script标签放到最后面。

请解释什么是单页应用 (single page app), 以及如何使其对搜索引擎友好 (SEO-friendly)。

单页应用是一种特殊的web应用,它将所有的活动局限于一个web页面中,仅在该Web页面初始化时加载相应的HTML、JavaScript 和 CSS。

优点:

用户体验:对于内容的改动不需要加载整个页面

高效:服务器压力很小,消耗更少的带宽,能够与面向服务的架构更好地结合。

经典MVC开发模式,前后端各负其责。

一套Server API,多端使用(web、移动APP等)

重前端,业务逻辑全部在本地操作,数据都需要通过AJAX同步、提交

缺点:

不利于SEO:解决方案也有一些:H5pushState,通过浏览器历史记录让搜索引擎抓取;url中#!
复杂的单页架构页面,对Google来说抓取比较困难,于是给开发者制定一个规范:
1)、网站提交sitemap给Google;
2)、Google发现URL里有#!符号,例如example.com/#!/detail/1,于是Google开始抓取example.com/?_escaped_fragment_=/detail/1;_escaped_fragment_这个参数是Google指定的命名,如果开发者希望把网站内容提交给Google,就必须通过这个参数生成静态页面。

首屏渲染速度慢

使用 Promises 而非回调 (callbacks) 优缺点是什么?

优点:易读性改善

使用一种可以编译成 JavaScript 的语言来写 JavaScript 代码有哪些优缺点?

以Typescript为例子:
typescript是javascript的强类型版本,在编译期去掉类型和特有语法,生成纯粹的javascript代码。TypeScript 是 JavaScript 的超集,这意味着他支持所有的 JavaScript 语法。并在此之上对 JavaScript 添加了一些扩展,如 class / interface / module 等。这样会大大提升代码的可阅读性。

优点:

静态类型检查

IDE 智能提示 (编译阶段即可发现类型不匹配的错误)

代码重构

可读性

缺点:

不指定类型就写不了程序,类型只是辅助信息,并不是程序的本之后

灵活性问题

你使用哪些工具和技术来调试 JavaScript 代码?

alert

console.log

断点调试(这三种调试方式都是打断点)

js断点调试

source断点调试

Debugger断点(具体的说就是通过在代码中添加”debugger;”语句,当代码执行到该语句的时候就会自动断点。)

DOM断点调试

当节点内部子节点变化时断点

当节点属性发生变化时断点

当节点被移除时断点

请解释可变 (mutable) 和不变 (immutable) 对象的区别?

javascript中的原始值(undefined、null、布尔值、数字和字符串)与对象(包括数组和函数)有着根本区别。原始值是不可更改的:任何方法都无法更改(或“突变”)一个原始值。对数字和布尔值来说显然如此—-改变数字的值本身就说不通,而对字符串来说就不那么明显了,因为字符串看起来像由字符组成的数组,我们期望可以通过指定索引来假改字符串中的字符。实际上,javascript是禁止这样做的。字符串中所有的方法看上去返回了一个修改后的字符串,实际上返回的是一个新的字符串值。

区别:

可变性:对象和原始值不同,首先,它们是可变的–它们的值是可修改的

值的比较:对象的比较并非值的比较:即使两个对象包含同样的属性及相同的值,它们也是不相等的。各个索引元素相等的两个数组也不相等。

不变性 (immutability) 有哪些优缺点?

优点:
*因为不能修改一个不变对象的状态,所以可以避免由此引起的不必要的程序错误;一个不变的对象要比一个可变的对象更加容易维护。

因为没有任何一个线程能够修改不变对象的内部状态,一个不变对象自动就是线程安全的,这样可以省掉处理同步化的开销。一个不变对象可以自由地被不同的客户端共享。

缺点:

一旦需要修改一个不变对象的状态,就只好创建一个新的同类对象。在需要频繁修改不变对象的环境里,会有大量的不变对象作为中间结果被创建出来,这是一种资源上的浪费。

如何用你自己的代码来实现不变性 (immutability)?

可以使用const 修饰变量不可变

你会使用怎样的语言结构来遍历对象属性 (object properties) 和数组内容? 请解释同步 (synchronous) 和异步 (asynchronous) 函数的区别。

同步式:当计算机调度线程进行I/O操作命令后,由于文件的读写或者网络通信需要较长的操作时间,操作系统为了充分利用cpu,此时会暂停到当前的I/O线程对CPU的控制(故又称同步式为阻塞式I/O),把cup资源然给其他的线程资源,当I/O线程完成了操作时,此时操作系统会恢复此时的I/O线程,从而当前I/O线程重新获得了cup的的控制权,继续完成其他操作。

异步式:异步式IO又称非阻塞式I/O,异步式与同步式不同的是,当线程进行IO操作时,操作系统并不是暂停当前的线程操作,而是执行完I/O指令后,操作系统继续让当前线程执行下一条指令,当I/O操作完成后,会通过事件(event)通知I/O线程,而线程在接收到通知后,会处理响应事件。

什么是事件循环 (event loop)?

主线程从“任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。

请问调用栈 (call stack) 和任务队列 (task queue) 的区别是什么?

所谓“回调函数”(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当异步任务从“任务队列”回到执行栈,回调函数就会执行。

“任务队列”是一个先进先出的数据结构,排在前面的事件,优先返回主线程。主线程的读取过程基本上是自动的,只要执行栈一清空,“任务队列”上第一位的事件就自动返回主线程。但是,由于存在后文提到的“定时器”功能,主线程要检查一下执行时间,某些事件必须要在规定的时间返回主线程。

解释 function foo() {} 与 var foo = function() {} 用法的区别?

第一个未函数声明,第二个为函数定义表达式(函数定义表达式foo,变量声明提前单赋值并未提前)

下面的代码将输出什么到控制台,为什么?
(function(){ 
    var a = b = 3;
})(); 
console.log("a defined? " + (typeof a !== "undefined"));
console.log("b defined? " + (typeof b !== "undefined"));

输出: a defined? false b defined? true

封装JavaScript源文件的全部内容到一个函数块有什么意义及理由?

这是一个越来越普遍的做法,被许多流行的JavaScript库(jQuery,Node.js等)采用。这种技术创建了一个围绕文件全部内容的闭包,也许是最重要的是,创建了一个私有的命名空间,从而有助于避免不同JavaScript模块和库之间潜在的名称冲突。
这种技术的另一个特点是,允许一个易于引用的(假设更短的)别名用于全局变量。这通常用于,例如,jQuery插件中。jQuery允许你使用jQuery.noConflict(),来禁用 $ 引用到jQuery命名空间。在完成这项工作之后,你的代码仍然可以使用$ 利用这种闭包技术,如下所示:
(function($) { /* jQuery plugin code referencing $ */ } )(jQuery);

在JavaScript源文件的开头包含 use strict 有什么意义和好处?

use strict 是一种在JavaScript代码运行时自动实行更严格解析和错误处理的方法。那些被忽略或默默失败了的代码错误,会产生错误或抛出异常。通常而言,这是一个很好的做法。

严格模式的一些主要优点包括:

使调试更加容易。那些被忽略或默默失败了的代码错误,会产生错误或抛出异常,因此尽早提醒你代码中的问题,你才能更快地指引到它们的源代码。

防止意外的全局变量。如果没有严格模式,将值分配给一个未声明的变量会自动创建该名称的全局变量。这是JavaScript中最常见的错误之一。在严格模式下,这样做的话会抛出错误。

消除 this 强制。如果没有严格模式,引用null或未定义的值到 this 值会自动强制到全局变量。这可能会导致许多令人头痛的问题和让人恨不得拔自己头发的bug。在严格模式下,引用 null或未定义的 this 值会抛出错误。

不允许重复的属性名称或参数值。当检测到对象(例如,var object = {foo: "bar", foo: "baz"};)中重复命名的属性,或检测到函数中(例如,function foo(val1, val2, val1){})重复命名的参数时,严格模式会抛出错误,因此捕捉几乎可以肯定是代码中的bug可以避免浪费大量的跟踪时间。

使eval() 更安全。在严格模式和非严格模式下,eval() 的行为方式有所不同。最显而易见的是,在严格模式下,变量和声明在 eval() 语句内部的函数不会在包含范围内创建(它们会在非严格模式下的包含范围中被创建,这也是一个常见的问题源)。

在 delete使用无效时抛出错误。delete操作符(用于从对象中删除属性)不能用在对象不可配置的属性上。当试图删除一个不可配置的属性时,非严格代码将默默地失败,而严格模式将在这样的情况下抛出异常。

考虑以下两个函数。它们会返回相同的东西吗? 为什么相同或为什么不相同?
function foo1(){ return {
    bar: "hello"
};
}function foo2(){ return
{
    bar: "hello"
};
}

返回不相同: foo1 returns:Object {bar: "hello"}foo2 returns:undefined

分号在JavaScript中是一个可选项(尽管省略它们通常是非常糟糕的形式)。其结果就是,当碰到 foo2()中包含 return语句的代码行(代码行上没有其他任何代码),分号会立即自动插入到返回语句之后。也不会抛出错误,因为代码的其余部分是完全有效的,即使它没有得到调用或做任何事情(相当于它就是是一个未使用的代码块,定义了等同于字符串 "hello"的属性 bar)。

这种行为也支持放置左括号于JavaScript代码行的末尾,而不是新代码行开头的约定。正如这里所示,这不仅仅只是JavaScript中的一个风格偏好。

NaN 是什么?它的类型是什么?你如何可靠地测试一个值是否等于 NaN ?

NaN 属性代表一个“不是数字”的值。这个特殊的值是因为运算不能执行而导致的,不能执行的原因要么是因为其中的运算对象之一非数字(例如, "abc" / 4),要么是因为运算的结果非数字(例如,除数为零)。

NaN的特点:

NaN的类型为Number: console.log(typeof NaN === "Number"); // logs "true"

NaN 和任何东西比较——甚至是它自己本身!——结果是false:console.log(NaN === NaN); // logs "false"

测试数字为NaN的方法:

使用内置函数iSNaN(半可靠)

value !== value,如果值等于NaN,只会产生true

ES6提供: Number.isNaN() 比老的isNaN更可靠

下列代码将输出什么?并解释原因
console.log(0.1 + 0.2);console.log(0.1 + 0.2 == 0.3);

JavaScript中的数字和浮点精度的处理相同,因此,可能不会总是产生预期的结果。
以上所提供的例子就是一个演示了这个问题的典型例子。但出人意料的是,它会输出:
0.30000000000000004false

讨论写函数 isInteger(x) 的可能方法,用于确定x是否是整数

ECMAScript 6 之前没有提供类似 Number.isInteger 的方法。在ECMAScript规格说明中,整数只概念上存在:即,数字值总是存储为浮点值。
方法:

最简单又最干净的ECMAScript6之前的解决方法(同时也非常稳健地返回 false ,即使一个非数字的值,如字符串或 null ,被传递给函数)如:function isInteger(x) { return (x^0) === x; }

function isInteger(x) { return Math.round(x) === x; } (不如第一个优雅)

function isInteger(x) { return (typeof x === "number") && (x % 1 === 0);

function isInteger(x) { return parseInt(x, 10) === x; }

虽然这个以 parseInt函数为基础的方法在 x 取许多值时都能工作良好,但一旦 x 取值相当大的时候,就会无法正常工作。问题在于 parseInt() 在解析数字之前强制其第一个参数到字符串。因此,一旦数目变得足够大,它的字符串就会表达为指数形式(例如, 1e+21)。因此,parseInt() 函数就会去解析 1e+21,但当到达 e字符串的时候,就会停止解析,因此只会返回值 1。注意:

String(1000000000000000000000)"1e+21"> parseInt(1000000000000000000000, 10)1> parseInt(1000000000000000000000, 10) === 1000000000000000000000false
下列代码行1-4如何排序,使之能够在执行代码时输出到控制台? 为什么?
(function() { console.log(1);
    setTimeout(function(){console.log(2)}, 1000);
    setTimeout(function(){console.log(3)}, 0);
    console.log(4);
})();

序号如下:
1 4 3 2

比较明显而易见的那部分:
1 和 4之所以放在前面,是因为它们是通过简单调用 console.log() 而没有任何延迟输出的
2 之所以放在 3的后面,是因为 2 是延迟了1000毫秒(即,1秒)之后输出的,而 3 是延迟了0毫秒之后输出的。
好的。但是,既然 3 是0毫秒延迟之后输出的,那么是否意味着它是立即输出的呢?如果是的话,那么它是不是应该在 4 之前输出,既然 4 是在第二行输出的?
要回答这个问题,你需要正确理解JavaScript的事件和时间设置。
浏览器有一个事件循环,会检查事件队列和处理未完成的事件。例如,如果时间发生在后台(例如,脚本的 onload 事件)时,浏览器正忙(例如,处理一个 onclick),那么事件会添加到队列中。当onclick处理程序完成后,检查队列,然后处理该事件(例如,执行 onload 脚本)。
同样的, setTimeout() 也会把其引用的函数的执行放到事件队列中,如果浏览器正忙的话。
当setTimeout()的第二个参数为0的时候,它的意思是“尽快”执行指定的函数。具体而言,函数的执行会放置在事件队列的下一个计时器开始。但是请注意,这不是立即执行:函数不会被执行除非下一个计时器开始。这就是为什么在上述的例子中,调用 console.log(4) 发生在调用 console.log(3) 之前(因为调用 console.log(3) 是通过setTimeout被调用的,因此会稍微延迟)。

写一个简单的函数(少于80个字符),要求返回一个布尔值指明字符串是否为回文结构。

下面这个函数在 str 是回文结构的时候返回true,否则,返回false。

function isPalindrome(str) {
    str = str.replace(/W/g, "").toLowerCase(); return (str == str.split("").reverse().join(""));
}

例如:

console.log(isPalindrome("level")); // logs "true"console.log(isPalindrome("levels")); // logs "false"console.log(isPalindrome("A car, a man, a maraca")); // logs "true"
写一个 sum方法,在使用下面任一语法调用时,都可以正常工作
console.log(sum(2,3)); // Outputs 5
console.log(sum(2)(3)); // Outputs 5

(至少)有两种方法可以做到:

方法1:

function sum(x) { 
    if (arguments.length == 2) { 
            return arguments[0] + arguments[1];
        }else {
            return function(y) { 
            return x + y; 
        };
    }
}

方法2:

function sum(x, y) { 
    if (y !== undefined) { 
        return x + y;
    } else { 
        return function(y) { 
            return x + y; 
        };
    }
}
请看下面的代码片段
for (var i = 0; i < 5; i++) { 
    var btn = document.createElement("button");
    btn.appendChild(document.createTextNode("Button " + i));
    btn.addEventListener("click", function(){ console.log(i); }); 
    document.body.appendChild(btn);
}

(a)当用户点击“Button 4”的时候会输出什么到控制台,为什么?
无论用户点击什么按钮,数字5将总会输出到控制台。这是因为,当 onclick 方法被调用(对于任何按钮)的时候, for 循环已经结束,变量 i 已经获得了5的值。

(b)提供一个或多个备用的可按预期工作的实现方案?
要让代码工作的关键是,通过传递到一个新创建的函数对象,在每次传递通过 for 循环时,捕捉到 i 值。下面是三种可能实现的方法:

方法一

for (var i = 0; i < 5; i++) {

var btn = document.createElement("button");
btn.appendChild(document.createTextNode("Button " + i));
btn.addEventListener("click", (function(i) { 
    return function() { console.log(i); };
})(i)); 
document.body.appendChild(btn);

}

方法二: 封装全部调用到在新匿名函数中的 btn.addEventListener

for (var i = 0; i < 5; i++) {

var btn = document.createElement("button");
btn.appendChild(document.createTextNode("Button " + i));
(function (i) {
    btn.addEventListener("click", function() { console.log(i); });
})(i); 
document.body.appendChild(btn);

}

方法三: 调用数组对象的本地 forEach 方法来替代 for 循环

["a", "b", "c", "d", "e"].forEach(function (value, i) {

var btn = document.createElement("button");
btn.appendChild(document.createTextNode("Button " + i));
btn.addEventListener("click", function() { console.log(i); }); 
document.body.appendChild(btn);

});

下面的代码将输出什么到控制台,为什么?
var arr1 = "john".split("");
var arr2 = arr1.reverse();
var arr3 = "jones".split("");
arr2.push(arr3);
console.log("array 1: length=" + arr1.length + " last=" + arr1.slice(-1));
console.log("array 2: length=" + arr2.length + " last=" + arr2.slice(-1));        

输出结果是:

"array 1: length=5 last=j,o,n,e,s""array 2: length=5 last=j,o,n,e,s" 

调用数组对象的 reverse() 方法并不只返回反顺序的阵列,它也反转了数组本身的顺序(即,在这种情况下,指的是 arr1)。
reverse() 方法返回一个到数组本身的引用(在这种情况下即,arr1)。其结果为,arr2 仅仅是一个到 arr1的引用(而不是副本)。因此,当对 arr2做了任何事情(即当我们调用 arr2.push(arr3);)时,arr1 也会受到影响,因为 arr1 和 arr2 引用的是同一个对象。

注意:
传递数组到另一个数组的 push() 方法会让整个数组作为单个元素映射到数组的末端。其结果是,语句 arr2.push(arr3); 在其整体中添加 arr3 作为一个单一的元素到 arr2 的末端(也就是说,它并没有连接两个数组,连接数组是 concat() 方法的目的)。
和Python一样,JavaScript标榜数组方法调用中的负数下标,例如 slice() 可作为引用数组末尾元素的方法:例如,-1下标表示数组中的最后一个元素,等等。

下面的代码将输出什么到控制台,为什么?
console.log(1 + "2" + "2");
console.log(1 + +"2" + "2");
console.log(1 + -"1" + "2");
console.log(+"1" + "1" + "2");
console.log( "A" - "B" + "2");
console.log( "A" - "B" + 2);

上面的代码将输出以下内容到控制台:

"122""32""02""112""NaN2"NaN    

根本原因是,JavaScript(ECMAScript)是一种弱类型语言,它可对值进行自动类型转换,以适应正在执行的操作。让我们通过上面的例子来说明这是如何做到的。
例1:1 + "2" + "2" 输出:"122" 说明: 1 + "2" 是执行的第一个操作。由于其中一个运算对象("2")是字符串,JavaScript会假设它需要执行字符串连接,因此,会将 1 的类型转换为 "1", 1 + "2"结果就是 "12"。然后, "12" + "2" 就是 "122"。
例2: 1 + +"2" + "2" 输出: "32" 说明:根据运算的顺序,要执行的第一个运算是 +"2"(第一个 "2" 前面的额外 + 被视为一元运算符)。因此,JavaScript将 "2" 的类型转换为数字,然后应用一元 + 号(即,将其视为一个正数)。其结果是,接下来的运算就是 1 + 2 ,这当然是 3。然后我们需要在一个数字和一个字符串之间进行运算(即, 3 和 "2"),同样的,JavaScript会将数值类型转换为字符串,并执行字符串的连接,产生 "32"。
例3: 1 + -"1" + "2" 输出: "02" 说明:这里的解释和前一个例子相同,除了此处的一元运算符是 - 而不是 +。先是 "1" 变为 1,然后当应用 - 时又变为了 -1 ,然后将其与 1相加,结果为 0,再将其转换为字符串,连接最后的 "2" 运算对象,得到 "02"。
例4: +"1" + "1" + "2" 输出: "112" 说明:虽然第一个运算对象 "1"因为前缀的一元 + 运算符类型转换为数值,但又立即转换回字符串,当连接到第二个运算对象 "1" 的时候,然后又和最后的运算对象"2" 连接,产生了字符串 "112"。
例5: "A" - "B" + "2" 输出: "NaN2" 说明:由于运算符 - 不能被应用于字符串,并且 "A" 和 "B" 都不能转换成数值,因此,"A" - "B"的结果是 NaN,然后再和字符串 "2" 连接,得到 "NaN2" 。
例6: "A" - "B" + 2 输出: NaN 说明:参见前一个例子, "A" - "B" 结果为 NaN。但是,应用任何运算符到NaN与其他任何的数字运算对象,结果仍然是 NaN。

下面的递归代码在数组列表偏大的情况下会导致堆栈溢出。在保留递归模式的基础上,你怎么解决这个问题?
var list = readHugeList();
var nextListItem = function() { var item = list.pop(); 
    if (item) { 
        // process the list item...
        nextListItem();
    }
};

潜在的堆栈溢出可以通过修改nextListItem 函数避免:

var list = readHugeList();
var nextListItem = function() { 
    var item = list.pop(); 
    if (item) { // process the list item...
        setTimeout( nextListItem, 0);
    }
};    

堆栈溢出之所以会被消除,是因为事件循环操纵了递归,而不是调用堆栈。当 nextListItem 运行时,如果 item不为空,timeout函数(nextListItem)就会被推到事件队列,该函数退出,因此就清空调用堆栈。当事件队列运行其timeout事件,且进行到下一个 item 时,定时器被设置为再次调用 extListItem。因此,该方法从头到尾都没有直接的递归调用,所以无论迭代次数的多少,调用堆栈保持清空的状态。

JavaScript中的“闭包”是什么?请举一个例子

闭包是一个可以访问外部(封闭)函数作用域链中的变量的内部函数。
闭包可以访问三种范围中的变量:这三个范围具体为:

自己范围内的变量

封闭函数范围内的变量

全局变量。

下面是一个简单的例子:

var globalVar = "xyz";
(function outerFunc(outerArg) { 
    var outerVar = "a";
    (function innerFunc(innerArg) { 
        var innerVar = "b"; 
        console.log( "outerArg = " + outerArg + "
" 
        + "innerArg = " + innerArg + "
" 
        + "outerVar = " + outerVar + "
" 
        + "innerVar = " + innerVar + "
" 
        + "globalVar = " + globalVar);
    })(456);   
})(123);

在上面的例子中,来自于 innerFunc, outerFunc和全局命名空间的变量都在 innerFunc的范围内。因此,上面的代码将输出如下:

outerArg = 123innerArg = 456outerVar = ainnerVar = bglobalVar = xyz

下面的代码将输出什么
for (var i = 0; i < 5; i++) {
    setTimeout(function() { console.log(i); }, i * 1000 );
}

解释你的答案。闭包在这里能起什么作用?
上面的代码不会按预期显示值0,1,2,3,和4,而是会显示5,5,5,5,和5。
原因是在循环中执行的每个函数将整个循环完成之后被执行,因此,将会引用存储在 i中的最后一个值,那就是5。
闭包可以通过为每次迭代创建一个唯一的范围,存储范围内变量的每个唯一的值,来防止这个问题,如下:

for (var i = 0; i < 5; i++) {
    (function(x) {
        setTimeout(function() { console.log(x); }, x * 1000 );
    })(i);
}

这就会按预期输出0,1,2,3,和4到控制台。

以下代码行将输出什么到控制台?
console.log("0 || 1 = "+(0 || 1));
console.log("1 || 2 = "+(1 || 2));
console.log("0 && 1 = "+(0 && 1));
console.log("1 && 2 = "+(1 && 2));

该代码将输出:

0 || 1 = 11 || 2 = 10 && 1 = 01 && 2 = 2

在JavaScript中, || 和 &&都是逻辑运算符,用于在从左至右计算时,返回第一个可完全确定的“逻辑值”。
或( || )运算符。在形如 X||Y的表达式中,首先计算X 并将其解释执行为一个布尔值。如果这个布尔值true,那么返回true(1),不再计算 Y,因为“或”的条件已经满足。如果这个布尔值为false,那么我们仍然不能知道 X||Y是真是假,直到我们计算 Y,并且也把它解释执行为一个布尔值。

因此, 0 || 1 的计算结果为true(1),同理计算1 || 2。
与( &&)运算符。在形如 X&&Y的表达式中,首先计算 X并将其解释执行为一个布尔值。如果这个布尔值为 false,那么返回 false(0),不再计算 Y,因为“与”的条件已经失败。如果这个布尔值为true,但是,我们仍然不知道 X&&Y 是真是假,直到我们去计算 Y,并且也把它解释执行为一个布尔值。

不过,关于 &&运算符有趣的地方在于,当一个表达式计算为“true”的时候,那么就返回表达式本身。这很好,虽然它在逻辑表达式方面计算为“真”,但如果你希望的话也可用于返回该值。这就解释了为什么,有些令人奇怪的是, 1 && 2返回 2(而不是你以为的可能返回 true 或 1)。

执行下面的代码时将输出什么?请解释。
console.log(false == "0")
console.log(false === "0")

代码将输出:

true false

在JavaScript中,有两种等式运算符。三个等于运算符 === 的作用类似传统的等于运算符:如果两侧的表达式有着相同的类型和相同的值,那么计算结果为true。而双等于运算符,会只强制比较它们的值。因此,总体上而言,使用 ===而不是 ==的做法更好。 !==vs !=亦是同理。

以下代码将输出什么?并解释你的答案。
var a={},
b={key:"b"}, c={key:"c"};
a[b]=123;
a[c]=456;
console.log(a[b]);

这段代码将输出 456(而不是 123)

原因为:当设置对象属性时,JavaScript会暗中字符串化参数值。在这种情况下,由于 b 和 c都是对象,因此它们都将被转换为"[object Object]"。结果就是, a[b]和a[c]均相当于a["[object Object]"] ,并可以互换使用。因此,设置或引用 a[c]和设置或引用 a[b]完全相同。

以下代码行将输出什么到控制台?
console.log((function f(n){return ((n > 1) ? n * f(n-1) : n)})(10));

代码将输出10!的值(即10!或3628800)。
原因是:
命名函数 f()递归地调用本身,当调用 f(1)的时候,只简单地返回1。下面就是它的调用过程:
f(1): returns n, which is 1f(2): returns 2 f(1), which is 2f(3): returns 3 f(2), which is 6f(4): returns 4 f(3), which is 24f(5): returns 5 f(4), which is 120f(6): returns 6 f(5), which is 720f(7): returns 7 f(6), which is 5040f(8): returns 8 f(7), which is 40320f(9): returns 9 f(8), which is 362880f(10): returns 10 * f(9), which is 3628800

请看下面的代码段。控制台将输出什么,为什么?
(function(x) { 
    return (function(y) {         
            console.log(x);
        })(2)
})(1);

控制台将输出 1,即使从来没有在函数内部设置过x的值。原因是:
闭包是一个函数,连同在闭包创建的时候,其范围内的所有变量或函数一起。在JavaScript中,闭包是作为一个“内部函数”实施的:即,另一个函数主体内定义的函数。闭包的一个重要特征是,内部函数仍然有权访问外部函数的变量。
因此,在本例中,由于 x未在函数内部中定义,因此在外部函数范围中搜索定义的变量 x,且被发现具有1的值。

下面的代码将输出什么到控制台,为什么
var hero = {
    _name: "John Doe",
    getSecretIdentity: function (){ 
        return this._name;
    }
}; 
var stoleSecretIdentity = hero.getSecretIdentity;
console.log(stoleSecretIdentity());
console.log(hero.getSecretIdentity());

代码将输出:

undefinedJohn Doe

第一个 console.log之所以输出 undefined,是因为我们正在从 hero对象提取方法,所以调用了全局上下文中(即窗口对象)的 stoleSecretIdentity(),而在此全局上下文中, _name属性不存在。
其中一种修复stoleSecretIdentity() 函数的方法如下:
var stoleSecretIdentity = hero.getSecretIdentity.bind(hero);

创建一个给定页面上的一个DOM元素,就会去访问元素本身及其所有子元素(不只是它的直接子元素)的函数。对于每个被访问的元素,函数应该传递元素到提供的回调函数

此函数的参数为:
DOM元素
回调函数(将DOM元素作为其参数)
访问树(DOM)的所有元素是经典的深度优先搜索算法应用。下面是一个示范的解决方案:

function Traverse(p_element,p_callback) {
    p_callback(p_element); 
    var list = p_element.children; 
    for (var i = 0; i < list.length; i++) {
        Traverse(list[i],p_callback); // recursive call
    }
}























面试题收集

面试题一
面试题二

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94399.html

相关文章

  • 前端最强面经汇总

    摘要:获取的对象范围方法获取的是最终应用在元素上的所有属性对象即使没有代码,也会把默认的祖宗八代都显示出来而只能获取元素属性中的样式。因此对于一个光秃秃的元素,方法返回对象中属性值如果有就是据我测试不同环境结果可能有差异而就是。 花了很长时间整理的前端面试资源,喜欢请大家不要吝啬star~ 别只收藏,点个赞,点个star再走哈~ 持续更新中……,可以关注下github 项目地址 https:...

    wangjuntytl 评论0 收藏0
  • 前端开发面试链接

    摘要:手册网超级有用的前端基础技术面试问题收集前端面试题目及答案汇总史上最全前端面试题含答案常见前端面试题及答案经典面试题及答案精选总结前端面试过程中最容易出现的问题前端面试题整理腾讯前端面试经验前端基础面试题部分最新前端面试题攻略前端面试前端入 手册网:http://www.shouce.ren/post/index 超级有用的前端基础技术面试问题收集:http://www.codec...

    h9911 评论0 收藏0
  • 前端开发面试链接

    摘要:手册网超级有用的前端基础技术面试问题收集前端面试题目及答案汇总史上最全前端面试题含答案常见前端面试题及答案经典面试题及答案精选总结前端面试过程中最容易出现的问题前端面试题整理腾讯前端面试经验前端基础面试题部分最新前端面试题攻略前端面试前端入 手册网:http://www.shouce.ren/post/index 超级有用的前端基础技术面试问题收集:http://www.codec...

    snifes 评论0 收藏0
  • 深入理解js

    摘要:详解十大常用设计模式力荐深度好文深入理解大设计模式收集各种疑难杂症的问题集锦关于,工作和学习过程中遇到过许多问题,也解答过许多别人的问题。介绍了的内存管理。 延迟加载 (Lazyload) 三种实现方式 延迟加载也称为惰性加载,即在长网页中延迟加载图像。用户滚动到它们之前,视口外的图像不会加载。本文详细介绍了三种延迟加载的实现方式。 详解 Javascript十大常用设计模式 力荐~ ...

    caikeal 评论0 收藏0
  • 前端面试 - 收藏集 - 掘金

    摘要:一基础接口的意义百度规范扩展回调抽象类的意义我的前端面试经历百度前端掘金博主就读于电子科技大学,大三狗一枚面试是个漫长的过程,从海投到收获电话面试,一面二面三面,一个步骤出错那么后面就宣告终结。 一道常被人轻视的前端 JS 面试题 - 前端 - 掘金 目录前言第一问第二问变量声明提升函数表达式第三问第四问第五问第六问构造函数的返回值第七问最后前言 年前刚刚离职了,分享下我曾经出过的一道...

    lpjustdoit 评论0 收藏0
  • 前端面试总结js、html、小程序、React、ES6、Vue、算法、全栈热门视频资源)

    摘要:并总结经典面试题集各种算法和插件前端视频源码资源于一身的文档,优化项目,在浏览器端的层面上提升速度,帮助初中级前端工程师快速搭建项目。 本文是关注微信小程序的开发和面试问题,由基础到困难循序渐进,适合面试和开发小程序。并总结vue React html css js 经典面试题 集各种算法和插件、前端视频源码资源于一身的文档,优化项目,在浏览器端的层面上提升速度,帮助初中级前端工程师快...

    pumpkin9 评论0 收藏0

发表评论

0条评论

TesterHome

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<