前言
说真的,看了下自己的掘金注册日期,还是蛮早的,不过平时在这里没怎么写过技术文章,前几天刚辞职,裸辞的那种。这几天在面试,之前搭建过自己的技术网站,也在博客园和开源中国写过技术博客,但是最近对比了一下,感觉掘金的界面交互还是挺不错的,想着将自己平时的一些技术总结,好好回顾下。整理的面试题有自己平时总结的,也有看的一些其他人的文章,自己做的笔记。发表出来,希望大家共同学习,提高。如果内容有误,还请大家及时指出。谢谢、
JavaScript 的组成
- ECMAScript(核心):JavaScript 语言基础
- DOM(文档对象模型):规定了访问 HTML 和 XML 的接口
- BOM(浏览器对象模型):提供了浏览器窗口之间进行交互的对象和方法
JS 的基本数据类型和引用数据类型
- 基本数据类型:undefined、null、boolean、number、string、symbol
- 引用数据类型:object、array、function
介绍JS有哪些内置对象?
- 数据封装类对象:Object、Array、Boolean、Number、String;
- 其他对象:Function、Arguments、Math、Date、RegExp、Error;
- ES6 新增对象:Symbol、Map、Set、Promises、Proxy、Reflect;
说几条写 JavaScript 的基本规范?
- 代码缩进,建议使用“四个空格”缩进(一般使用Tab键缩进)
- 代码段使用花括号{}包裹
- 语句结束使用分号;
- 变量和函数在使用前进行声明
- 以大写字母开头命名构造函数,全大写命名常量
- 规范定义 JSON 对象,补全双引号
- 用{}和[]声明对象和数组
如何编写高性能的 JavaScript?
- 遵循严格模式:"use strict"
- 将 js 脚本放在页面底部,加快渲染页面
- 使用gulp或者webpack压缩代码,减少请求
- 使用非阻塞方式下载 js 脚本
- 尽量使用局部变量来保存全局变量
- 尽量减少使用闭包
- 使用 window 对象属性方法时,省略 window
- 尽量减少对象成员嵌套
- 缓存 DOM 节点的访问
- 通过避免使用 eval() 和 Function() 构造器
- 给 setTimeout() 和 setInterval() 传递函数而不是字符串作为参数
- 尽量使用直接量创建对象和数组
- 最小化重绘(repaint)和回流(reflow)
请说出offsetWidth/offsetHeight,clientWidth/clientHeight与scrollWidth/scrollHeight 的区别
offsetWidth/offsetHeight返回值包含content+padding+border,效果与Element.getBoundingClientRect()相同clientWidth/clientHeight返回值只包含content+padding,如果有滚动条,也不包含滚动条scrollWidth/scrollHeight返回值包含content+padding+ 溢出内容的尺寸。
描述浏览器的渲染过程,DOM 树和渲染树的区别?
浏览器的渲染过程:
- 解析 HTML 构建 DOM(DOM 树),并行请求 css/image/js
- CSS 文件下载完成,开始构建CSS 树
- CSS树构建结束后,和 DOM 一起生成 Render Tree(渲染树)
- 布局(Layout):计算出每个节点在屏幕中的位置
- 显示(Painting):通过显卡把页面画到屏幕上
DOM树和渲染树的区别:
- DOM 树与 HTML 标签一一对应,包括 head 和隐藏元素
- 渲染树不包括 head和隐藏元素,大段文本的每一个行都是独立节点,每一个节点都有对应的 css 属性。
重绘和回流(重排)的区别和关系?
- 重绘:当渲染树中的元素外观(如:颜色)发生改变,不影响布局时,产生重绘
- 回流:当渲染树中的元素的布局(如:尺寸、位置、隐藏/状态状态)发生改变时,产生重绘回流
- 注意:JS 获取Layout属性值(如:offsetLeft、scrollTop、getComputedStyle 等)也会引起回流。因为浏览器需要通过回流计算最新值
- 回流必将引起重绘,而重绘不一定会引起回流。
如何最小化重绘(repaint)和回流(reflow)?
- 需要对元素进行复杂的操作时,可以先隐藏(display:"none"),操作完成后再显示
- 需要创建多个 DOM 节点时,使用
DocumentFragment创建完后一次性的加入document - 缓存 Layout 属性值,如:var left = elem.offsetLeft; 这样,多次使用 left 只产生一次回流
- 尽量避免用 table 布局(table 元素一旦触发回流就会导致 table 里所有的其它元素回流)
- 避免使用 css 表达式(expression),因为每次调用都会重新计算值(包括加载页面)
- 尽量使用 css 属性简写,如:用 border 代替 border-width, border-style, border-color 批量修改元素样式:elem.className 和 elem.style.cssText 代替 elem.style.xxx
script 的位置是否会影响首屏显示时间?
- 在解析 HTML 生成 DOM 过程中,js 文件的下载是并行的,不需要 DOM 处理到 script 节点。因此,script 的位置不影响首屏显示的开始时间。
- 浏览器解析 HTML 是自上而下的线性过程,script 作为 HTML 的一部分同样遵循这个原则
- 因此,script 会延迟 DomContentLoad,只显示其上部分首屏内容,从而影响首屏显示的完成时间。
解释 JavaScript 中的作用域与变量声明提升?
JavaScript 作用域:
- 在 Java、C 等语言中,作用域为 for 语句、if 语句或{}内的一块区域,称为作用域;
- 而在 JavaScript 中,作用域为 function(){}内的区域,称为函数作用域。
JavaScript 变量声明提升:
- 在 JavaScript 中,函数声明与变量声明经常被 JavaScript 引擎隐式地提升到当前作用域的顶部。
- 声明语句中的赋值部分并不会被提升,只有名称被提升
- 函数声明的优先级高于变量,如果变量名跟函数名相同且未赋值,则函数声明会覆盖变量声明
- 如果函数有多个同名参数,那么最后一个参数(即使没有定义)会覆盖前面的同名参数。
JavaScript 有几种类型的值?
- 原始数据类型(Undefined,Null,Boolean,Number、String)-- 栈
- 引用数据类型(对象、数组和函数)-- 堆
- 两种类型的区别是:存储位置不同:
- 原始数据类型是直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据;
- 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能;
- 引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。
- 当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体
Javascript 作用链域?
- 全局函数无法查看局部函数的内部细节,但局部函数可以查看其上层的函数细节,直至全局细节。
- 如果当前作用域没有找到属性或方法,会向上层作用域查找,直至全局函数,这种形式就是作用域链。
谈谈 this 对象的理解!
- this一般情况总是指向函数的直接调用者
- 如果有 new 关键字,this 指向 new 出来的实例对象
- 在事件中,this 指向触发这个事件的对象
- IE 下 attachEvent 中的 this 总是指向全局对象 Window
eval 是做什么的?
eval的功能是把对应的字符串解析成 JS 代码并运行。
- 应该避免使用 eval,不安全,非常耗性能(先解析成 js 语句,再执行)
- 由 JSON 字符串转换为 JSON 对象的时候可以用 eval('('+ str +')');
什么是 Window 对象? 什么是 Document 对象?
- Window 对象表示当前浏览器的窗口,是 JavaScript 的顶级对象。
- 我们创建的所有对象、函数、变量都是 Window 对象的成员。
- Window 对象的方法和属性是在全局范围内有效的。
- Document 对象是 HTML 文档的根节点与所有其他节点(元素节点,文本节点,属性节点, 注释节点)
- Document 对象使我们可以通过脚本对 HTML 页面中的所有元素进行访问
- Document 对象是 Window 对象的一部分,可通过 window.document 属性对其进行访问
介绍事件“捕获”和“冒泡”执行顺序和事件的执行次数?
按照 W3C 标准的事件:首是进入捕获阶段,直到达到目标元素,再进入冒泡阶段
事件执行次数(DOM2-addEventListener):元素上绑定事件的个数
- 注意 1:前提是事件被确实触发
- 注意 2:事件绑定几次就算几个事件,即使类型和功能完全一样也不会“覆盖”
事件执行顺序:判断的关键是否目标元素
- 非目标元素:根据 W3C 的标准执行:捕获->目标元素->冒泡(不依据事件绑定顺序)
- 目标元素:依据事件绑定顺序:先绑定的事件先执行(不依据捕获冒泡标准)
- 最终顺序:父元素捕获->目标元素事件 1->目标元素事件 2->子元素捕获->子元素冒泡->父元素冒泡
- 注意:子元素事件执行前提 事件确实“落”到子元素布局区域上,而不是简单的具有嵌套关系
在一个 DOM 上同时绑定两个点击事件:一个用捕获,一个用冒泡。事件会执行几次,先执行冒泡还是捕获?
- 该 DOM 上的事件如果被触发,会执行两次(执行次数等于绑定次数)
- 如果该 DOM 是目标元素,则按事件绑定顺序执行,不区分冒泡/捕获
- 如果该 DOM 是处于事件流中的非目标元素,则先执行捕获,后执行冒泡
事件的代理/委托
事件委托是指将事件绑定目标元素的到父元素上,利用冒泡机制触发该事件。
优点:
- 可以减少事件注册,节省大量内存占用
- 可以将事件应用于动态添加的子元素上
缺点: 使用不当会造成事件在不应该触发时触发
IE 与火狐的事件机制有什么区别? 如何阻止冒泡?
IE 只事件冒泡,不支持事件捕获;火狐同时支持件冒泡和事件捕获。
阻止冒泡:
- 取消默认操作: w3c 的方法是 e.preventDefault(),IE 则是使用 e.returnValue = false;
- return false javascript 的 return false 只会阻止默认行为,而是用 jQuery 的话则既阻止默认行为又防止对象冒泡。
- 阻止冒泡 w3c 的方法是 e.stopPropagation(),IE 则是使用 e.cancelBubble = true。
什么是函数节流?介绍一下应用场景和原理?
- 函数节流(throttle)是指阻止一个函数在很短时间间隔内连续调用。 只有当上一次函数执行后达到规定的时间间隔,才能进行下一次调用。 但要保证一个累计最小调用间隔(否则拖拽类的节流都将无连续效果)
- 函数节流用于 onresize, onscroll 等短时间内会多次触发的事件
- 函数节流的原理:使用定时器做时间节流。 当触发一个事件时,先用 setTimout 让这个事件延迟一小段时间再执行。 如果在这个时间间隔内又触发了事件,就 clearTimeout 原来的定时器, 再 setTimeout 一个新的定时器重复以上流程。
函数节流简单实现:
function throttle(method, context) {
clearTimeout(methor.tId);
method.tId = setTimeout(function(){
method.call(context);
}, 100); // 两次调用至少间隔 100ms
}
// 调用
window.onresize = function(){
throttle(myFunc, window);
}
区分什么是“客户区坐标”、“页面坐标”、“屏幕坐标”?
- 客户区坐标:鼠标指针在可视区中的水平坐标(clientX)和垂直坐标(clientY)
- 页面坐标:鼠标指针在页面布局中的水平坐标(pageX)和垂直坐标(pageY)
- 屏幕坐标:设备物理屏幕的水平坐标(screenX)和垂直坐标(screenY)
new 操作符具体干了什么?
- 第一步创建一个空对象;
- 第二步将this指向空对象;
- 第三步动态给刚创建的对象添加成员属性;
- 第四步隐式返回this
JavaScript 实现异步编程的方法?
- 回调函数
- 事件监听
- 发布/订阅
- Promises 对象
- Async 函数[ES7]
什么是闭包(closure),为什么要用它?
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域
闭包的特性:
- 函数内再嵌套函数
- 内部函数可以引用外层的参数和变量
- 参数和变量不会被垃圾回收机制回收
function fn() {
var a = 100;
return function() {
return a;
}
}
var fn1 = fn();
fn1();
Ajax 是什么? 如何创建一个 Ajax?
ajax 的全称:Asyn Javascript And XML
异步传输+js+xml
所谓异步,在这里简单地解释就是:向服务器发送请求的时候,我们不必等待结果,而是可以同时做其他的事情,等到有了结果它自己会根据设定进行后续操作,与此同时,页面是不会发生整页刷新的,提高了用户体验
- 创建 XMLHttpRequest 对象,也就是创建一个异步调用对象
- 建一个新的 HTTP 请求,并指定该 HTTP 请求的方法、URL 及验证信息
- 设置响应 HTTP 请求状态变化的函数
- 发送 HTTP 请求
- 获取异步调用返回的数据
- 用 JavaScript 和 DOM 实现局部刷新
同步和异步的区别?
- 同步:浏览器访问服务器请求,用户看得到页面刷新,重新发请求,等请求完,页面刷新,新内容出现,用户看到新内容,进行下一步操作
- 异步:浏览器访问服务器请求,用户正常操作,浏览器后端进行请求。等请求完,页面不刷新,新内容也会出现,用户看到新内容
documen.write 和 innerHTML 的区别
- document.write 只能重绘整个页面
- innerHTML 可以重绘页面的一部分
DOM 操作——怎样添加、移除、移动、复制、创建和查找节点?
创建新节点
- createDocumentFragment() //创建一个 DOM 片段
- createElement() //创建一个具体的元素
- createTextNode() //创建一个文本节点
添加、移除、替换、插入
- appendChild()
- removeChild()
- replaceChild()
- insertBefore() //在已有的子节点前插入一个新的子节点
查找
- getElementsByTagName() //通过标签名称
- getElementsByName() // 通过元素的 Name 属性的值(IE 容错能力较强,会得到一个数组,其中包括 id 等于 name 值的) * getElementById() //通过元素 Id,唯一性
哪些操作会造成内存泄漏?
- 内存泄漏指任何对象在您不再拥有或需要它之后仍然存在
- 垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为 0(没有其他对象引用过该对象),或对该对象的惟一引用是循环的,那么该对象的内存即可回收
- setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏
- 闭包、控制台日志、循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)
渐进增强和优雅降级
- 渐进增强 :针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。
- 优雅降级 :一开始就构建完整的功能,然后再针对低版本浏览器进行兼容
请解释一下 JavaScript 的同源策略
- 概念:同源策略是客户端脚本(尤其是 Javascript)的重要的安全度量标准。它最早出自 Netscape Navigator2.0,其目的是防止某个文档或脚本从多个不同源装载。这里的同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议
- 指一段脚本只能读取来自同一来源的窗口和文档的属性
attribute 和 property 的区别是什么?
- attribute 是 dom 元素在文档中作为 html 标签拥有的属性;
- property 就是 dom 元素在 js 中作为对象拥有的属性。
- 对于 html 的标准属性来说,attribute 和 property 是同步的,是会自动更新的
- 但是对于自定义的属性来说,他们是不同步的
页面编码和被请求的资源编码如果不一致如何处理?
- 后端响应头设置 charset
- 前端页面设置 charset
把<script>放在</head> 中会有什么问题?
在浏览器渲染页面之前,它需要通过解析HTML标记然后构建DOM树。在这个过程中,如果解析器遇到了一个脚本(script),它就会停下来,并且执行这个脚本,然后才会继续解析HTML。如果遇到了一个引用外部资源的脚本(script),它就必须停下来等待这个脚本资源的下载,而这个行为会导致一个或者多个的网络往返,并且会延迟页面的首次渲染时间。
还有一点是需要我们注意的,那就是外部引入的脚本(script)会阻塞浏览器的并行下载,HTTP/1.1规范表明,浏览器在每个主机下并行下载的组件不超过两个(也就是说,浏览器一次只能够同时从同一个服务器加载两个脚本);如果你网站的图片是通过多个服务器提供的,那么按道理来说,你的网站可以一次并行下载多张图片。但是,当我们网站在加载脚本的时候;浏览器不会再启动任何其它的下载,即使这些组件来自不同的服务器。
在 javascript 中,1 与 Number(1)有什么区别 [易混淆]
var a = Number(1) // 1
var b = new Number(1) // Number {[[PrimitiveValue]]: 1}
typeof (a) // number
typeof (b) // object
a == b // true
- var a = 1 是一个常量,而 Number(1)是一个函数
- new Number(1)返回的是一个对象
- a==b 为 true 是因为所以在求值过程中,总是会强制转为原始数据类型而非对象,例如下面的代码:
typeof 123 // "number"
typeof new Number(123) // "object"
123 instanceof Number // false
(new Number(123)) instanceof Number // true
123 === new Number(123) // false
console.log(!!(new Boolean(false))输出什么 [易混淆]
答案为:true
布尔的包装对象 Boolean 的对象实例,对象只有在 null 与 undefined 时,才会认定为布尔的 false 值,布尔包装对象本身是个对象,对象->布尔 都是 true,所以 new Boolean(false)其实是布尔的 true,看下面这段代码:
if(new Boolean(false)){
alert('true!!');
}
只有使用了 valueOf 后才是真正的转换布尔值,与上面包装对象与原始资料转换说明的相同:
!!(new Boolean(false)) //true
(new Boolean(false)).valueOf() //false
为什么 JS 是单线程,而不是多线程 [常考]
- 单线程是指 JavaScript 在执行的时候,有且只有一个主线程来处理所有的任务。
- 目的是为了实现与浏览器交互。
- 我们设想一下,如果 JavaScript 是多线程的,现在我们在浏览器中同时操作一个 DOM,一个线程要求浏览器在这个 DOM 中添加节点,而另一个线程却要求浏览器删掉这个 DOM 节点,那这个时候浏览器就会很郁闷,他不知道应该以哪个线程为准。所以为了避免此类现象的发生,降低复杂度,JavaScript 选择只用一个主线程来执行代码,以此来保证程序执行的一致性。
typeof(typeof()) 和instanceof 的区别?
- typeof可以判断变量的数据类型, 返回值是字符串;
- a instanceof b是判断b是不是在a的原型链上, 也可以实现判断数据类型, 返回值为布尔。
怎么判断两个对象相等?
- 先判断俩者是不是对象;
- 再判断俩个对象的所有key值是否相等相同;
- 最后判断俩个对象的相应的key对应的值是否相同
js中函数有哪些定义方式
- 函数声明:function fn(){}
- 函数表达式:var fn=function(){}
- 构造函数:var fn=new Function(‘参数1’, ’参数2’, ’函数体’)
列举和数组操作相关的方法
- push: 将元素添加到数组的末尾, 返回值是数组新的长度
- pop: 将数组最后一个元素删除, 返回值是被删除的那个元素
- unshift: 在数组的开头插入一个元素, 返回值是新数组的长度
- shift: 将数组第一个元素删除, 返回值是被删除的元素
- splice(index, len): 删除数组中指定元素
- concat: 连接数组
- reverse: 翻转数组
列举和字符串操相关的方法
- substr(start, len)/substring(start, end): 截取字符串
- slice: 从数组会字符串中截取一段
- indexOf/lastIndexOf: 查找某一个字符是否存在于另外一个字符串中, 存在则返回索引, 不存在则返回-1;
- indexOf是从前向后顺序查找;
- lastIndexOf: 是从后向前查找
- replace: 替换字符串特定的字符
- toUpperCase: 将字符串转成大写
- toLowerCase: 将字符串转成小写
- charAt: 获取字符串中指定索引的字符
分别阐述split(), slice(), splice(), join()
- split可以使用一个字符串切割另外一个字符串, 返回值是数组;
- slice可以从数组中截取一部分(字符串对象也有slice方法);
- splice(index, len)可以删除指定的数组元素;
- join可以将数组元素使用特定的连接符拼接成字符串
什么是原型链?
每个构造函数都有一个prototype属性, 即原型对象, 通过实例对象的proto属性也可访问原型对象; 而原型对象本质也是一个对象, 是对象就有自己的原型对象, 最终形成的链状的结构称为原型链.
什么是构造函数?
构造函数本质也是一个函数, 只不过这个函数在定义的时候首字母一般需要大写; 构造函数调用的时候, 必须通过一个new关键字来调用; 我们一般不直接使用构造函数, 而是使用构造函数创建出来的实例对象. 构造函数是js面向对象的一个重要组成部分.
什么是预解析?
JS代码在执行之前, 解析引擎会对代码进行一个预先的检查, 主要会对变量和函数的声明进行提升, 将变量和函数的声明提到代码的最前面. 变量只提升声明, 不提升赋值.
call/apply/bind的区别
这三个方法都是函数这个特殊对象的方法,通过这三个方法都可以改变函数内部this的指向.
不同点:
- call和apply会调用一次函数, 而bind不会调用函数,只会在内存中创建一个函数的副本(修改过this指向的函数).
- call从第二个参数开始需要一个参数列表,
- apply第二个参数需要是一个数组
caller和callee的区别是什么?
-
函数fun.caller返回调用fun的函数对象,即fun的执行环境,如果fun的执行环境为window则返回null;
-
Callee是函数的arguments这个特殊对象的一个属性, 指向函数本身。
js 继承方式及其优缺点
原型链继承的缺点
- 一是字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数。
借用构造函数(类式继承)
- 借用构造函数虽然解决了刚才两种问题,但没有原型,则复用无从谈起。所以我们需要原型链+借用构造函数的模式,这种模式称为组合继承
组合式继承
- 组合式继承是比较常用的一种继承方法,其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又保证每个实例都有它自己的属性。
暂时就先总结整理这么些吧、后面再后续补充。