1. JavaScript如何实现继承
1.1 原型链继承
原型链继承是比较常见的继承方式之一,其中涉及的构造函数、原型和实例,三者之间存在着一定的关系,即每一个构造函数都有一个原型对象,原型对象又包含一个指向构造函数的指针,而实例则包含一个原型对象的指针。
1 2 3 4 5 6 7 8 9
| function Parent() { this.name = 'parent1'; this.play = [1, 2, 3] } function Child() { this.type = 'child2'; } Child.prototype = new Parent(); console.log(new Child())
|
1.2 盗用构造函数继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function Parent(){ this.name = 'parent'; }
Parent.prototype.getName = function () { return this.name; }
function Child(){ Parent.call(this); this.type = 'child' }
let child = new Child(); console.log(child); console.log(child.getName());
|
可以看到,父类原型对象中一旦存在父类之前自己定义的方法,那么子类将无法继承这些方法
相比第一种原型链继承方式,父类的引用属性不会被共享,优化了第一种继承方式的弊端,但是只能继承父类的实例属性和方法,不能继承原型属性或者方法。
1.3 组合继承
组合继承则将前两种方式继承起来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function Parent () { this.name = 'parent'; this.play = [1, 2, 3]; }
Parent.prototype.getName = function () { return this.name; } function Child() { Parent.call(this); this.type = 'child'; }
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var s1 = new Child(); var s2 = new Child(); s1.play.push(4); console.log(s1.play, s2.play); console.log(s1.getName()); console.log(s2.getName());
|
1.4 原型式继承
这里主要借助Object.create
方法实现普通对象的继承。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| let parent = { name: "parent", friends: ["p1", "p2", "p3"], getName: function() { return this.name; } };
let person1 = Object.create(parent); person1.name = "tom"; person1.friends.push("jerry");
let person2 = Object.create(parent); person2.friends.push("lucy");
console.log(person1.name); console.log(person1.name === person1.getName()); console.log(person2.name); console.log(person1.friends); console.log(person2.friends);
|
这种继承方式的缺点也很明显,因为Object.create
方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能。
1.5 寄生式继承
寄生式继承在上面继承基础上进行优化,利用这个浅拷贝的能力再进行增强,添加一些方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| let parent = { name: "parent", friends: ["p1", "p2", "p3"], getName: function() { return this.name; } };
function clone(original) { let clone = Object.create(original); clone.getFriends = function() { return this.friends; }; return clone; }
let person = clone(parent);
console.log(person.getName()); console.log(person.getFriends());
|
其优缺点也很明显,跟上面讲的原型式继承一样。
1.6 寄生式组合继承
寄生组合式继承,借助解决普通对象的继承问题的Object.create
方法,在前面几种继承方式的优缺点基础上进行改造,这也是所有继承方式里面相对最优的继承方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| function clone (parent, child) { child.prototype = Object.create(parent.prototype); child.prototype.constructor = child; }
function Parent() { this.name = 'parent'; this.play = [1, 2, 3]; } Parent.prototype.getName = function () { return this.name; } function Child() { Parent.call(this); this.friends = 'child'; }
clone(Parent, Child);
Child.prototype.getFriends = function () { return this.friends; }
let person = new Child(); console.log(person); console.log(person.getName()); console.log(person.getFriends());
|
2. object.assign和扩展运算符的区别
- 两者都是浅拷贝,确切地说是对于对象实例的拷贝属于浅拷贝
- 对象合并,数组合并,Object.assign、connat的性能会比展开运算符“…”的性能高
- Object.assign会触发Proxy/Object.definedProperty的set方法,展开运算符“…”不会触发
- 合并对象、数组的时候,展开运算符放在前面的性能比放在后面的性能高
3. Web Storage API
3.1 sessionStorage
sessionStorage为每一个给定的源(origin)维持一个独立的存储区域,该存储区域在页面会话期间可用(即只要浏览器处于打开状态,包括页面重新加载和恢复)。
- 仅为会话存储数据,这意味着数据将一直存储到浏览器(或选项卡)关闭。
- 数据永远不会被传输到服务器。
- 存储限额大于 cookie(最大 5MB)。
3.2 localStorage
localStorage做同样的事情,但即使浏览器关闭并重新打开也仍然存在。
- 存储的数据没有过期日期,只能通过 JavaScript、清除浏览器缓存或本地存储的数据来清除。
- 存储限额是两者之间的最大值。
4. WebSocket
- WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
- WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
- 现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
- HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>菜鸟教程(runoob.com)</title> <script type="text/javascript"> function WebSocketTest() { if ("WebSocket" in window) { alert("您的浏览器支持 WebSocket!"); var ws = new WebSocket("ws://localhost:9998/echo"); ws.onopen = function() { ws.send("发送数据"); alert("数据发送中..."); }; ws.onmessage = function (evt) { var received_msg = evt.data; alert("数据已接收..."); }; ws.onclose = function() { alert("连接已关闭..."); }; } else { alert("您的浏览器不支持 WebSocket!"); } } </script> </head> <body> <div id="sse"> <a href="javascript:WebSocketTest()">运行 WebSocket</a> </div> </body> </html>
|
5. 前端本地存储的4种方法
5.1 cookie
- 用于浏览器和server的通讯
- 可设置失效时间,默认是浏览器关闭后失效
- 存放数据大小为4K左右
- 每次都会携带在HTTP头中,如果使用Cookie保存过多数据会带来性能的问题
- cookie中只能保管ASCII字符串,并需要通过编码方式存储为Unicode字符或者二进制数据
5.2 localStorage
- 除非被清除,否则永久保存
- 存放数据大小一般为5MB
- 仅在客户端(即浏览器)中保存,不参与和服务器的通信
- 浏览器可以设置是否可以访问数据,如果设置不允许会访问失败
- 兼容IE8以上浏览器
- 只能存储字符串类型,需要转成字符串存储
5.3 sessionStorage
- 仅在当前会话下有效,关闭tab页面或浏览器后被清除
- 存放数据大小一般为5MB
- 仅在客户端(即浏览器)中保存,不参与和服务器的通信
5.4 indexDB
MDN官网:
IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。该 API 使用索引实现对数据的高性能搜索。虽然 Web Storage 在存储较少量的数据很有用,但对于存储更大量的结构化数据来说力不从心。而 IndexedDB 提供了这种场景的解决方案。
IndexedDB主要用来客户端存储大量数据而生的,我们都知道cookie、localstorage等存储方式都有存储大小限制。如果数据量很大,且都需要客户端存储时,那么就可以使用IndexedDB数据库。

使用场景:
- 数据可视化等界面,大量数据,每次请求会消耗很大性能。
- 即时聊天工具,大量消息需要存在本地。
- 其它存储方式容量不满足时,不得已使用IndexedDB
6. 函数柯里化
函数柯里化指的是一种将使用多个参数的一个函数,转换成一系列使用一个参数的函数的技术。
对于已经柯里化后的 _fn 函数来说,
- 当接收的参数数量与原函数的形参数量相同时,执行原函数;
- 当接收的参数数量小于原函数的形参数量时,返回一个函数用于接收剩余的参数,直至接收的参数数量与形参数量一致,执行原函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| function curry(fn, args) { let length = fn.length;
args = args || [];
return function() { let subArgs = [...args, ... arguments]; if (subArgs.length >= length) { return fn.apply(this, subArgs); } else { return curry.call(this, fn, subArgs); } } }
function multiFn(a, b, c) { return a * b * c; }
let multi = curry(multiFn); console.log(multi(2)(3)(4)); console.log(multi(2, 3, 4)); console.log(multi(2)(3, 4)); console.log(multi(2, 3)(4));
|
1 2 3 4
| function curry(fn, ...args) { return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args); }
|
7. 利用 Promise.race 和 Promise.all 解决网络过快 loading 闪烁问题
终极解决方式是将 Promise.all() 和 Promise.race() 搭配使用。先利用Promise.race()约束请求在超时时间内返回时就直接渲染,否则就固定展示一段时间的loading动画再渲染数据。即请求如果没有在 500ms 内返回则固定展示 1500ms 的loading。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| function resolvePromise(time) { return new Promise((resolve) => { setTimeout(() => { resolve(`在${time}ms后返回成功Promise`); }, time) }); }
function rejectPromise(time) { return new Promise((resolve, reject) => { setTimeout(() => { reject(new Error(`在${time}ms后返回失败Promise`)); }, time) }); }
function reqData() { const axiosRequest = getData(); Promise.race([axiosRequest, rejectPromise(500)]).then((res) => { }).catch((err) => { loading.value = true; console.log(err.message); Promise.all([axiosRequest, resolvePromise(1500)]).then((res) => { console.log(res[0]); }).catch((err) => { console.log(err); }).finally(() => { lodaing.value = false; }) }) }
|
8. 链式调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class N { constructor(value) { this.value = value || 0; } add(num) { this.value += num; return this; } minus(num) { this.value -= num; return this; } get() { console.log(this.value); return this.value; } }
const n = new N(2); n.add(198).minus(100).get();
|
9. 手写Promise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| const PENDING = 'pending'; const FULLFILLED = 'fullfilled'; const REJECTED = 'rejected';
function MyPromise(fn) { this.state = PENDING; this.value = null; const that = this; this.resolvedCallbacks = []; this.rejectedCallabcks = [];
function resolve(val) { if (that.state === PENDING) { that.state = FULLFILLED; that.value = val; that.resolvedCallbacks.map((cb) => { cb(that.value); }) } }
function reject(val) { if (that.state === PENDING) { that.state = REJECTED; that.value = val; that.rejectedCallabcks.map((cb) => { cb(that.value); }) } }
try { fn(resolve, reject); } catch (err) { reject(err); } }
MyPromise.prototype.then = function (onFullfilled, onRejected) { const self = this; return new MyPromise((resolve, reject) => { let fullfilled = () => { try { const result = onFullfilled(self.value); return result instanceof MyPromise ? result.then(resolve, reject) : resolve(result); } catch (err) { reject(err); } } let rejected = () => { try { const result = onReject(self.reason); return result instanceof MyPromise ? result.then(resolve, reject) : reject(result); } catch (err) { reject(err) } } switch (self.state) { case PENDING: self.resolvedCallbacks.push(fullfilled); self.rejectedCallabcks.push(rejected); break; case FULLFILLED: fullfilled(); break; case REJECTED: rejectted(); break; } }) }
let p = new MyPromise((resolve, reject) => { resolve('OK'); }) p.then((res) => { console.log(res, 'OK'); }, (err) => { console.log(err, 'fail'); })
|
10. 执行上下文的创建
创建执行上下文有明确的几个步骤:
- 确定this,即我们所熟知的this绑定。
- 创建词法环境组件。
- 创建变量环境组件。
10.1 确定this
在全局执行上下文中,this 总是指向全局对象。例如:浏览器环境下 this 指向 window 对象。
在函数执行上下文中,this 的值取决于函数的调用方式,如果被一个对象调用,那么 this 指向这个对象。否则(在浏览器中) this 一般指向全局对象 window 或者 undefined (严格模式)。
10.2 创建词法环境组件
词法环境是一个包含标识符变量映射的结构,这里的标识符表示变量(函数)的名称,变量是对实际对象(包括函数类型对象)或原始值的引用。
如:var name = 1;。标识符是 name,引用是 1。
词法环境由环境记录器与对外部环境的引用两个组件组成:
- 环境记录器用于存储当前环境中的变量和函数声明的实际位置。
- 外部环境的引用对应着可以访问的其它外部环境。(所以子作用域可以访问父作用域)
10.3 创建变量环境
变量环境与词法环境十分相似。在 ES6 中,词法环境和变量环境的明显不同就是前者被用来存储函数声明和变量(let/const)的绑定,而后者只用来存储 var 变量的绑定。
11. bind函数与call和apply的区别
11.1 bind
bind方法和call很相似,第一个参数也是this指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)。bind返回的是一个永久改this指向的函数。
11.2 区别
共同点:
- 三者都可以改变函数的this对象指向。
- 三者第一个参数都是this要指向的对象,如果没有这个参数或参数为undefined或null,则默认指向全局window。
区别:
- 三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入。
- bind是返回绑定this之后的函数,apply和call是立即执行。
12. new的实现原理
new 方法主要分为四个步骤:
- 创建一个对象
- 将构造函数中的this指向该对象
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function _new(obj, ...rest){ const newObj = Object.create(obj.prototype);
const result = obj.apply(newObj, rest);
return typeof result === 'object' ? result : newObj; }
function obj(name, age) { this.name = name; this.age = age; } console.log(_new(obj, "zwx", 18));
|
13. JavaScript脚本延迟加载的方式与区别
13.1 默认<script>
让我们首先定义<script>
没有任何属性的情况。HTML 文件将被解析,直到脚本文件被命中,此时解析将停止,并且将发出请求来获取该文件(如果它是外部的)。然后将在恢复解析之前执行该脚本。


13.2 异步加载<script async>
async
在 HTML 解析期间下载文件,并在下载完成后暂停 HTML 解析器以执行该文件。

13.3延迟加载<script defer>
defer
在 HTML 解析期间下载文件,并且仅在解析器完成后才执行它。defer
脚本还保证按照它们在文档中出现的顺序执行。

14. Ajax、Fetch、Axios三者的区别
14.1 Ajax
它的全称是:Asynchronous JavaScript And XML,翻译过来就是“异步的 Javascript 和 XML”。
Ajax 是一个技术统称,是一个概念模型,它囊括了很多技术,并不特指某一技术,它很重要的特性之一就是让页面实现局部刷新,无需重载整个页面。
简单来说,Ajax 是一种思想,XMLHttpRequest 只是实现 Ajax 的一种方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <body> <script> function ajax(url) { const xhr = new XMLHttpRequest(); xhr.open("get", url, false); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { console.info("响应结果", xhr.response) } } } xhr.send(null); } ajax('https://smallpig.site/api/category/getCategory') </script> </body>
|
我们使用这种方式实现网络请求时,如果请求内部又包含请求,以此循环,就会出现回调地狱,这也是一个诟病,后来才催生了更加优雅的请求方式。
14.2 Fetch
Fetch 是在 ES6 出现的,它使用了 ES6 提出的 promise 对象。它是 XMLHttpRequest 的替代品。
特点:
- 使用 promise,不使用回调函数。
- 采用模块化设计,比如 rep、res 等对象分散开来,比较友好。
- 通过数据流对象处理数据,可以提高网站性能。
1 2 3 4 5 6 7 8 9 10
| <body> <script> function ajaxFetch(url) { fetch(url).then(res => res.json()).then(data => { console.info(data) }) } ajaxFetch('https://smallpig.site/api/category/getCategory') </script> </body>
|
上段代码利用 Fetch 发送了一个最简单的 get 请求,其中最重要的特点之一就是采用了.then 链式调用的方式处理结果,这样不仅利于代码的可读,而且也解决了回调地狱的问题。
14.3 Axios
Axios 是一个基于 promise 封装的网络请求库,它是基于 XHR 进行二次封装。
特点:
- 从浏览器中创建 XMLHttpRequests
- 从 node.js 创建 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF
所以说,Axios 可以说是 XHR 的一个子集,而 XHR 又是 Ajax 的一个子集。
1 2 3 4 5 6 7 8 9
| axios({ method: 'post', url: '/user/12345', data: { firstName: 'Fred', lastName: 'Flintstone' } })
|
14.4 总结

