Webpack Proxy工作原理?为什么能解决跨域
1. 是什么
webpack proxy
,即webpack
提供的代理服务
根本行为就是接管客户端发送的申请后转发给其余服务器
其目标是为了便于开发者在开发模式下解决跨域问题(浏览器安全策略限度)
想要实现代理首先须要一个两头服务器,webpack
中提供服务器的工具为webpack-dev-server
2. webpack-dev-server
webpack-dev-server
是 webpack
官网推出的一款开发工具,将主动编译和主动刷新浏览器等一系列对开发敌对的性能全副集成在了一起
目标是为了进步开发者日常的开发效率,「只实用在开发阶段」
对于配置方面,在webpack
配置对象属性中通过devServer
属性提供,如下:
// ./webpack.config.jsconst path = require('path')module.exports = { // ... devServer: { contentBase: path.join(__dirname, 'dist'), compress: true, port: 9000, proxy: { '/api': { target: 'https://api.github.com' } } // ... }}
devServetr
外面proxy
则是对于代理的配置,该属性为对象的模式,对象中每一个属性就是一个代理的规定匹配
属性的名称是须要被代理的申请门路前缀,个别为了分别都会设置前缀为/api
,值为对应的代理匹配规定,对应如下:
target
:示意的是代理到的指标地址pathRewrite
:默认状况下,咱们的/api-hy
也会被写入到URL中,如果心愿删除,能够应用pathRewrite
secure
:默认状况下不接管转发到https
的服务器上,如果心愿反对,能够设置为false
changeOrigin
:它示意是否更新代理后申请的headers
中host
地址
2. 工作原理
proxy
工作原理本质上是利用http-proxy-middleware
这个http
代理中间件,实现申请转发给其余服务器
举个例子:
在开发阶段,本地地址为http://localhost:3000
,该浏览器发送一个前缀带有/api
标识的申请到服务端获取数据,但响应这个申请的服务器只是将申请转发到另一台服务器中
const express = require('express');const proxy = require('http-proxy-middleware');const app = express();app.use('/api', proxy({target: 'http://www.example.org', changeOrigin: true}));app.listen(3000);// http://localhost:3000/api/foo/bar -> http://www.example.org/api/foo/bar
3. 跨域
在开发阶段,webpack-dev-server
会启动一个本地开发服务器,所以咱们的利用在开发阶段是独立运行在localhost
的一个端口上,而后端服务又是运行在另外一个地址上
所以在开发阶段中,因为浏览器同源策略的起因,当本地拜访后端就会呈现跨域申请的问题
通过设置webpack proxy
实现代理申请后,相当于浏览器与服务端中增加一个代理者
当本地发送申请的时候,代理服务器响应该申请,并将申请转发到指标服务器,指标服务器响应数据后再将数据返回给代理服务器,最终再由代理服务器将数据响应给本地
在代理服务器传递数据给本地浏览器的过程中,两者同源,并不存在跨域行为,这时候浏览器就能失常接收数据
留神:「服务器与服务器之间申请数据并不会存在跨域行为,跨域行为是浏览器安全策略限度」
代码输入后果
var obj = { say: function() { var f1 = () => { console.log("1111", this); } f1(); }, pro: { getPro:() => { console.log(this); } }}var o = obj.say;o();obj.say();obj.pro.getPro();
输入后果:
1111 window对象1111 obj对象window对象
解析:
- o(),o是在全局执行的,而f1是箭头函数,它是没有绑定this的,它的this指向其父级的this,其父级say办法的this指向的是全局作用域,所以会打印出window;
- obj.say(),谁调用say,say 的this就指向谁,所以此时this指向的是obj对象;
- obj.pro.getPro(),咱们晓得,箭头函数时不绑定this的,getPro处于pro中,而对象不形成独自的作用域,所以箭头的函数的this就指向了全局作用域window。
闭包的利用场景
- 柯里化 bind
- 模块
网络劫持有哪几种,如何防备?
⽹络劫持分为两种:
(1)DNS劫持: (输⼊京东被强制跳转到淘宝这就属于dns劫持)
- DNS强制解析: 通过批改运营商的本地DNS记录,来疏导⽤户流量到缓存服务器
- 302跳转的⽅式: 通过监控⽹络出⼝的流量,分析判断哪些内容是能够进⾏劫持解决的,再对劫持的内存发动302跳转的回复,疏导⽤户获取内容
(2)HTTP劫持: (拜访⾕歌然而⼀直有贪玩蓝⽉的⼴告),因为http明⽂传输,运营商会批改你的http响应内容(即加⼴告)
DNS劫持因为涉嫌守法,曾经被监管起来,当初很少会有DNS劫持,⽽http劫持仍然⾮常盛⾏,最无效的方法就是全站HTTPS,将HTTP加密,这使得运营商⽆法获取明⽂,就⽆法劫持你的响应内容。
实现一个 add 办法
题目形容:实现一个 add 办法 使计算结果可能满足如下预期:
add(1)(2)(3)()=6
add(1,2,3)(4)()=10
其实就是考函数柯里化
实现代码如下:
function add(...args) { let allArgs = [...args]; function fn(...newArgs) { allArgs = [...allArgs, ...newArgs]; return fn; } fn.toString = function () { if (!allArgs.length) { return; } return allArgs.reduce((sum, cur) => sum + cur); }; return fn;}
浅拷贝
// 这里只思考对象类型function shallowClone(obj) { if(!isObject(obj)) return obj; let newObj = Array.isArray(obj) ? [] : {}; // for...in 只会遍历对象本身的和继承的可枚举的属性(不含 Symbol 属性) for(let key in obj) { // obj.hasOwnProperty() 办法只思考对象本身的属性 if(obj.hasOwnProperty(key)) { newObj[key] = obj[key]; } } return newObj;}
参考:前端进阶面试题具体解答
AJAX
const getJSON = function(url) { return new Promise((resolve, reject) => { const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'); xhr.open('GET', url, false); xhr.setRequestHeader('Accept', 'application/json'); xhr.onreadystatechange = function() { if (xhr.readyState !== 4) return; if (xhr.status === 200 || xhr.status === 304) { resolve(xhr.responseText); } else { reject(new Error(xhr.responseText)); } } xhr.send(); })}
实现数组原型办法
forEach
Array.prototype.forEach2 = function(callback, thisArg) { if (this == null) { throw new TypeError('this is null or not defined') } if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function') } const O = Object(this) // this 就是以后的数组 const len = O.length >>> 0 // 前面有解释 let k = 0 while (k < len) { if (k in O) { callback.call(thisArg, O[k], k, O); } k++; }}
O.length >>> 0 是什么操作?就是无符号右移 0 位,那有什么意义嘛?就是为了保障转换后的值为正整数。其实底层做了 2 层转换,第一是非 number 转成 number 类型,第二是将 number 转成 Uint32 类型
map
基于 forEach 的实现可能很容易写出 map 的实现:
- Array.prototype.forEach2 = function(callback, thisArg) {+ Array.prototype.map2 = function(callback, thisArg) { if (this == null) { throw new TypeError('this is null or not defined') } if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function') } const O = Object(this) const len = O.length >>> 0- let k = 0+ let k = 0, res = [] while (k < len) { if (k in O) {- callback.call(thisArg, O[k], k, O);+ res[k] = callback.call(thisArg, O[k], k, O); } k++; }+ return res}
filter
同样,基于 forEach 的实现可能很容易写出 filter 的实现:
- Array.prototype.forEach2 = function(callback, thisArg) {+ Array.prototype.filter2 = function(callback, thisArg) { if (this == null) { throw new TypeError('this is null or not defined') } if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function') } const O = Object(this) const len = O.length >>> 0- let k = 0+ let k = 0, res = [] while (k < len) { if (k in O) {- callback.call(thisArg, O[k], k, O);+ if (callback.call(thisArg, O[k], k, O)) {+ res.push(O[k]) + } } k++; }+ return res}
some
同样,基于 forEach 的实现可能很容易写出 some 的实现:
- Array.prototype.forEach2 = function(callback, thisArg) {+ Array.prototype.some2 = function(callback, thisArg) { if (this == null) { throw new TypeError('this is null or not defined') } if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function') } const O = Object(this) const len = O.length >>> 0 let k = 0 while (k < len) { if (k in O) {- callback.call(thisArg, O[k], k, O);+ if (callback.call(thisArg, O[k], k, O)) {+ return true+ } } k++; }+ return false}
reduce
Array.prototype.reduce2 = function(callback, initialValue) { if (this == null) { throw new TypeError('this is null or not defined') } if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function') } const O = Object(this) const len = O.length >>> 0 let k = 0, acc if (arguments.length > 1) { acc = initialValue } else { // 没传入初始值的时候,取数组中第一个非 empty 的值为初始值 while (k < len && !(k in O)) { k++ } if (k > len) { throw new TypeError( 'Reduce of empty array with no initial value' ); } acc = O[k++] } while (k < len) { if (k in O) { acc = callback(acc, O[k], k, O) } k++ } return acc}
浏览器渲染优化
(1)针对JavaScript: JavaScript既会阻塞HTML的解析,也会阻塞CSS的解析。因而咱们能够对JavaScript的加载形式进行扭转,来进行优化:
(1)尽量将JavaScript文件放在body的最初
(2) body两头尽量不要写<script>
标签
(3)<script>
标签的引入资源形式有三种,有一种就是咱们罕用的间接引入,还有两种就是应用 async 属性和 defer 属性来异步引入,两者都是去异步加载内部的JS文件,不会阻塞DOM的解析(尽量应用异步加载)。三者的区别如下:
- script 立刻进行页面渲染去加载资源文件,当资源加载结束后立刻执行js代码,js代码执行结束后持续渲染页面;
- async 是在下载实现之后,立刻异步加载,加载好后立刻执行,多个带async属性的标签,不能保障加载的程序;
- defer 是在下载实现之后,立刻异步加载。加载好后,如果 DOM 树还没构建好,则先等 DOM 树解析好再执行;如果DOM树曾经筹备好,则立刻执行。多个带defer属性的标签,依照程序执行。
(2)针对CSS:应用CSS有三种形式:应用link、@import、内联款式,其中link和@import都是导入内部款式。它们之间的区别:
- link:浏览器会派发一个新等线程(HTTP线程)去加载资源文件,与此同时GUI渲染线程会持续向下渲染代码
- @import:GUI渲染线程会临时进行渲染,去服务器加载资源文件,资源文件没有返回之前不会持续渲染(妨碍浏览器渲染)
- style:GUI间接渲染
内部款式如果长时间没有加载结束,浏览器为了用户体验,会应用浏览器会默认款式,确保首次渲染的速度。所以CSS个别写在headr中,让浏览器尽快发送申请去获取css款式。
所以,在开发过程中,导入内部款式应用link,而不必@import。如果css少,尽可能采纳内嵌款式,间接写在style标签中。
(3)针对DOM树、CSSOM树: 能够通过以下几种形式来缩小渲染的工夫:
- HTML文件的代码层级尽量不要太深
- 应用语义化的标签,来防止不规范语义化的非凡解决
- 缩小CSSD代码的层级,因为选择器是从左向右进行解析的
(4)缩小回流与重绘:
- 操作DOM时,尽量在低层级的DOM节点进行操作
- 不要应用
table
布局, 一个小的改变可能会使整个table
进行从新布局 - 应用CSS的表达式
- 不要频繁操作元素的款式,对于动态页面,能够批改类名,而不是款式。
- 应用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其余元素
- 防止频繁操作DOM,能够创立一个文档片段
documentFragment
,在它下面利用所有DOM操作,最初再把它增加到文档中 - 将元素先设置
display: none
,操作完结后再把它显示进去。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。 - 将DOM的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于浏览器的渲染队列机制。
浏览器针对页面的回流与重绘,进行了本身的优化——渲染队列
浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了肯定的数量或者到了肯定的工夫距离,浏览器就会对队列进行批处理。这样就会让屡次的回流、重绘变成一次回流重绘。
将多个读操作(或者写操作)放在一起,就会等所有的读操作进入队列之后执行,这样,本来应该是触发屡次回流,变成了只触发一次回流。
new 一个构造函数,如果函数返回 return {}
、 return null
, return 1
, return true
会产生什么状况?
如果函数返回一个对象,那么new 这个函数调用返回这个函数的返回对象,否则返回 new 创立的新对象
组件之间的传值有几种形式
1、父传子2、子传父3、eventbus4、ref/$refs5、$parent/$children6、$attrs/$listeners7、依赖注入(provide/inject)
Promise.resolve
Promise.resolve = function(value) { // 1.如果 value 参数是一个 Promise 对象,则一成不变返回该对象 if(value instanceof Promise) return value; // 2.如果 value 参数是一个具备 then 办法的对象,则将这个对象转为 Promise 对象,并立刻执行它的then办法 if(typeof value === "object" && 'then' in value) { return new Promise((resolve, reject) => { value.then(resolve, reject); }); } // 3.否则返回一个新的 Promise 对象,状态为 fulfilled return new Promise(resolve => resolve(value));}
常见的浏览器内核比拟
- Trident: 这种浏览器内核是 IE 浏览器用的内核,因为在晚期 IE 占有大量的市场份额,所以这种内核比拟风行,以前有很多网页也是依据这个内核的规范来编写的,然而实际上这个内核对真正的网页规范反对不是很好。然而因为 IE 的高市场占有率,微软也很长时间没有更新 Trident 内核,就导致了 Trident 内核和 W3C 规范脱节。还有就是 Trident 内核的大量 Bug 等平安问题没有失去解决,加上一些专家学者公开本人认为 IE 浏览器不平安的观点,使很多用户开始转向其余浏览器。
- Gecko: 这是 Firefox 和 Flock 所采纳的内核,这个内核的长处就是功能强大、丰盛,能够反对很多简单网页成果和浏览器扩大接口,然而代价是也不言而喻就是要耗费很多的资源,比方内存。
- Presto: Opera 已经采纳的就是 Presto 内核,Presto 内核被称为公认的浏览网页速度最快的内核,这得益于它在开发时的天生劣势,在解决 JS 脚本等脚本语言时,会比其余的内核快3倍左右,毛病就是为了达到很快的速度而丢掉了一部分网页兼容性。
- Webkit: Webkit 是 Safari 采纳的内核,它的长处就是网页浏览速度较快,尽管不迭 Presto 然而也胜于 Gecko 和 Trident,毛病是对于网页代码的容错性不高,也就是说对网页代码的兼容性较低,会使一些编写不规范的网页无奈正确显示。WebKit 前身是 KDE 小组的 KHTML 引擎,能够说 WebKit 是 KHTML 的一个开源的分支。
- Blink: 谷歌在 Chromium Blog 上发表博客,称将与苹果的开源浏览器外围 Webkit 各奔前程,在 Chromium 我的项目中研发 Blink 渲染引擎(即浏览器外围),内置于 Chrome 浏览器之中。其实 Blink 引擎就是 Webkit 的一个分支,就像 webkit 是KHTML 的分支一样。Blink 引擎当初是谷歌公司与 Opera Software 独特研发,下面提到过的,Opera 弃用了本人的 Presto 内核,退出 Google 营垒,追随谷歌一起研发 Blink。
Loader和Plugin 有什么区别
Loader:直译为"加载器"。Webpack将所有文件视为模块,然而webpack原生是只能解析js文件,如果想将其余文件也打包的话,就会用到loader
。 所以Loader的作用是让webpack领有了加载和解析非JavaScript文件的能力。 Plugin:直译为"插件"。Plugin能够扩大webpack的性能,让webpack具备更多的灵活性。 在 Webpack 运行的生命周期中会播送出许多事件,Plugin 能够监听这些事件,在适合的机会通过 Webpack 提供的 API 扭转输入后果。
事件总线(公布订阅模式)
class EventEmitter { constructor() { this.cache = {} } on(name, fn) { if (this.cache[name]) { this.cache[name].push(fn) } else { this.cache[name] = [fn] } } off(name, fn) { let tasks = this.cache[name] if (tasks) { const index = tasks.findIndex(f => f === fn || f.callback === fn) if (index >= 0) { tasks.splice(index, 1) } } } emit(name, once = false, ...args) { if (this.cache[name]) { // 创立正本,如果回调函数内持续注册雷同事件,会造成死循环 let tasks = this.cache[name].slice() for (let fn of tasks) { fn(...args) } if (once) { delete this.cache[name] } } }}// 测试let eventBus = new EventEmitter()let fn1 = function(name, age) { console.log(`${name} ${age}`)}let fn2 = function(name, age) { console.log(`hello, ${name} ${age}`)}eventBus.on('aaa', fn1)eventBus.on('aaa', fn2)eventBus.emit('aaa', false, '布兰', 12)// '布兰 12'// 'hello, 布兰 12'
原函数形参定长(此时 fn.length
是个不变的常数)
// 写法1-不保留参数,递归部分函数function curry(fn) { let judge = (...args) => { // 递归完结条件 if(args.length === fn.length) return fn(...args); return (...arg) => judge(...args, ...arg); } return judge;}// 写法2-保留参数,递归整体函数function curry(fn) { // 保留参数,除去第一个函数参数 let presentArgs = [].slice.call(arguments, 1); // 返回一个新函数 return function(){ // 新函数调用时会持续传参 let allArgs = [...presentArgs, ...arguments]; // 递归完结条件 if(allArgs.length === fn.length) { // 如果参数够了,就执行原函数 return fn(,,,allArgs); } // 否则持续柯里化 else return curry(fn, ...allArgs); }}// 测试function add(a, b, c, d) { return a + b + c + d;}console.log(add(1, 2, 3, 4));let addCurry = curry(add);// 以下后果都返回 10console.log(addCurry(1)(2)(3)(4)); console.log(addCurry(1)(2, 3, 4));console.log(addCurry(1, 2)(3)(4));console.log(addCurry(1, 2)(3, 4));console.log(addCurry(1, 2, 3)(4));console.log(addCurry(1, 2, 3, 4));
代码输入后果
function fn1(){ console.log('fn1')}var fn2fn1()fn2()fn2 = function() { console.log('fn2')}fn2()
输入后果:
fn1Uncaught TypeError: fn2 is not a functionfn2
这里也是在考查变量晋升,关键在于第一个fn2(),这时fn2仍是一个undefined的变量,所以会报错fn2不是一个函数。
实现有并行限度的 Promise 调度器
题目形容:JS 实现一个带并发限度的异步调度器 Scheduler,保障同时运行的工作最多有两个
addTask(1000,"1"); addTask(500,"2"); addTask(300,"3"); addTask(400,"4"); 的输入程序是:2 3 1 4 整个的残缺执行流程:一开始1、2两个工作开始执行500ms时,2工作执行结束,输入2,工作3开始执行800ms时,3工作执行结束,输入3,工作4开始执行1000ms时,1工作执行结束,输入1,此时只剩下4工作在执行1200ms时,4工作执行结束,输入4
实现代码如下:
class Scheduler { constructor(limit) { this.queue = []; this.maxCount = limit; this.runCounts = 0; } add(time, order) { const promiseCreator = () => { return new Promise((resolve, reject) => { setTimeout(() => { console.log(order); resolve(); }, time); }); }; this.queue.push(promiseCreator); } taskStart() { for (let i = 0; i < this.maxCount; i++) { this.request(); } } request() { if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) { return; } this.runCounts++; this.queue .shift()() .then(() => { this.runCounts--; this.request(); }); }}const scheduler = new Scheduler(2);const addTask = (time, order) => { scheduler.add(time, order);};addTask(1000, "1");addTask(500, "2");addTask(300, "3");addTask(400, "4");scheduler.taskStart();
什么是 XSS 攻打?
(1)概念
XSS 攻打指的是跨站脚本攻打,是一种代码注入攻打。攻击者通过在网站注入歹意脚本,使之在用户的浏览器上运行,从而盗取用户的信息如 cookie 等。
XSS 的实质是因为网站没有对恶意代码进行过滤,与失常的代码混合在一起了,浏览器没有方法分辨哪些脚本是可信的,从而导致了恶意代码的执行。
攻击者能够通过这种攻击方式能够进行以下操作:
- 获取页面的数据,如DOM、cookie、localStorage;
- DOS攻打,发送正当申请,占用服务器资源,从而使用户无法访问服务器;
- 毁坏页面构造;
- 流量劫持(将链接指向某网站);
(2)攻打类型
XSS 能够分为存储型、反射型和 DOM 型:
- 存储型指的是歹意脚本会存储在指标服务器上,当浏览器申请数据时,脚本从服务器传回并执行。
- 反射型指的是攻击者诱导用户拜访一个带有恶意代码的 URL 后,服务器端接收数据后处理,而后把带有恶意代码的数据发送到浏览器端,浏览器端解析这段带有 XSS 代码的数据后当做脚本执行,最终实现 XSS 攻打。
- DOM 型指的通过批改页面的 DOM 节点造成的 XSS。
1)存储型 XSS 的攻打步骤:
- 攻击者将恶意代码提交到⽬标⽹站的数据库中。
- ⽤户关上⽬标⽹站时,⽹站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
- ⽤户浏览器接管到响应后解析执⾏,混在其中的恶意代码也被执⾏。
- 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者假冒⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。
这种攻打常⻅于带有⽤户保留数据的⽹站性能,如论坛发帖、商品评论、⽤户私信等。
2)反射型 XSS 的攻打步骤:
- 攻击者结构出非凡的 URL,其中蕴含恶意代码。
- ⽤户关上带有恶意代码的 URL 时,⽹站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
- ⽤户浏览器接管到响应后解析执⾏,混在其中的恶意代码也被执⾏。
- 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者假冒⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。
反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库⾥,反射型 XSS 的恶意代码存在 URL ⾥。
反射型 XSS 破绽常⻅于通过 URL 传递参数的性能,如⽹站搜寻、跳转等。 因为须要⽤户被动关上歹意的 URL 能力⽣效,攻击者往往会联合多种⼿段诱导⽤户点击。
3)DOM 型 XSS 的攻打步骤:
- 攻击者结构出非凡的 URL,其中蕴含恶意代码。
- ⽤户关上带有恶意代码的 URL。
- ⽤户浏览器接管到响应后解析执⾏,前端 JavaScript 取出 URL 中的恶意代码并执⾏。
- 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者假冒⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。
DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻打中,取出和执⾏恶意代码由浏览器端实现,属于前端JavaScript ⾃身的安全漏洞,⽽其余两种 XSS 都属于服务端的安全漏洞。
写代码:实现函数可能深度克隆根本类型
浅克隆:
function shallowClone(obj) { let cloneObj = {}; for (let i in obj) { cloneObj[i] = obj[i]; } return cloneObj;}
深克隆:
- 思考根底类型
援用类型
- RegExp、Date、函数 不是 JSON 平安的
- 会失落 constructor,所有的构造函数都指向 Object
- 破解循环援用
function deepCopy(obj) { if (typeof obj === 'object') { var result = obj.constructor === Array ? [] : {}; for (var i in obj) { result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i]; } } else { var result = obj; } return result;}
数组扁平化
ES5 递归写法 —— isArray()、concat()
function flat11(arr) { var res = []; for (var i = 0; i < arr.length; i++) { if (Array.isArray(arr[i])) { res = res.concat(flat11(arr[i])); } else { res.push(arr[i]); } } return res;}
如果想实现第二个参数(指定“拉平”的层数),能够这样实现,前面的几种能够本人相似实现:
function flat(arr, level = 1) { var res = []; for(var i = 0; i < arr.length; i++) { if(Array.isArray(arr[i]) || level >= 1) { res = res.concat(flat(arr[i]), level - 1); } else { res.push(arr[i]); } } return res;}
ES6 递归写法 — reduce()、concat()、isArray()
function flat(arr) { return arr.reduce( (pre, cur) => pre.concat(Array.isArray(cur) ? flat(cur) : cur), [] );}
ES6 迭代写法 — 扩大运算符(...)、some()、concat()、isArray()
ES6 的扩大运算符(...) 只能扁平化一层
function flat(arr) { return [].concat(...arr);}
全副扁平化:遍历原数组,若arr
中含有数组则应用一次扩大运算符,直至没有为止。
function flat(arr) { while(arr.some(item => Array.isArray(item))) { arr = [].concat(...arr); } return arr;}
toString/join & split
调用数组的 toString()/join()
办法(它会主动扁平化解决),将数组变为字符串而后再用 split
宰割还原为数组。因为 split
宰割后造成的数组的每一项值为字符串,所以须要用一个map
办法遍历数组将其每一项转换为数值型。
function flat(arr){ return arr.toString().split(',').map(item => Number(item)); // return arr.join().split(',').map(item => Number(item));}
应用正则
JSON.stringify(arr).replace(/[|]/g, '')
会先将数组arr
序列化为字符串,而后应用 replace()
办法将字符串中所有的[
或 ]
替换成空字符,从而达到扁平化解决,此时的后果为 arr
不蕴含 []
的字符串。最初通过JSON.parse()
解析字符串。
function flat(arr) { return JSON.parse("[" + JSON.stringify(arr).replace(/\[|\]/g,'') + "]");}
类数组转化为数组
类数组是具备 length
属性,但不具备数组原型上的办法。常见的类数组有 arguments
、DOM 操作方法返回的后果(如document.querySelectorAll('div')
)等。
扩大运算符(...)
留神:扩大运算符只能作用于 iterable
对象,即领有 Symbol(Symbol.iterator)
属性值。
let arr = [...arrayLike]
Array.from()
let arr = Array.from(arrayLike);
Array.prototype.slice.call()
let arr = Array.prototype.slice.call(arrayLike);
Array.apply()
let arr = Array.apply(null, arrayLike);
concat + apply
let arr = Array.prototype.concat.apply([], arrayLike);