mutationobserver
Mutation Observer API 用来监视 DOM 变动。DOM 的任何变动,比如节点的增减、属性的变动、文本内容的变动,这个 API 都可以得到通知。
概念上,它很接近事件,可以理解为 DOM 发生变动就会触发 Mutation Observer 事件。但是,它与事件有一个本质不同:事件是同步触发,也就是说,DOM 的变动立刻会触发相应的事件;Mutation Observer 则是异步触发,DOM 的变动并不会马上触发,而是要等到当前所有 DOM 操作都结束才触发。
Mutation Observer 有以下特点。
- 它等待所有脚本任务完成后,才会运行(即异步触发方式)。
- 它把 DOM 变动记录封装成一个数组进行处理,而不是一条条个别处理 DOM 变动。
- 它既可以观察 DOM 的所有类型变动,也可以指定只观察某一类变动。
MutationObserver构造函数
通过new创建一个观察者对象,改构造函数需要传递一个回调函数。
var observer = new MutationObserver(callback);
该回调函数接收两个参数,参一:变动dom的数组,参二:观察者实例。
let callback = function (mutations, self) { }
实例方法:
1.observe(变动元素,观察选项)
let odiv = document.getElementById('#div'); let options = { 'childList': true, 'attributes':true } ; observer.observe(odiv, options);
以下是 options对象的各属性及其描述:
属性 | 类型 | 描述 |
childList | Boolean | 是否观察子节点的变动 |
attributes | Boolean | 是否观察属性的变动 |
characterData | Boolean | 是否节点内容或节点文本的变动 |
subtree | Boolean | 是否观察所有后代节点的变动 |
attributeOldValue | Boolean | 观察 attributes 变动时,是否记录变动前的属性值 |
characterDataOldValue | Boolean | 观察 characterData 变动时,是否记录变动前的属性值 |
attributeFilter | Array | 表示需要观察的特定属性(比如['class','src']),不在此数组中的属性变化时将被忽略 |
注意:
- 对一个节点添加观察器,就像使用
addEventListener
方法一样,多次添加同一个观察器是无效的,回调函数依然只会触发一次。但是,如果指定不同的options
对象,就会被当作两个不同的观察器。
- 监听选项中必须指定 childList、attributes 和 characterData 中的一种或多种。
Failed to execute 'observe' on 'MutationObserver': The options object must set at least one of 'attributes', 'characterData', or 'childList' to true.
2.disconnect()
该方法是停止监听dom变化的。
observer.disconnect();
3.takeRecords()
用来清除变动记录,即不再处理未处理的变动。该方法返回变动记录的数组。
// 保存所有没有被观察器处理的变动 observer.takeRecords();
MutationRecord 对象
即每次dom发生变化所产生的状态值。
属性 | 类型 | 描述 |
type | String | 根据变动类型,值为 attributes, characterData 或 childList |
target | Node | 发生变动的DOM节点 |
addedNodes | NodeList | 被添加的节点,或者为 null |
removedNodes | NodeList | 被删除的节点,或者为 null |
previousSibling | Node | 被添加或被删除的节点的前一个兄弟节点,或者为 null |
nextSibling | Node | 被添加或被删除的节点的后一个兄弟节点,或者为 null |
attributeName | String | 发生变更的属性的本地名称,或者为 null |
attributeNamespace | String | 发生变更的属性的命名空间,或者为 null |
oldValue | String | 如果 type 为 attributes,则返回该属性变化之前的属性值;如果 type 为 characterData,则返回该节点变化之前的文本数据;如果 type为 childList,则返回 null |
优化js中的逻辑判断
普通的写法:if-else if-else, switch-case
一元判断:Map, 对象
Map: [[条件, 逻辑参数],[条件, 逻辑参数],...]
对象: {条件:逻辑参数}
/** * 按钮点击事件 * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消 */ const onButtonClick = (status)=>{ if(status == 1){ sendLog('processing') jumpTo('IndexPage') }else if(status == 2){ sendLog('fail') jumpTo('FailPage') }else if(status == 3){ sendLog('fail') jumpTo('FailPage') }else if(status == 4){ sendLog('success') jumpTo('SuccessPage') }else if(status == 5){ sendLog('cancel') jumpTo('CancelPage') }else { sendLog('other') jumpTo('Index') } }
const actions = { '1': ['processing','IndexPage'], '2': ['fail','FailPage'], '3': ['fail','FailPage'], '4': ['success','SuccessPage'], '5': ['cancel','CancelPage'], 'default': ['other','Index'], } /** * 按钮点击事件 * @param {number} status 活动状态:1开团进行中 2开团失败 3 商品售罄 4 开团成功 5 系统取消 */ const onButtonClick = (status)=>{ let action = actions[status] || actions['default'], logName = action[0], pageName = action[1] sendLog(logName) jumpTo(pageName) }
const actions = new Map([ [1, ['processing','IndexPage']], [2, ['fail','FailPage']], [3, ['fail','FailPage']], [4, ['success','SuccessPage']], [5, ['cancel','CancelPage']], ['default', ['other','Index']] ]) /** * 按钮点击事件 * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消 */ const onButtonClick = (status)=>{ let action = actions.get(status) || actions.get('default') sendLog(action[0]) jumpTo(action[1]) }
多层判断:Map
Map: [[条件,() => {}], [条件,() => {}], ....]
- 条件可以是多个条件合并的字符串
- 条件可以是一个对象
- 条件可以是正则,为了匹配更多相同逻辑不同条件的处理
- .....
对象:{条件:() => {}, 条件:() => {}, ...}
- 条件可以是多个条件合并的字符串
/** * 按钮点击事件 * @param {number} status 活动状态:1开团进行中 2开团失败 3 开团成功 4 商品售罄 5 有库存未开团 * @param {string} identity 身份标识:guest客态 master主态 */ const onButtonClick = (status,identity)=>{ if(identity == 'guest'){ if(status == 1){ //do sth }else if(status == 2){ //do sth }else if(status == 3){ //do sth }else if(status == 4){ //do sth }else if(status == 5){ //do sth }else { //do sth } }else if(identity == 'master') { if(status == 1){ //do sth }else if(status == 2){ //do sth }else if(status == 3){ //do sth }else if(status == 4){ //do sth }else if(status == 5){ //do sth }else { //do sth } } }
const actions = new Map([ ['guest_1', ()=>{/*do sth*/}], ['guest_2', ()=>{/*do sth*/}], ['guest_3', ()=>{/*do sth*/}], ['guest_4', ()=>{/*do sth*/}], ['guest_5', ()=>{/*do sth*/}], ['master_1', ()=>{/*do sth*/}], ['master_2', ()=>{/*do sth*/}], ['master_3', ()=>{/*do sth*/}], ['master_4', ()=>{/*do sth*/}], ['master_5', ()=>{/*do sth*/}], ['default', ()=>{/*do sth*/}], ]) /** * 按钮点击事件 * @param {string} identity 身份标识:guest客态 master主态 * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 开团成功 4 商品售罄 5 有库存未开团 */ const onButtonClick = (identity,status)=>{ let action = actions.get(`${identity}_${status}`) || actions.get('default') action.call(this) }
const actions = { 'guest_1':()=>{/*do sth*/}, 'guest_2':()=>{/*do sth*/}, //.... } const onButtonClick = (identity,status)=>{ let action = actions[`${identity}_${status}`] || actions['default'] action.call(this) }
const actions = new Map([ [{identity:'guest',status:1},()=>{/*do sth*/}], [{identity:'guest',status:2},()=>{/*do sth*/}], //... ]) const onButtonClick = (identity,status)=>{ // ...actions表示将[[],[],[]]内部的数组取出[],[],[] let action = [...actions].filter(([key,value])=>(key.identity == identity && key.status == status)) action.forEach(([key,value])=>value.call(this)) }
const actions = ()=>{ const functionA = ()=>{/*do sth*/} const functionB = ()=>{/*do sth*/} return new Map([ [{identity:'guest',status:1},functionA], [{identity:'guest',status:2},functionA], [{identity:'guest',status:3},functionA], [{identity:'guest',status:4},functionA], [{identity:'guest',status:5},functionB], //... ]) } const onButtonClick = (identity,status)=>{ let action = [...actions()].filter(([key,value])=>(key.identity == identity && key.status == status)) action.forEach(([key,value])=>value.call(this)) }
const actions = ()=>{ const functionA = ()=>{/*do sth*/} const functionB = ()=>{/*do sth*/} const functionC = ()=>{/*send log*/} return new Map([ [/^guest_[1-4]$/,functionA], [/^guest_5$/,functionB], [/^guest_.*$/,functionC], //... ]) } const onButtonClick = (identity,status)=>{ let action = [...actions()].filter(([key,value])=>(key.test(`${identity}_${status}`))) action.forEach(([key,value])=>value.call(this)) }
学习自 掘金Think.大佬
promise
常用的静态方法:all,race,resolve, reject。
常用的实例方法:then, catch。
对于all方法来说,参数是一个若干个promise组成的数组,如果这些promise定义了自己的then,catch方法,并且有返回值,不管是成功还是失败,那么执行all方法的then就会执行,catch就不会执行。
const p1 = new Promise((resolve, reject) => { resolve('hello'); }) .then(res => res) .catch(e => e); const p2 = new Promise((resolve, reject) => { throw new Error('报错了'); }) .then(result => result) .catch();//这里没有返回值,才会执行all的catch方法。 Promise.all([p1, p2]) .then(result => console.log(result)) .catch(e => console.log(e)); //执行结果 Error: 报错了
axios对其还做了一步简化。将接收到的请求参数封装成一个函数接收spread()
function getUserAccount() { return axios.get('/user/12345'); } function getUserPermissions() { return axios.get('/user/12345/permissions'); } axios.all([getUserAccount(), getUserPermissions()]) .then(axios.spread(function (acct, perms) { // 两个请求现在都执行完成 }));
promise只执行一次,但是同一个promise的then,catch可以被执行多次。
const promise = new Promise((resolve, reject) => { setTimeout(() => { console.log('once') resolve('success') }, 1000) }) const start = Date.now() promise.then((res) => { console.log(res, Date.now() - start) }) promise.then((res) => { console.log(res, Date.now() - start) }) //只会打印出一次once
.then
或者 .catch
的参数期望是函数,传入非函数则会发生值穿透。
Promise.resolve(1) .then(2) .then(Promise.resolve(3)) .then(console.log)
.then
抛出的错误不会被第二个参数函数捕获,只能被后面的catch捕获。
Promise.resolve() .then(function success (res) { //注意这里是抛出错误,而非返回错误。 throw new Error('error') }, function fail1 (e) { console.error('fail1: ', e) }) .catch(function fail2 (e) { console.error('fail2: ', e) })
手写promise
class Promise { constructor(executor) {//执行器函数 let _this = this _this.status = 'pending' //保存当前状态 _this.value = undefined //执行成功时,传递的值 _this.error = undefined//执行失败时,传递的值 function resolve(value) { if (_this.status === 'pending') { _this.status = 'resolved' _this.value = value } } function reject(error) { if (_this.status === 'pending') { _this.status = 'rejected' _this.error = error } } executor(resolve, reject)//调用执行器函数 } then(onfulfilled, onRejected) { let _this = this if (_this.status === 'resolved') { onfulfilled(_this.value) } else if (_this.status === 'rejected') { onRejected(_this.error) } } } //测试,此代码的问题是不能实现异步方法 let promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('我是张昊') reject('我是李龙淼') }, 1000) }).then(res => { console.log(res) }, res => { console.log(res) }) //这里什么也不输出
原因是我们在then函数中只对成功态和失败态进行了判断,而实例被new时,执行器中的代码会立即执行,但setTimeout中的代码将稍后执行,也就是说,then方法执行时,Promise的状态没有被改变依然是pending态,所以我们要对pending态也做判断,而由于代码可能是异步的,那么我们就要想办法把回调函数进行缓存,并且,then方法是可以多次使用的,所以要能存多个回调,那么这里我们用一个数组。
创建两个数组,为了存储成功的回调(resolve)和失败的回调(reject)
_this.onResolvedCallbacks = []; // 存放then成功的回调,这里存的都是resolve()的函数 _this.onRejectedCallbacks = []; // 存放then失败的回调,这里存的都是reject()的函数
在then中判断pending的判断,将then中的回调分别存入onResolvedCallbacks
和onRejectedCallbacks
数组中
if (_this.status === 'pending') { // 每一次then时,如果是等待态,就把回调函数push进数组中,什么时候改变状态什么时候再执行 //缓存成功时的回调函数,等到状态变为resolved的时候,在执行 _this.onResolvedCallbacks.push(function () { onfulfilled(_this.value) }) //缓存失败时的回调函数,等到状态变为rejected的时候,在执行 _this.onRejectedCallbacks.push(function () { onRejectedCallbacks(_this.error) }) }
在resolve方法中缓存异步执行的成功回调,在reject方法中缓存异步执行的失败的回调
function resolve(value) { if (_this.status === 'pending') { _this.status = 'resolved' _this.value = value // 当成功的函数 (resolve()) 被调用时,之前缓存的回调函数会被一一调用 _this.onResolvedCallbacks.map(callback => callback()) } } function reject(error) { if (_this.status === 'pending') { _this.status = 'rejected' _this.error = error // 当失败的函数 (reject()) 被调用时,之前缓存的回调函数会被一一调用 _this.onRejectedCallbacks.map(callback => callback()) } }
为了防止promise在执行出现错误,我们需要做错误处理
try { executor(resolve, reject) } catch (e) { console.log(e) }
promise的链式调用。由于promise的状态会发生改变,所以then返回的promise是一个全新的。
有点复杂!!! promise的其他原型方法
// 捕获错误的方法,在原型上有catch方法,返回一个没有resolve的then结果即可 Promise.prototype.catch = function (callback) { return this.then(null, callback) } // 解析全部方法,接收一个Promise数组promises,返回新的Promise,遍历数组,都完成再resolve Promise.all = function (promises) { //promises是一个promise的数组 return new Promise(function (resolve, reject) { let arr = []; //arr是最终返回值的结果 let i = 0; // 表示成功了多少次 function processData(index, y) { arr[index] = y; if (++i === promises.length) { resolve(arr); } } for (let i = 0; i < promises.length; i++) { promises[i].then(function (y) { processData(i, y) }, reject) } }) } // 只要有一个promise成功了 就算成功。如果第一个失败了就失败了 Promise.race = function (promises) { return new Promise(function (resolve, reject) { for (var i = 0; i < promises.length; i++) { promises[i].then(resolve,reject) } }) } // 生成一个成功的promise Promise.resolve = function(value){ return new Promise(function(resolve,reject){ resolve(value); }) } // 生成一个失败的promise Promise.reject = function(error){ return new Promise(function(resolve,reject){ reject(error); }) }
学习自
前端的路由跳转
Hash 方法是在路由中带有一个 #
,主要原理是通过监听 #
后的 URL 路径标识符的更改而触发的浏览器 hashchange
事件,然后通过获取 location.hash
得到当前的路径标识符,再进行一些路由跳转的操作。
class RouterClass { constructor() { this.isBack = false this.routes = {} // 记录路径标识符对应的cb this.currentUrl = '' // 记录hash只为方便执行cb this.historyStack = [] // hash栈 window.addEventListener('load', () => this.render()) window.addEventListener('hashchange', () => this.render()) } /* 初始化 */ static init() { window.Router = new RouterClass() } /* 记录path对应cb */ route(path, cb) { this.routes[path] = cb || function() {} } /* 入栈当前hash,执行cb */ render() { if (this.isBack) { // 如果是由backoff进入,则置false之后return this.isBack = false // 其他操作在backoff方法中已经做了 return } this.currentUrl = location.hash.slice(1) || '/' //将每一个路径都加入到栈中,为了back的判断 this.historyStack.push(this.currentUrl) this.routes[this.currentUrl]() } /* 路由后退 */ back() { this.isBack = true this.historyStack.pop() // 移除当前hash,回退到上一个 const { length } = this.historyStack if (!length) return //如果栈中没有路径了,将直接结束 let prev = this.historyStack[length - 1] // 拿到要回退到的目标hash location.hash = `#${ prev }` //为了使手动跳转正常进行,需要将当前路径加上一个#,来满足slice的分割 this.currentUrl = prev this.routes[prev]() // 执行对应cb } }
history
history.go(n)
:路由跳转,比如n为2
是往前移动2个页面,n为-2
是向后移动2个页面,n为0是刷新页面
history.back()
:路由后退,相当于history.go(-1)
history.forward()
:路由前进,相当于history.go(1)
history.pushState()
:添加一条路由历史记录,如果设置跨域网址则报错,浏览器有记录。
history.replaceState()
:替换当前页在路由历史记录的信息,浏览器无记录。
popstate
事件:当活动的历史记录发生变化,就会触发popstate
事件,在点击浏览器的前进后退按钮或者调用上面前三个方法的时候也会触发,参见 MDN
何为闭包?
闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。环境里是若干对符号和值的对应关系,它既要包括约束变量(该函数内部绑定的符号),也要包括自由变量(在函数外部定义但在函数内被引用),有些函数也可能没有自由变量。闭包跟函数最大的不同在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。捕捉时对于值的处理可以是值拷贝,也可以是名称引用。
而闭包则意味着同时包括函数指针和环境两个关键元素。在编译优化当中,没有捕捉自由变量的闭包可以被优化成普通函数。
闭包中引入的变量何时被销毁?
闭包中访问的外部变量是存放在堆内存中的。
变量的生命周期取决于闭包的生命周期。被闭包引用的外部作用域中的变量将一直存活直到闭包函数被销毁。如果一个变量被多个闭包所引用,那么直到所有的闭包被垃圾回收后,该变量才会被销毁。
数组方法(存在高阶函数)的返回值
map:该方法的返回值就是回调函数的返回值组成的数组。
filter: 该方法的返回值就是回调函数符合条件的返回值组成的数组。
let arr = [1, 2, 3, 4] let resArr = arr.filter(item => { if (item > 2) { return item } }) //等价于 arr.filter(item => item > 2) console.log(resArr)
forEach:该方法没有返回值。
reduce:该方法返回的值是累计器(回调函数的第一个参数)累计后的值。
let arr = [ { id: 1, name: 'zh', age: 20 }, { id: 2, name: 'hy', age: 19 }, { id: 3, name: 'llm', age: 19 } ] let reArr = arr.reduce((pre, next) => { if (next.age === 19) { return pre.concat(Object.assign({}, next, { sex: 'female' })) } return []//这里必须有返回值,不然会报错,因为每遍历一项都需要有返回值。如果上面用到了pre,下面没有返回就会报错,pre的值是每次回调函数返回的值。 }, []) console.log(reArr) // {id: 2, name: "hy", age: 19, sex: "female"} // {id: 3, name: "llm", age: 19, sex: "female"}
every: 该方法的返回值是布尔值,只有数组中的元素都满足条件,才回返回true,否则返回false。
let arr = [1, 2, 3, 4] let resArr = arr.every(item => item >= 1) console.log(resArr)
some:该方法返回的是布尔值,表示只要数组中有一个元素满足条件就返回true,否则返回false。
find:该方法返回的是第一个满足条件的值。
findIndex:该方法返回的是第一个满足条件的值的下标。
URLsearchParams
URLsearchParams(url)用来解析url参数的。
//URLSearchParams用来解析参数的 let url = '?name=zh&age=20'; let searchParams = new URLSearchParams(url) // console.log(searchParams)//map对象 let arr = [...searchParams] console.log(arr) //[ [ 'name', 'zh' ], [ 'age', '20' ] ]
获取单个参数。
searchParams.get('name')
校验参数是否存在。
searchParams.has('sex') // false searchParams.has('age') // true
添加参数。
searchParams.append('sex', 'male') console.log(searchParams)//URLSearchParams { 'name' => 'zh', 'age' => '20', 'sex' => 'male' } console.log(url)//?name=zh&age=20,注意url并不会添加上该参数,但是解析后是有该参数的
删除参数。
searchParams.delete('sex'); searchParams.has('sex'); // false
修改参数。
searchParams.set('age', 22) console.log(searchParams)//URLSearchParams { 'name' => 'zh', 'age' => '22', 'sex' => 'male' }
将解析后的参数,再转为查询字符串。
searchParams.toString() //name=zh&age=22&sex=male
重写数组方法
重写map方法,利用for循环
const selfMap = function (fn, context = this) { //这里的context会被当做fn函数中的this,如果不传入context,那么this就是调用的数组。 let arr = Array.prototype.slice.call(context) let mappedArr = Array() for (let i = 0; i < arr.length; i++) { //判断稀疏数组的情况 if (!arr.hasOwnProperty(i)) continue; mappedArr[i] = fn.call(context, arr[i], i, this) // console.log(context) } return mappedArr } Array.prototype.selfMap = selfMap let resArr = [0, 0, 0, 1].selfMap(number => number * 2, [2, 3, 4]) console.log(resArr)//[4,6,8]
我对map方法的错误理解,context只是改变了this指向,然而并不是改变函数执行的数组。
作者大大,第二个方法,即map方法,我感觉应该把context初始为this,然后slice方法中传入context,这样传入第二个参数的时候,this的值才回改变。不然没有效果 let resArr = [0, 0, 0, 1].selfMap(number => number * 2, [2, 3, 4]) console.log(resArr)//这里仍然是[0,0,0,2],而不是[4,6,8] //下面这样就是对的 const selfMap = function (fn, context = this) { //这里的context会被当做fn函数中的this let arr = Array.prototype.slice.call(context) let mappedArr = Array() for (let i = 0; i number * 2, [2, 3, 4]) console.log(resArr)//[4,6,8] 如果不对,望告知。
这里是js原生的map测试
let resArr = [4, 5, 6].map(function (item, index, arr1) { console.log(arr1)//[4,5,6] console.log('this', this)//[1,2,3] return item * 2//作用的是[4,5,6] }, [1, 2, 3]) console.log(resArr)
重写map方法。利用reduce方法。
const selfMap = function (fn, context) { let arr = Array.prototype.slice.call(this) return arr.reduce((pre, cur, index) => { //这个返回值就是pre,然后将完全处理后的元素,都返回给pre,然后展开pre即可。 //也就是这里我有一个疑问,他每次都返回pre,每次都展开pre,每次展开都会重复上一次返回的pre中的元素呀,所以应该会重复很多呀。 return [...pre, fn.call(context, cur, index, this)] }, []) } Array.prototype.selfMap = selfMap let resArr = [1, 2, 3].selfMap(item => item * 2) console.log(resArr)
解决疑惑:因为数组的内存地址都是一样的,pre的内存地址一样的,只是展开了最后一次赋值的值。
let a = new Array(10) let resArr = [] for (let i = 0; i <= a.length; i++) { let arr = [i] resArr = [...arr, 2, 3, 4] } console.log(resArr)//[ 10, 2, 3, 4 ]
重写filter方法,利用for循环。
const selfFilter = function (fn, context) { //要处理的数组 let arr = Array.prototype.slice.call(this) let resArr = Array() for (let i = 0; i < arr.length; i++) { //判断返回的条件是否正确,并且加入到数组中。 fn.call(context, arr[i], i, this) && resArr.push(arr[i]) } return resArr } Array.prototype.selfFilter = selfFilter let arr = [1, 2, 3] let arr1 = arr.selfFilter(item => item > 2) console.log(arr1)
重写filter,利用reduce。
const selfFilter = function (fn, context) { let arr = Array.prototype.slice.call(this) let resArr = [] return arr.reduce((pre, cur, index) => { // if (fn.call(context, cur, index, this)) { // resArr.push(cur) // return resArr // } return fn.call(context, cur, index, this) ? [...pre, cur] : [...pre] }, []) } Array.prototype.selfFilter = selfFilter let arr = [0, 2, 3] let arr1 = arr.selfFilter(item => item > 2) console.log(arr1)
重写some,利用for循环。
const someFilter = function (fn, context) { let arr = Array.prototype.slice.call(this) for (let i = 0; i < arr.length; i++) { if (fn.call(context, arr[i], i, this)) return true } return false } Array.prototype.someFilter = someFilter let arr = [1, 2, 3] let arr1 = arr.someFilter(item => item > 2) console.log(arr1)
重写flat方法,利用reduce。
const selfFlat = function (depth = 1) { let arr = Array.prototype.slice.call(this) // let resArr = [] if (depth === 0) return arr return reduce((pre, cur) => { if (Array.isArray(cur)) { //反正就是递归 return [...pre, ...selfFlat.call(cur, depth - 1)] } else { return [...pre, cur] } }, []) }
es6的面向对象class语法
ES6 的 class 内部是基于寄生组合式继承
function inherit(subType, superType) { subType.prototype = Object.create(superType.prototype, { constructor: { value: subType, enumerable: false, configurable: true, writable: true } }) // 让类的静态方法也可以被继承。 Object.setPrototypeOf(subType, superType) }