1 new 操作符具体做了什么、如何实现?
// 1.用new Object() 的方式新建了一个对象 obj
// 2. 取出第一个参数,就是我们要传入的构造函数。
// 此外因为 shift 会修改原数组,所以 arguments 会被去除第一个参数
// 3.将 obj 的原型指向构造函数,这样 obj 就可以访问到构造函数原型中的属性
// 4. 使用 apply,改变构造函数 this 的指向到新建的对象,这样 obj 就可以访问到构造函数中的属性
// 5.返回 obj
function objectFactory() {
let obj = new Object();
Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
let ret = Constructor.apply(obj, arguments);
return typeof ret === 'object' ? ret : obj;
}
// 执行
objectFactory(构造函数,初始化参数)
理解原型及原型链: cavszhouyou.top/JavaScript%…
模拟 new 的实现:github.com/mqyqingfeng…
2 js 延迟加载的几种方式
defer:将脚本文件设置为延迟加载,浏览器会再开启一个线程去下载 js 脚本,同时继续解析 html 文档,解析完毕后再去执行已经下载好的 js 脚本。async:这个属性会使脚本异步加载,不会阻碍页面的解析过程。但是脚本加载完后,会立即执行,如果此时页面解析还没有结束,同样会造成阻塞。多个 async 脚本的执行顺序是不可预测的,一般不会按照代码顺序执行。将 js 脚本放在最后加载:常见的优化 js 加载的方法动态创建 dom 元素:监听页面的加载状态,当文档加载完成后再创建 script 标签
// windown.DOMContenLoaded: DOM 解析完毕之后触发,这时 DOM 解析完成,js 可以获取到 DOM 的引用,但是页面中的一些图片等资源还没有加载完成。
// window.load: 所有资源全部加载完成,包括图片、视频等资源。
(function() {
if(window.attachEvent {
window.attachEvent('load', asyncLoad);
} else {
window.addEventListener('load', asyncLoad);
}
var asyncLoad = function(){
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = 'true';
ga.src = 'xxxx';
var s = document.getElementByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
}
)()
以下提供一个通用的事件监听方法
const EventUtils = {
// 添加监听
addEvent: function(ele, type, handler) {
if(ele.addEventListener) {
ele.addEventListener(type, handler, false);
} else if(ele.attachEvent) {
ele.attachEvent('on' + type, handler);
} else {
element['on' + type] = handler;
}
},
// 移除事件监听
removeEvent:function(ele, type, handler) {
if(ele.removeEventListener) {
ele.removeEventListener(type, handler);
} else if(ele.detachEvent) {
ele.detachEvent('on' + type, handler);
} else {
ele['on + type'] = null;
}
},
// 获取事件目标
getTarget:function(event){
return event.target || event.srcElement; // 兼容 IE
},
// 获取 event 对象的引用,取到事件的所有信息,确保随时能使用 event
getEvent: function(event){
return event || window.event;
},
// 阻止事件(主要是冒泡事件,因为 IE 不支持时间捕获
stopPropagation:function(event) {
if(event.stopPropagation()) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
},
// 取消事件的默认行为
preventDefault:function(event){
if(evnet.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
}
}
3 事件委托是什么
1 基本概念
通俗来说,就是把一个元素的响应事件(click,mousedown...)的函数委托到另一个元素;
一般来说,会把一个或者一组元素的事件委托到它的父级或者更外层元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素时,会通过事件冒泡机制触发外层绑定的事件,然后在外层元素上去执行函数。
2 优点
- 减少内存消耗:给每个 item 都绑定事件,是非常消耗内存的,影响性能
- 动态绑定事件:事件绑定在父级,子元素的增减不影响事件的绑定
4 什么是闭包
1 定义
闭包是指有权访问另一个函数作用域中的变量的函数。
2 创建方式
在一个函数 a 中创建另一个函数 b ,在 b 中可以访问到 a 中的局部变量。
3 用途
- 使函数外部能访问到函数内部的变量。通过使用闭包,可以在外部调用闭包函数,从而在外部访问到函数内部的变量,可以用这种方法来创建私有变量。
- 使已经结束运行的函数上下文中的变量继续留在内存中。因为闭包中还有对这些变量的引用,所有不会被回收。
4 例子
function createFunctions(){
var result = new Array();
for(var i=0; i < 10; i++){
result[i] = function(){
console.log(i);
}
}
return result;
}
var result = createFunctions();
result[0](); // 9
result[1](); // 9
result[2](); // 9
result[3](); // 9
result[4](); // 9
result[5](); // 9
以上并不符合我们的期待,原因是:
执行 console.log 时,首先 JavaScript 引擎回去寻找当前函数变量对象,当前函数变量对象找不到 i 值时,会根据作用域链向上查找,于是就在 createFunctions 中找到了 i,此时的 i 是叠加之后的 i,因此打印结果为 9。
需要注意的是:一般来说,createFunctions 函数执行完成后,其 createFunctionsAO 就应该销毁了,但是因为 resultContext 中还保留着对它的引用,那么在垃圾回收时,判断可以通过引用找到该对象,那么就不会被清除。
解决方案:
function createFunctions(){
var result = new Array();
for(var i=0; i < 10; i++){
result[i] = (function(num){
console.log(num);
})(i);
}
return result;
}
var result = createFunctions();
5 Ajax是什么
是一种异步通信的方式,由 js 脚本向服务器发起 http 通信,获取数据,然后更新当前网页的部分数据,实现局部刷新。
具体来说,Ajax 包括以下几个步骤:
- 创建 XMLHttpRequest 对象,即创建一个异步调用对象
- 创建一个 http 请求,并指定请求的方法、URL 以及验证信息
- 设置相应 http 请求的状态变化的函数
- 发送 http 请求
- 获取异步调用的返回结果数据
- 使用 JavaScript 和 dom 实现局部刷新
let xhr = new XMLHttpReuqest();
xhr.open(GET, 'server_url', true);
xhr.onreadystatechange = function(){
if(this.readyState != 4){
return;
}
if(this.status == 200) {
handle(this.response);
} else {
console.error(this.statusText);
}
}
xhr.onerror = function(){
console.error(this.statusText);
}
xhr.responseType = 'json';
xhr.sendRequestHeader('Accept', 'application/json');
xhr.send();
6 浏览器的缓存机制
1 定义
浏览器缓存机制指的是在一段时间内保存已接收到的 web 资源的一个副本,如果在资源的效时间内再次发送对该资源的请求,那么浏览器就会直接使用缓存的副本而不是向浏览器发送请求。
因此,使用 web 缓存可以极大的提高网页的打开速度、减少不必要的网络宽度消耗、降低服务器的压力、减少网络延迟。
2 分类
一般由服务器指定:
- 强缓存策略
- 协商缓存策略
2.1 强缓存策略
强缓存是利用 expires 或者 cache-control 这两个 http response header 实现的,它们都用来表示资源在客户端缓存的有效期。
- expires 是较老的强缓存管理 header,由于它是服务器返回的一个绝对时间。在服务器时间和客户端时间相差较大时,缓存管理容易出现问题。
- cache-control:是一个相对时间,在配置缓存时以秒为单位,用数值表示。
2.2 协商缓存策略
当浏览器对某个资源没有命中强缓存时,就会发送一个请求到服务器,验证协商缓存是否命中,如果命中,则响应体返回 http 的状态为304并且会带一个 Not Modified 的字符串。
7 什么是同源策略?以及跨域问题
1 定义
一个 js 脚本在未经允许的情况下,不可以访问另一个域中的内容。
同源指的是:两个域的协议、域名、端口号必须一致
2 目的
主要是为了保护用户信息的安全。它只是一种对 js 脚本的限制,对于一般的 img 或者 script 脚本请求都不会有跨域限制,因为它们通过响应结果来进行可能出现安全问题的操作。
3 限制
- 第一个是当前域下的 js 脚本不能访问其他域下的 cookie、localhost 和 indexDB
- 第二个是当前域下的 js 脚本不能操作其他域下的 DOM 元素
- 第三个是当前域下的 ajax 不能发送跨域请求
4 怎么解决跨域问题
按目的划分:
- 只是实现主域名下不同子域名的跨域操作
- document.domain + iframe:两个页面都通过 js 强制设置 document.domain 为基础主域
- 解决不同跨域窗口间的通信问题
- location.hash + iframe:在主页面动态的修改 iframe 窗口的 hash 值,然后在 iframe 窗口
- window.name + iframe
- postMessage
- 如解决 ajax 无法提交跨域请求的问题
- jsonp
// jsonp 的缺点是只能发送 get 请求
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 传一个回调函数名给后端
script.src= 'https://www.domain2.com:8080/login?user=admin&callbalck=handleCallbak';
document.appendChild(script);
function handleCallback(res) {
console.log(res)
}
</script>
- 跨域资源共享(CORS)
- 非简单请求,浏览器会发一次预检请求来判断该域名是否存在服务器的白名单中,收到肯定回复后才会发起请求
- ngnix 代理跨域
- nodejs 中间件代理跨域
- WebScoket 协议跨域 WebScoket protocol 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信、允许跨域通讯,是 server push 技术的一种很好的实现。Scoket.io 很好的封装了 WebScoket 接口,提供了更简单灵活的接口,对不支持 WebScoket 的浏览器提供了向下兼容。
参考:
segmentfault.com/a/119000001…
cors:www.ruanyifeng.com/blog/2016/0…
8 什么是 Cookie
1 定义
cookie 是服务器提供的一种用于维护会话状态信息的数据,通过服务器发送到浏览器,并保存在本地。当下一次有同源请求时,浏览器会自动的将 cookie 添加到请求头中发送给服务器。每个 cookie 的 maxSize 是4kb,每个域名下的 cookie 数量最多为20个。
2 用途
- 会话状态管理(用户登陆状态、购物车、游戏分数等)
- 个性化设置(自定义设置、主题等)
- 浏览器行为跟踪(跟踪分析用户行为等)
3 配置
服务器端:使用 Set-Cookie 的响应头部来配置 cookie 信息。
cookie 包括以下5个属性:
- expires:失效时间,GMT 格式
- domian:域名
- path:路径
- secure:规定了 cookie 只能在确保安全的情况下传输
- HttpOnly:规定了这个 cookie 只能被服务器访问,不能被 js 脚本访问
参考:
HTTP Cookie:developer.mozilla.org/zh-CN/docs/…
4 cookies, session 和 webStroage 的区别及应用场景
cookies 和 session 都是用来跟踪浏览器用户身份的会话方式。
区别:
- 保持状态:cookies 保存在客户端,session 保存在服务器端
- 存储类型:cookies 只能存储字符串类型,session 通过类似 HashTable 的数据结构存储
- 存储大小:单个 cookies 不超过 4kb,session 无大小限制
- 安全性: cookies 不如 session
而 webstroage 的目的:
- 提供一种在 cookies 之外的存储路径
- 提供一种存储大量可跨会话的存储机制
HTML5的WebStorage提供了loacalStroage 和 sessionStroage 两种API,区别:
- 生命周期:前者只有手动清除才会消失,而后者只有关闭标签页就会消失(刷新不会)
- 存储大小:都是 5 MB
- 存储内容类型:都只能存储字符串类型
- 获取方式: window.localStroage, window.sessionStroage
9 模块化开发
9.1 是模块化开发
一个模块实现一个特定的功能的一组方法。
由于函数具有独立作用域的特点,最原始的写法是使用函数作为模块,但这种方式容易造成全局变量污染,且模块间没有联系。
之后,提出了对象写法。将函数作为一个对象的方法来实现,这样解决了直接使用函数作为模块的一些缺点,但是这样会暴露所有的模块成员,外部代码可以修改内部属性的值。
现在,最常用的是立即执行函数的写法。通过闭包实现模块私有作用域的建立,同时不会对全局作用域造成污染。
9.2 js的几种模块规范
- CommonJS:通过
require来引入模块,通过module.exports定义模块的输出接口。这是服务端的解决方案,以同步的形式引入需要的模块,因为服务端的数据都存在磁盘,读取非常快,加载没有问题。但在浏览器端,加载数据需要发送网络请求,用这种方式就没那么合适了。 - AMD:采用异步加载的方式来加载模块,模块的加载不影响后面语句的执行,所有依赖这个模块的语句都定义在一个回调函数中,等模块加载完成,执行回调。
require.js实现了 AMD 规范。 - CMD:和 AMD 一样是异步加载,区别在于模块定义时对依赖的处理不同、对依赖模块的执行时机不同 区别:
- import/export:es6 提出
参考
浅谈模块化开发:juejin.im/post/684490…
10 document.write 和 innerHtml 的区别
前者:write 的内容会代替整个文档流的内容,重写整个页面 后者:仅代替指定元素的内容,重写页面部分内容
11 DOM 操作
11.1 创建新节点
- createDocumentFragment(node)
fragment是一个指向空DocumentFragment对象的引用。文档片段存在于内存中,并不在 DOM 树中,所以将子元素插入到文档片段时,不会引起页面的回流。
let fragment = document.createDocumentFragment()
- createElement(TagName[,options]):创建一个由标签名指定的 html 元素
- createTextNode(text):创建一个新的文本节点
11.2 添加、移除、替换、插入
- appendChild(node)
- removeChild(node)
- parentNode.replaceChild(new, old)
- parentNode .insertBefore(new:新节点, refrenceNode:将要插在这个结点之前)
11.3 查找
- getElementById()
- getElementByName()
- getElementByTagName()
- getElementByClassName()
- quetySelector()
- querySelectorAll()
11.4 属性操作
- getAttribute(key)
- setAttribute(key, value)
- hasAttribute(key)
- removeAttribute(key)
12 call() 和 apply()
12.1 区别
作用一模一样,仅传入的参数形式不同
apply(thisArg, [argsArray])
call(thisArg, arg1, arg2,...)
13 类数组
13.1 什么是类数组
一个拥有length属性和若干索引属性的对象,就可以被称为类数组对象。和数组相似,但是不能调用数组的方法。
13.2 常见的类数组
- arguments 方法的返回结果
- DOM 方法的返回结果
13.3 常用类数组转数组的方法
(1)通过 call 调用数组的 slice 方法来实现
Array.prototype.slice.call(arrayLike)
(2) 通过 call 调用数组的 splice 方法来实现转换
Array.prototype.splice.call(arrayLike, 0)
(3) 通过 apply 调用数组的 concat 方法来实现转换
Array.prototype.concat.apply([], arrayLike)
(4) 通过 Array.from 方法来实现转换
// 从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例
Array.from(arrayLike)
14 Javascript 作用域与变量声明提升
todo
15 垃圾回收
15.1 定义
有些数据使用后,可能不再需要,这种数据称为垃圾数据。 对垃圾数据进行回收,以释放有限的内存空间,称为垃圾回收。
15.2 类别
手动回收:何时分配、何时销毁内存都是由代码控制(c、c++)
自动回收:产生的垃圾是由垃圾回收器来释放,并不需要手动通过代码释放
15.3 调用栈中的数据如何回收
有一个记录当前执行状态的指针(称为ESP),指向调用栈中当前正在执行代码的上下文,执行完成后,JavaScript会将ESP下移到另一个执行上下文,这个下移的过程就是销毁只想上下文的过程。
15.4 堆中的数据如何回收
回收堆中数据需要用到JavaScript的垃圾回收器。
代际假说:两个特点
- 大部分新生对象倾向于早死;
- 不死对象,会活得更久。
15.4.1 以chrome中的v8引擎为例分析:
v8 中会把堆分为:
- 新生代:存放生存时间短的对象(1~8M) —— 使用副垃圾回收器
- 老生代:存放生存时间长的对象 —— 使用主垃圾回收器
垃圾回收器的流程:
- 标记空间中的活动对象和非活动对象
- 回收非活动对象所占据的内存
- 内存整理(频繁回收后存在的大量不连续空间,称为内存碎片)
副垃圾回收器
用Scavenge算法:将新生代空间对半分为两个区域,一半是对象空间,一半是空闲区域。首先对对象区域中的垃圾做标记,副垃圾回收器会将存活的对象复制到空闲区域,并进行有序排列。完成后,进行对象区域和空闲区域角色翻转,这样就完成了垃圾对象的回收操作。
对象晋升策略:经过两次垃圾回收依然还存活的对象,会被移到老生区。
主垃圾回收器:
老生区中的对象:占用空间大、对象存活时间长
采用 标记-清除 算法:
- 标记过程阶段:从一组根元素开始,递归遍历根元素,可到达为活动对象,否则为垃圾数据
- 垃圾清除:清除垃圾数据,然后使用 标记-整理算法,让所有存活对象向一端移动,然乎直接清理掉端边界以外的内存
15.5 全停顿
定义:JavaScript运行在主线程上,一旦执行垃圾回收算法会将其他正在执行的js脚本暂停,待垃圾回收完毕再恢复。这种行为,称为全停顿。
此时,使用增量标记法,V8将标记过程分为一个个的子标记过程,同时让垃圾回收标记和JavaScript应用逻辑交替进行,直到标记阶段完成。由此,可将完成的垃圾回收任务分为许多小的任务,在其他js任务中间穿插执行,避免用户感受到页面卡顿。
16 移动端点击事件延迟、点击穿透
16.1 延迟
原因: 因为移动端有双击缩放的操作,因此浏览器在 click 后会等待 300ms,看用户有没有下一次点击,来判断这次操作是不是双击。
解决方案:
- 通过 meta 标签禁用网页缩放
<meta name="viewport" content="user-scalable=no">
<meta name="viewport" content="initial-scale=1,maximum-scale=1">
- 通过 meta 标签将网页的 viewport 设置为 ideal viewpoint
- 调用一些 js 库,如 FastClick
16.2 穿透
假如页面上有两个元素A和B。B元素在A元素之上。我们在B元素的touchstart事件上注册了一个回调函数,该回调函数的作用是隐藏B元素。我们发现,当我们点击B元素,B元素被隐藏了,随后,A元素触发了click事件。
这是因为在移动端浏览器,事件执行的顺序是touchstart > touchend > click。而click事件有300ms的延迟,当touchstart事件把B元素隐藏之后,隔了300ms,浏览器触发了click事件,但是此时B元素不见了,所以该事件被派发到了A元素身上。如果A元素是一个链接,那此时页面就会意外地跳转。
17 前端路由
17.1 定义
前端路由就是把不同路由对应不同的内容或页面交给前端来做,之前是后端根据不同的请求返回不同的页面内容。
17.2 何时使用
在单页应用,大部分页面结构不变,只改变部分内容
17.3 优缺点
优点:用户体验好,不需要每次都从服务器端请求页面数据,能快速展示
缺点:单页面无法在前进/后退时记住之前滚动的位置
17.4 实现方式
- hash
- pushState
18 事件循环
宏任务:
- 页面进程中存在消息队列和事件循环机制,渲染进程内部会维护多个消息队列,如延迟执行队列、普通的消息队列
- 宏任务:在消息队列中的任务称为宏任务(setTimeout)
微任务:
- 需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前
- 产生微任务的方式:
- MutationObserver:监控dom节点,当dom节点发生变化时,就会产生 DOM 变化记录的微任务
- Promise.resolve() Promise.reject()
- 在执行微任务过程中生成的新的微任务,也会加到微任务队列中,V8 引擎会一直循环执行,直到队列为空。也就是,新产生的微任务不会推迟到下个宏任务中执行,而是在当前宏任务中继续。
总结: 宏任务在任务队列中依次执行,每执行完成一个宏任务的主函数且宏任务未结束之前,执行该宏任务的微任务队列,微任务产生的新的微任务,继续在当前宏任务中执行,直到所有的微任务都结束,然后再执行下一个宏任务。