- ">

- 数据类型
- 数组方法
- substr和substring的区别
- 原型链
- instanceof原理
- 继承
- 作用域
- 闭包
- var、let、const
- for…in…和for…of…的区别
- 使用单例模式实现Storage,并对localStorage进行封装设置值setItem(key,value)和getItem(key)
- 浏览器存储
- 取出url参数
- 类数组转换成数组
- 模块化
- ajax
- call、apply、bind的区别以及如何模拟
- 数组去重
- 数组扁平化
- 浅拷贝和深拷贝
- 实现一个equal函数,只要值一样,那么两个对象就相等
- 自定义事件
- 事件流
- 什么是事件委托
- EventLoop
- 实现一个Promise
- 同源策略
- JSONP
- 用户从输入URL回车后到浏览器呈现页面发生了什么
- 性能优化
- web安全
- webpack
数据类型
在js中,数据类型分为基本数据类型和引用数据类型
基本数据类型:string、number、boolean、symbol、null、undefined
引用数据类型:function、object
我们可以通过typeof简单的区分js数据类型,能够区分基本数据类型以及对象类型,对于string、number、boolean、symbol、undefined、function、object,而object类型有object、array、null,由于历史的问题,这里null被划分到了object。如果我们要获取具体的类型,我们可以使用Object.prototype.toString.call(obj)来获取更加准确的类型,对此可以进行函数封装
/*** 获取准确数据类型* @param obj 要获取的数据类型的数据* @return string 数据类型,* 有 Null、Object、Array、Undefined、String、Boolean、Number、Function、Symbol*/function getType(obj) {const baseType = Object.prototype.toString.call(obj);const startIndex = 8;const endIndex = baseType.length - 1;return baseType.substring(startIndex, endIndex);}
数组方法
改变原数组:push、pop、unshift、shift、splice
substr和substring的区别
substr(startIndex, length)
startIndex:起始位置length:截取的长度
substring(startIndex, endIndex)
startIndex:起始位置endIndex:结束位置(不包括在内,即区间是左闭右开)
原型链
每一个构造函数的实例都有一个隐式原型proto,每一个构造函数都有一个显示原型,实例的隐式原型指向构造函数的显示原型prototype,显示原型prototype的构造器指向构造函数自己,构造函数的构造器指向Function,这样层层向上,直到null为止,可以参考下图:
instanceof原理
利用原型链判断实例是否出现在原型链上。其实我们可以思考一下,上面的结构是不是有点像数据结构的中链表,那么我们来改造一部分。
好了,这样是不是比较好理解了,这样实现模拟也就不难了。
function _instanceof(o1, o2) {let current = o1.__proto__;while (current !== null) {if (current === o2.prototype) {return true;}current = current.__proto__;}return false;}
继承
作用域
在js中,作用域一共有3种,分别是:全局作用域、函数作用域和块级作用域,而闭包是作用域一种特殊的应用。
闭包
闭包就是函数A中嵌套函数B,函数B使用到了函数A中的变量,函数A返回函数B。闭包用来创建私有变量,闭包中的数据时不能被外部直接访问的,所以可能会造成内存泄漏,因此尽可能少用闭包。
应用:模拟ES6中Map
function Map() {this.data = {};return (function(data) {return {add: function(key, value) {data[key] = value;},get: function(key) {return data[key];},clear: function() {data = {};},remove: function(key) {delete data[key];},getSize: function() {return Object.keys(data).length;}};})(this.data);}
var、let、const
用var声明的变量可以会变量提升,而let、const不会有变量提升,使用let和const会生成块级作用域,var和let都是声明变量,const是声明常量,此常量并不是说不可以修改,对于原始数据类型来说是不能修改的,但是如果是引用数据类型就可以修改该引用类型里面的属性值。let会存在暂时性死区。
for…in…和for…of…的区别
for…in…是ES5中的,for…of…是ES6中的。
for…in…输出的是key,for…of…输出的是value。
如果给遍历对象添加自定义属性,for…in…会遍历自身的自定义属性,for…of…不会。
遍历对象推荐使用for…in…,遍历数组推荐使用for…of…,且for…in..不能遍历对象。
使用单例模式实现Storage,并对localStorage进行封装设置值setItem(key,value)和getItem(key)
class Storage {static getInstance() {if (!this.instance) {this.instance = new Storage();}return this.instance;}getItem(key) {return localStorage.getItem(key);}setItem(key, value) {localStorage.setItem(key, value);}}
浏览器存储
浏览器中的存储方式有3中,古老的cookie,现代的localStorage、sessionStorage
cookie
cookie是用来辨别身份,进行session跟踪而存储在用户本地终端上的数据,由客户机暂时保存。由于每次请求都会携带cookie,所以这就会浪费带宽,因为有时候并不需要cookie。
localStorage
sessionStorage
区别
cookie由于古老,所以没有友好的api,而localStorage、sessionStorage有便捷的api,如getItem、setItem
取出url参数
方法1:分割
// http://www.baidu.com?name=xx&age=22function getParams(url) {const urls = url.split('?');const params = urls[1].split('&');const res = {};params.forEach(item => {const _p = item.split('=');res[_p[0]] = _p[1]});return res;}
方法2:URLSearchParams
利用URLSearchParams对象
const p = new URLSearchParams(url);p.has(key)p.append(key, value)p.getAll()p.get(key)p.set(key, value)p.keys()p.values()p.sort()
类数组转换成数组
ES5方法
Array.prototype.slice.call(arguments)
ES6方法
Array.from(arguments)
模块化
CommonJS
使用同步方式加载模块,Node.js是commonJS的主要实践者,它有4个重要的环境变量为模块化提供支持:module、exports、require、global。实际使用时,用module.exports对外暴露(不推荐使用exports),用require来引入模块。
AMD
采用异步方式加载模块,模块的加载不影响它后面的语句的执行,所有依赖这个模块的语句,都定义在一个回调函数中,等加载完成之后,这个回调函数才会执行。
CMD
CMD和AMD很相似,不同在于AMD推崇依赖前置、提前执行,而CMD推崇依赖就近、延迟执行
ES6
CommonJS和ES6的区别
CommonJS模块输出是一个值的拷贝,ES6模块输出是值得引用。
CommonJS是运行时加载,ES6是编译时加载
ajax
function ajax(opts) {return new Promise((resolve, reject) => {const xhr = new XMLHttpRequest();xhr.open(opts.method, opts.url, opts.async);xhr.onreadystatechange = function(res) {if (res.target.readyState === 4 &&res.target.status === 200) {resolve(JSON.parse(res.target.responseText));} else {reject(res);}};xhr.send(opts.data);});}
call、apply、bind的区别以及如何模拟
区别
都是用来改变this的指向,call和apply是立即执行函数,而bind不是;call和bind传参都是以任意参数传递,而apply是以列表来传递参数
模拟
Function.prototype._call = function(ctx, ..args) {// 把当前this复制给ctx的_fn函数ctx._fn = this;// 执行ctx的_fn函数,这样_fn的this就是ctxctx._fn(...args)delete ctx._fn}Function.prototype._apply = function(ctx, ..args) {ctx._fn = this;ctx._fn(...args[0])delete ctx._fn}Function.prototype._bind = function(ctx, ...args) {return () => {ctx._fn = this;ctx._fn(...args)delete ctx._fn}}
数组去重
传统方法
function unique(arr) {let _arr = [];arr.filter(item => _arr.indexof(item) === -1 && _arr.push(item))return _arr;}
ES6
Array.from(new Set(arguments))
数组扁平化
function flat(arr) {const isDeep = arr.some(item => item instanceof Array);if (!isDeep) {return arr;}const res = Array.prototype.concat.apply([], arr);return falt(res);}
浅拷贝和深拷贝
浅拷贝就是只拷贝一层;深拷贝就是不管有几层,全部拷贝,不能存在引用关系
浅拷贝
function clone(obj) {if (typeof obj !== 'object' || typeof obj === null) {return obj;}let res;if (obj instanceof Array) {res = [];res = obj.concat();} else {res = {};Object.assign(res, obj)}return res;}
深拷贝
function deepClone(obj) {if (typeof obj !== 'object' || typeof obj === null) {return obj;}let res;if (obj instanceof Array) {res = [];} else {res = {};}const keys = Object.keys(obj);for(let key in keys) {res[key] = clone(obj[key])}return res;}
实现一个equal函数,只要值一样,那么两个对象就相等
function isObject(obj) {return typeof obj === 'object' && obj !== null}function equal(obj, compare) {if (!isObject(obj) || !isObject(compare)) {return obj === compare;}if (obj === compare) {return true;}const keys = Object.keys(obj);const compareKeys = Object.keys(compare);if (keys.length !== compareKeys.length) {return false;}for (let key in obj) {let res = equal(obj[key] === compare[key]);if (!res) {return false;}}return true;}
自定义事件
目前js中,主要通过Event()和CustomEvent()来实现自定义事件。
Event()
创建简单的自定义事件
let event = new Event(eventName, options)
eventName:事件名称options 配置项,包括
| 字段名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| bubbles | 该事件是否冒泡 | Boolean | false |
| cancelable | 该事件能否被取消 | Boolean | false |
| composed | 该事件是否会在影子DOM根节点之外触发侦听器 | Boolean | false |
示例:
// 创建一个支持冒泡且不能被取消的 pingan 事件let myEvent = new Event("pingan", {"bubbles":true, "cancelable":false});document.dispatchEvent(myEvent);// 事件可以在任何元素触发,不仅仅是documenttestDOM.dispatchEvent(myEvent);
CustomEvent()
用于创建需要传参的自定义事件
let event = new CustomEvent(eventName, options)
eventName:事件名称options 配置项,包括
| 字段名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| bubbles | 该事件是否冒泡 | Boolean | false |
| cancelable | 该事件能否被取消 | Boolean | false |
| detail | 表示该事件中需要被传递的数据,在 EventListener 获取。 |
Any | null |
示例:
// 创建事件let myEvent = new CustomEvent("ce", {detail: { name: "xxx" }});// 添加适当的事件监听器window.addEventListener("ce", e => {alert(`ce事件触发,是 ${e.detail.name} 触发。`);});document.getElementById("leo").addEventListener("click", function () {// 派发事件window.dispatchEvent(myEvent);})
使用场景
场景1:单个目标对象发生改变,需要通知多个观察者一同改变。
场景2:解耦多模块开协作。
事件流
事件流分为两种,分别是冒泡事件流和捕获事件流。
- 冒泡事件流:存在于普通浏览器中,从具体的元素向不具体元素冒泡。
- 捕获事件流:存在于IE浏览器中,从不具体的元素向具体的元素捕获。
什么是事件委托
事件委托利用事件冒泡机制,将原本子元素要干的事交给父级元素来干。
EventLoop
js是单线程的,所有的代码默认存在于宏任务中,如果遇到异步任务,则放入微任务中,首先去执行宏任务,如果宏任务为空,则执行微任务,微任务执行完后,再去判断是否有宏任务,这样反复循环,就形成了EventLoop。
宏任务:普通的代码
微任务:setTimeout/setInterval中的回调函数、process.nextTick、promise的then回调
下面代码的结果是什么?
console.log(1)setTimeout(function() {console.log(2)})process.nextTick(function() {console.log(3)})let p = new Promise((resolve, reject) => {console.log(4);resolve()});p.then(() => {console.log(5)})console.log(6)
实现一个Promise
同源策略
对于浏览器来说,只有同源的数据可以访问,即协议、域名、端口一致,而js、css、img属于资源文件,所以就可以跨域。
跨域的方法
JSONP
CROS
window.postMessage
WebSocket
JSONP
概念
jsonp是一种跨域的方案,由于script可以跨域的,所以通过动态创建script标签来实现跨域
手写Promise版JSONP
function jsonp(options) {return new Promise((resolve, reject) => {function clean(script, script) {document.body.remove(script);window[options.cbName] = null;}function concatUrl(url, params) {let _url = url + "?";for (let key in params) {_url += key + "=" + params[key] + "&";}return _url;}if (!options.url) {reject({ msg: "url不能为空" });}if (!options.cbName) {reject({ msg: "cbName不能为空" });}window[options.cbName] = function(res) {resolve(res);};let url = concatUrl(options.url, options.params);const _script = document.createElement("script");_script.src = url;document.body.appendChild(_script);_script.onload = function() {clean(_script, options.cbName);};_script.onerror = function(err) {clean(_script, options.cbName);reject({ msg: err });};});}// 示例jsonp({url: "http://localhost:5500/user.js",cbName: "",params: {age: 22}}).then(res => {console.log(res);}).catch(err => {console.log(err);});
用户从输入URL回车后到浏览器呈现页面发生了什么

用户输入url回车后,浏览器就会开启一个线程来处理了这个请求,首先去DNS缓存中寻找url对应的ip,如果没找到就去系统的hosts文件中找,hosts文件也没找到就会去路由器缓存中找,都没找到就使用DNS解析出ip,然后建立TCP/IP连接,向服务器发起请求,服务器做出处理并响应数据给浏览器,浏览器拿到数据后就开始解析;首先htmlParser来解析DOM生成DOMTree,同时cssParser来解析CSS生成CSSOMTree,两者都解析完了,就会合并成renderTree。在renderTree这里会有reflow和repaint,最后调用paint方法将页面绘制在浏览器上。要注意的是如果在接下DOM的时候,遇到了script脚本,那么就会阻塞DOM的解析,因为js是单线程的,如果DOM解析和script同时进行,那么浏览器到底该听谁的。对于script有三种状态:async、defer、无async和defer属性。
- async:当script使用async时,DOM的解析和js的解析是同步的,当js解析完后,js会立刻执行同时DOM解析会被停止,js执行完后,才会继续进行DOM解析
- defer:当script使用async时,DOM的解析和js的解析是同步的,当js解析完后,js不会立即执行,而DOM会继续解析,等到DOM全部解析完后,js才会执行
- 无async和defer属性:当script不使用任何属性时,遇到js时,DOM解析就会暂停,等到js解析并执行完毕后DOM才会继续解析。
性能优化
性能优化就可以根据上面一提来看性能监控
异常监控
对于代码错误,可以使用window.onerror节流
节流就是在一定周期内,该函数只执行一次。
function throttle(fn, delay = 100) {let timer;return () => {if (timer) {return;}timer = setTimeout(() => {fn.call(this);timer = null;}, delay)}}
防抖
防抖就是一段时间后,如果没有再次触发事件,则执行该事件。
function debounce(fn, delay = 300) {let timer;return () => {if (timer) {clearTimeout(timer)}timer = setTimeout(() => {fn.call(this, arguments);timer = null;}, delay)}}
web安全
XSS攻击
XSS攻击(Cross-site scripting)即跨站脚本攻击,是一种针对网站应用程序的安全漏洞的攻击,是代码注入的一种,它允许用户将恶意的代码注入到网页,其他用户访问网页时就会受到影响。
防御措施:
- 过滤特殊字符。
举例:攻击者在评论区提交获取cookie的代码,其它用户访问该评论所在网页就会获取到cookie信息。
CSRF攻击
CSRF(Cross-site request forgery)攻击即跨站请求伪造,是一种挟持用户在当前已登录的web应用程序上执行非本意的操作的攻击方法。
防御措施:
- 验证token
- 使用referer字段
- 写操作用post
-
webpack
webpack是前端自动化构建工具,主要由入口、出口、loaders、plugins组成
构建优化
减少编译体积 ContextReplacementPugin、IgnorePlugin、babel-plugin-import、babel-plugin-transform-runtime。
- 并行编译 happypack、thread-loader、uglifyjsWebpackPlugin开启并行
- 缓存 cache-loader、hard-source-webpack-plugin、uglifyjsWebpackPlugin开启缓存、babel-loader开启缓存
预编译 dllWebpackPlugin && DllReferencePlugin、auto-dll-webapck-plugin
性能优化
1、减少编译体积 Tree-shaking、Scope Hositing。
- 2、hash缓存 webpack-md5-plugin
- 3、拆包 splitChunksPlugin、import()、require.ensure
