JavaScript 篇
Last updated
Last updated
六种原始类型(基本类型):Undefined,Null,Boolean,Number,String,Symbol(ES6 新增)
引用类型:Object,Array,Date,RegExp,Function,基本包装类型(Boolean,Number,String 这是三种特殊的引用类型,也可以算作基本类型)
需要注意的是,typeof
的返回值(number, boolean, string, object, function, undefined, symbol)和 JS 的基本类型不一样。
基本类型保存在栈上。
引用类型的引用保存在栈上,其对象保存在堆中。
其实null
和undefined
从本质上讲,没什么太大的区别。如果硬要说区别的话,大概可以分为以下几点:
含义不同:null
表示该处不应该有值,而undefined
表示该处应该有一个值,但还没初始化。
转换数值不同:null = 0
,undefined = NaN
在面向对象语言中,继承分为两种:
接口继承:只继承方法签名
实现继承:继承实际的方法
因为 JavaScript 没有继承关键字,所以 JavaScript 实现继承的方法很特殊,大致有以下几种:
原型链继承(最常见)
原型链继承是一种非常传统的继承方式。如果从instanceof
关键字来看,实例既是父类的实例,也是子类的实例。但是这样的继承也有一个明显的问题:子类区别于父类的属性和方法需要在完成原型链继承之后再进行。同时,由于使用了 prototype
,无法实现多重继承。
补充一下:ES6 实现的extends
也是原型链继承,但是实现方式略有不同(可以参考下面的问题「ES5 与 ES6 的继承有什么区别?」)。
构造继承
这种继承解决了原型链继承的问题,但它也有一些问题:使用 instanceof
运算符的时候会发现,它的实例并不是父类的实例(因为父类没有在它的原型链上)。
实例继承
这种继承方法只能强行被归类为继承。因为它实际上是返回了一个父类的实例,与子类可以说毫无关系。同样,这种方法也不支持多继承。
拷贝继承
首先,只有可枚举的对象类型才能使用foreach
的方式获取到,其次,这种写法真的快绝迹了。它的优点是可以实现多继承,缺点是写起来真的难受,而且效率很低。
原型链是 JavaScript 实现继承最重要的一种方式。每一个对象都有自己的原型对象,根原型对象没有原型,所以其 __proto__
属性值为 null
。在调用时,如果访问的对象属性没有找到,JavaScript 会顺着原型链继续往下找,直到触碰到根原型为止。
这个问题我不太明白问什么,我猜测是在问 __proto__
和 prototype
的区别。
__proto__
是隐式原型,指向创建这个对象的函数(constructor)的 prototype
,用来构成原型链和实现基于原型的继承。
prototype
是显式原型,用来实现基于原型的继承与属性的共享。每一个函数都具有 prototype
属性。(因为函数在 ES5 中具备 constructor 的功能)
先上一张图:
区别显而易见:ES5 中子类继承的是父类的实例,ES6 中子类直接继承于父类。
实现 Ajax ,除了正常的生成和发送,主要是利用 onreadystatechange
回调函数对 Ajax 进行控制。无论是使用 XMLHttpRequest
还是 ActiveXObject('microsoft.xmlhttp')
,实现 Ajax 都要掌握它的五个关键状态,即 readyState
可能的五个值:
这是我给出的一个 DEMO:https://github.com/lcx960324/fe-interview-cheat-sheet-demo/tree/master/JavaScript/Ajax
什么是闭包,什么时候(需要)构成闭包?
闭包几乎是面试必考题了。闭包是指那些可以访问独立数据的函数,举个 MDN 的例子:
displayName
就是一个典型的闭包,它能够访问它之外的 name
变量,并保存这个变量在它创建时的状态。这个特性非常实用,因为 JavaScript 有很多奇怪的坑,比如 this context,比如作用域问题。举两个闭包的典型例子:
更多的内容,请参考:
通过闭包实现一个 throttle:http://www.alloyteam.com/2012/11/javascript-throttle/
一道经典的闭包面试题:https://zhuanlan.zhihu.com/p/25855075
如何禁止浏览器的默认事件?
event.preventDefault()
什么是事件冒泡?
事件冒泡指的是在某一节点上触发事件后,事件会沿着节点的父链逐级向上传递,直到根节点。
如何阻止事件冒泡?
event.stopPropagation()
readyState 来源于 XMLHttpRequest
,所以翻翻文档就好了:
至于第二个问题,我还真试了一下: 状态会停留在 3 一段时间,然后因为超时跳转到 4 。
由于闭包会携带包含它的函数作用域,所以它会比其他函数占用更多的内存,这可能会导致性能问题。所以在函数执行完后,应当尽量销毁闭包用不到的作用域中的变量,帮助系统回收内存。 同时,在 IE9 一下的浏览器中,如果闭包的作用域链中保存着一个 HTML 元素,那么就意味着该元素无法被销毁,这可能会造成内存泄漏。 还有,闭包可以访问到其外部的变量,所以要注意,如果将外部作用域当做一个对象来使用的话,如果闭包使用了内部变量,应当尽量避免修改闭包依赖的内部变量,因为这可能对闭包的行为造成影响。
送分题。最简单的:fibonacci 数列
至少要知道的两个方法和一个属性:event.preventDefault()
,event.stopPropagation()
,event.target
至于其他的...:https://developer.mozilla.org/en-US/docs/Web/API/Event
使用 call apply bind 都可以改变函数的 this 指向。
bind
返回一个新的函数。
call
和 apply
都会直接执行函数,不同的是,call
直接接收函数参数,apply
需要把参数变成数组一并传入。
这三个函数的具体用法,可以查一下文档,很简单~
https://segmentfault.com/a/1190000008637489
先解释一下什么是深拷贝,什么是浅拷贝:
浅拷贝:拷贝对象的引用。
深拷贝:拷贝对象的实例。
数组的浅拷贝其实蛮简单的,有很多方式。比如 Arrary.prototype.slice
。
深拷贝有黑科技:用 JSON.parse
和 JSON.stringify
超级好用!
以下是一个简单对象深拷贝的例子:
我能想到的办法:
Array.prototype.reduce
Array.prototype.join
Array.prototype.toString
:用逗号分隔,效果和上边的一样。
首先,介绍一个把字符串转换为数字的快捷方式(使用了 JavaScript 的自动类型转换):
跑题了!题目要求把字符串转换为整数:
ES6 将 parseInt
移植到了 Number
类上,当然,你一样可以在全局使用这个方法。
map
接收两个参数:
callback
:map
的回调函数,接收三个参数:
currentValue
:当前从数组取出的元素
index
:该元素在数组中的位置
array
:原始数组
thisArg
(可选):执行 callback
时的this
上下文。
reduce
接收两个参数:reduce
的回调函数,接收四个参数:
callback
:
accumulator
:上一个 reduce 函数的返回值(对于第一次执行,如果指定了 initialValue
,会是 initialValue
的值,如果没有,默认使用原始数组的第一个元素值)
currentValue
:当前元素
currentIndex
:该元素在数组中的位置
array
:原始数组
initialValue
(可选):初始值。
filter
:同map
,不过 callback
返回的是 true
或者 false
。
prototype
是原型,是 JS 实现面向对象特性的重要组成部分。在 ES6 之前,JavaScript 是没有类的概念的,实现类的方法通常是借助函数。每一个函数都是一个对象,且都具有 prototype
属性,这个属性定义了函数的原型。当使用该函数生成实例的时候,该实例会自动继承这个原型。JavaScript 具有原型链调用的特性,当我们访问某一个属性时,如果该属性在这个对象上找不到,会沿着这个对象的原型链一直寻找,直到找到或达到原型链底部为止。所以 prototype
是实现 JS 类与类的继承的重要元素。
说实话,事件冒泡我没想到什么兼容性问题。倒是事件有兼容性问题。事件的兼容性问题主要源自于 IE 与 Webkit 内核实现 Event Handler 的方式不同。红宝书的第 13 章提到了大量关于 IE 事件特殊性的地方。
首先,我们习惯使用的 addEventListener
和 removeEventListener
在 IE 中被实现为 attachEvent
和 detachEvent
。IE9 开始支持标准的 addEventListener
和 removeEventListener
,IE11 开始正式废弃了 attachEvent
和 detachEvent
。
同时 IE 实现阻止默认事件和阻止事件冒泡的方式也不一样。IE 的 event 对象有两个属性:cancelBubble
和 returnValue
,将这两个属性设置为 false
可分别组织事件冒泡和默认行为。
还有就是 IE8 之前的 Event
对象并没有提供 target
属性。
顺便提一下,不是所有的 DOM 事件都会冒泡,比如 blur
事件和 focus
事件就是默认不冒泡的。
这个问题我也不是很清楚,我唯一能想到的就是 XMLHttpRequest
和 fetch
的问题。XHR1
是从 IE7 开始提供支持的,IE10 开始基本完整的支持 XHR
。 fetch
使用了 Promise
,目前所有的 IE 版本都不支持这个函数。
同时提一下,IE8 通过 XDomainRequest
支持了 CORS,IE10 开始完全支持了 CORS。(CORS:Cross-Origin Resource Sharing)
以下是 XHR
兼容性的详细信息(注意那些 IE 没有实现的方法):
在老版本的 IE 中,可以使用 req = new ActiveXObject('Microsoft.XMLHTTP')
生成 Ajax 对象代替 XHR
。
当然,我们还需要一种 polyfill 彻底解决这个问题,欢迎扩充。
IE4 最先提供了获取视口坐标的函数:Element.getBoundingClientRect()
,随后,Webkit 也提供了支持。这个函数返回一个 DOMRect
对象,该对象的 x
,y
即是当前元素的左上角相对于视口左上角的坐标。
需要注意的是,IE8 及之前版本的 DOMRect
没有 height
和 width
属性。
详细信息可以参考文档:
getBoundingClientRect:https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect
DOMRect:https://developer.mozilla.org/zh-CN/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIDOMClientRect
offsetHeight
:元素在垂直方向上占用的空间大小,以像素计。(高度 + padding + 滚动条占用的高度 + 边框)
scrollHeight
:在没有滚动条的情况下,元素内容的总高度。(完全展开的高度 + padding)
clientHeight
:元素内容区高度加上上下内边距高度。(高度 + padding)
阮一峰老师的博客对严格模式给出了非常好的解读:http://www.ruanyifeng.com/blog/2013/01/javascript_strict_mode.html 我总结为以下几点:
全局变量要显式声明
不允许使用 with
为 eval
添加了独立的作用域
this === undefined
时不会默认指向全局对象
禁止在函数内部遍历调用栈(caller
和 arguments
)
无法删除变量(不能使用类似 delete x
这样的语法)
只有 configurable
设置为 true
的对象的属性,才能够被删除。
正常模式下不会报错的失败操作,在严格模式下会报错,比如对 getter
赋值,修改只读属性等。
对象不允许出现重名属性
函数不能出现重名参数
禁止八进制表示法
对 arguments
的限制:
不允许对 arguments
赋值
arguments
不再追踪参数变化,即在函数内修改参数值不会影响到 arguments
。
禁止使用 arguments.callee
函数声明必须在顶层
新增了 ES6 保留字(已无意义)
参考文档:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode
实现滚动懒加载,要利用 <img>
的一个特性:如果 <img>
没有 src
属性或者 src
属性为空,浏览器是不会加载这个图片的。所以我们实现懒加载的方法也很简单:<img>
的默认 src
为空,将真正的 src
保存在图片的 dataset
或自定义属性上。当 <img>
元素进入视图之后,我们再将图片的 src
设置为真实的 src
,促使图片加载。判断图片位置的方式,可以使用上边题目提到的 Element.getBoundingClientRect()
。
需要注意的是,出于性能考虑,当图片的加载被触发之后,应当移除图片的滚动事件监听器。
使用 throttle 技术,在一定的时间内只执行一次回调函数。如果连续触发,只有最后一次回调会执行。
首先要保证前端请求的资源大小在合理范围内,同时为了加快资源加载速度,可以使用 CDN 对依赖文件进行加载。
浏览器的并发量是有限的。前端要考虑的一个很重要的性能优化点,就是对待加载的资源进行排序,以保证最重要的资源能够最先进入加载序列。举个例子,头像等图片资源可以放在关键 JS 文件加载完后,再进行加载。
[待扩充]
参考文章:https://jinlong.github.io/2016/04/24/Debouncing-and-Throttling-Explained-Through-Examples/
debounce 用于解决某一个事件函数被频繁触发造成的性能问题(比如 scroll 事件函数会在 scroll 操作上不停触发),debounce 限制在某一个动作周期中该时间函数只在动作发生的开始 / 结尾执行一次。
leading: 在动作结束后执行 immediate: 在动作开始时执行
throttle 同样用于解决类似问题。它限制时间函数在特定时间周期内只执行一次,且一定会执行一次。
DOM 元素尺寸变化本身不会触发事件。只有 window
才有onresize
回调。不过,有两种方案可以解决这个问题:
MutationObserver
一个监测 DOM 尺寸变化的第三方库:https://github.com/wnr/element-resize-detector
答案:1,23,4
解释:Array 不能直接相加,所以要转换为最相近的可相加类型(String)再相加。Array to String 默认以逗号直接连接。
答案:如果 a 为假返回 a 的类型,如果 a 为真返回 b 的类型
解释:考察的其实是 a && b 表达式的返回值,与 typeof 关系不大。
在 ES5 中是不可以的。因为 ES5 子类继承的是父类的实例,也就是说子类的实例会共享父类的实例,这样会导致 Array 的 length
属性行为异常。
在 ES6 中是可以的。
(待扩充)
可以,看文档!
Array.prototype.splice
从第三个参数往后接收的所有参数都是要添加进当前数组的元素。
<a>
标签中有一个 <span>
标签,点击 <span>
触发了点击事件,target
是谁?target
是 <span>
。
先看看文档怎么说:是不是没看懂(或者懒得看...)!其实就是告诉 parseInt
你传入的数字字符串的是几进制。