/**
* @description 将数组平铺到指定的深度
*/
const flatten = (arr, depth = 1) =>
depth != 1 ? arr.reduce((a, v) => a.concat( Array.isArray(v) ? flatten(v, depth - 1) : v), [])
: arr.reduce((a, v) => a.concat(v), [])
var flat = (arr) => {
return arr.flat(Infinity)
}
/**
* 递归 flat
*/
const flat = (arr) => {
let result = []
function each(arr) {
arr.forEach(item => {
if (item instanceof Array) {
each(item)
} else {
result.push(item)
}
})
}
each(arr)
return result.join(',')
}
/**
* 做隐式类型转换 flat
*/
/**
* 原理:数组 + 加号是双目运算符,
* arr是数组,不是基本类型也不是数字,是一个对象,
* 调对象的时候,会调arr的valueof,
* 如果valueof返回的还不是基本类型,会调toString方法
* 数组的vauleOf返回的就是数组本身,所以会调toString方法
* 如果toString方法返回基本类型,到此为止,否则就会报错
*
*/
const flatten = function (arr) {
let toString = Array.prototype.toString
Array.prototype.toString = function() {
return this.join(',')
}
let result = arr + ''
Array.prototype.toString = toString
return result
}
// 所以 改valueOf 也是可以的
const flatten = function (arr) {
let value = Array.prototype.valueOf
Array.prototype.valueOf = function() {
return this.join(',')
}
let result = arr + ''
Array.prototype.valueOf = value
return result
}
/**
* es6中的遍历器iterator是对自定义的复杂数据结构,提供遍历的方法
* 如果想给这个数据结构增加一个遍历器,必须
* 1. Symbol.iterator为key
* 2. value为 function
* 3. function 要求必须返回一个对象
* 4. 这个对象中必须有next方法
*
*/
Array.prototype[Symbol.iterator] = function() {
let arr = [].concat(this) // [1, [2, [3, [4, 5], 6], 7], 8] 每一次都会调一次next方法
let getFirst = function(array) {
let first = array.shift()
return first
}
return {
next: function() {
let item = getFirst(arr) // 1, 第二个[2, [3, [4, 5], 6]
if (item) { // 1 是真,标识下面还有值
return {
value: item, // 遍历时,当前要返回的值, 当第二次是一个数组的时候,这里有一个隐式类型转换,转成toString了 "2,3,4,5,6"
done: false // 是不是已经遍历结束
}
} else {
return {
done: true
}
}
}
}
}
var flat = function(arr) {
let r = []
for (let i of arr) {
r.push(i)
}
return r.join(',')
}
// flat([1, [2, [3, [4, 5], 6], 7], 8]);
/**
* @description 使用reduce取代map和filter 更新数组每一项,并筛选部分
*/
const filterSomeValue = arr => arr.reduce((a, v) => {
v = v * 2
if (v > 50) {
a.push(v)
}
return a
}, [])
/**
* @description 取整 | 0
*/
const getInt = num => num | 0
/**
* @description 单例模式 IIFE方式加闭包 本质是通过函数作用域的方式来隐藏内部作用域的变量
*/
const Singleton = (function() {
let _instance = null // 存储单例
const Singleton = function() {
if (_instance) return _instance // 判断是否已经有单例
_instance = this
this.init() // 初始化
return _instance
}
Singleton.prototype.init = function() {
this.foo = 'Singeton Pattern'
}
Singleton.getInstance = function() {
if (_instance) return _instance
_instance = new Singleton()
return _instance
}
return Singleton
})()
const vistor1 = new Singleton()
const vistor2 = new Singleton()
const vistor3 = new Singleton.getInstance()
console.log(vistor1 === vistor3) // true
/**
* @description 单例模式 ES6的块级作用域 目的是为了 _instance隐藏内部变量
*/
let getInstance
{
let _instance = null
const Singleton = function() {
if (_instance) return _instance // 判断是否已经有单例
_instance = this
this.init() // 初始化
return _instance
}
Singleton.prototype.init = function() {
this.foo = 'Singeton Pattern'
}
getInstance = function() {
if (_instance) return _instance
_instance = new Singleton()
return _instance
}
}
const vistor1 = getInstance()
const vistor2 = getInstance()
/**
* @description 单例 抽离 创建 和 功能逻辑
*/
class FuncClass {
constructor(bar) {
this.bar = bar
this.init()
}
init() {
this.foo = 'Single Pattern'
}
}
const Singleton = (function() {
let _instance = null
const ProxySingleton = function(bar) {
if (_instance) return _instance
_instance = new FuncClass(bar)
return _instance
}
ProxySingleton.getInstance = function(bar) {
if (_instance) return _instance
_instance = new Singleton(bar)
return _instance
}
return ProxySingleton
})()
const vistor1 = new Singleton('单例1')
const vistor2 = new Singleton('单例2')
const vistor3 = Singleton.getInstance()
/**
* @description 单例模式的应用 element-ui 的 loading
*/
import Vue from 'vue'
import loadingVue from './loading.vue // 自己写的Loading组件
const LoadingConstructor = Vue.extend(loadingVue)
let fullscreenLoading
const Loading = (options = {}) => {
if (options.fullscreen && fullscreenLoading) { // 如果之前创建国,直接返回
return fullscreenLoading
}
let _instance = new LoadingConstructor({
el: document.createElement('div'),
data: options
})
if (options.fullscreen) {
fullscreenLoading = _instance
}
return _instance
}
export default Loading
/**
* 设计模式-工厂函数 根据不同的输入返回不同类的实例 主要思想是对象的创建和实现分离
*/
class Restaurant {
static getMenu(menu) {
switch (menu) {
case '鱼香肉丝':
return new YuXiangRouSi()
case '宫保鸡丁':
return new GongBaoJiDin()
default:
throw new Error('本店没有')
}
}
}
class YuXiangRouSi {
constructor () {
this.type = '鱼香肉丝'
}
eat() {
console.log(this.type + '好吃~')
}
}
class GongBaoJiDin {
constructor () {
this.type = '宫保鸡丁'
}
eat() {
console.log(this.type + '好吃~')
}
}
const dish1 = Restaurant.getMenu('鱼香肉丝')
const dish2 = Restaurant.getMenu('宫保鸡丁')
/**
* @description Vue 源码中的工厂模式 虚拟DOM树机制 createElement生成VNode 用来映射真是DOM节点
*/
createElement('h3', { class: 'main-title' }, [
createElement('img', { class: 'avatar', attrs: { src: './avatar.png' } }),
createElement('p', { class: 'user-desc' }, '我要的飞翔,不是谁的肩膀')
])
// createElement:
class Vnode(tag, data. children) {
// ...
}
function createElement(tag, data, children) {
return new Vnode(tag, data, children)
}
/**
* @description 抽象工厂模式
*/
// 饭店方法
class Restaurant {
static orderDish(type) {
switch (type) {
case '鱼香肉丝':
return new YuXiangRouSi()
case '宫保鸡丁':
return new GongBaoJiDin()
default:
throw new Error('本店没有~')
}
}
}
// 菜品抽象类
class Dish { // 父类
constructor() {
if (new.target === Dish) {
throw new Error('抽象类不能直接实例化')
}
this.kind = '菜'
}
// 抽象方法 相当于定义一个接口,由子类继承
eat() {
throw new Error('抽象方法不能被调用')
}
}
// 菜品 继承菜品抽象类
class YuXiangRouSi extends Dish {
constructor() {
super()
this.type = '鱼香肉丝'
}
eat() {
console.log(this.kind, this.type, '鱼香肉丝')
}
}
class GongBaoJiDin extends Dish {
constructor() {
super()
this.type = '宫保鸡丁'
}
eat() {
console.log(this.kind, this.type, '宫保鸡丁')
}
}
const dish0 = new Dish() // Error 抽象类不能直接实例化
const dish1 = Restaurant.orderDish('鱼香肉丝')
dish1.eat()
/**
* 林衣踩在一个板凳上,左手拿着颜料,右手拿着画笔,给自己的家门口写上四个大字,艺术设计
* 结束之后,满意的看着自己的作品,含笑打开房门,看着自己屋里的格局,左边窗户上挂着一把她从上个世纪
* 拿回来的剑,
* 小黄此时在她脚底下转圈,她把目光放到桌上的一个工厂模型,这是她准备去的地方,那里有她这个时代要找的人,
* 传说中可以拯救末世的人。
*
*
*/
/**
* @description 建造者模式 分布构建一个复杂对象,并允许韩兆步骤构造
* 如: 汽车, 笔记本, 很多部件都由零件制造商制造 指挥长决定如何组装
*/
/**
* @description 代理模式,又称为委托模式,他为目标对象创造了一个代理对象,以控制对目标对象的访问
* Visitor -----------> Proxy --------------> Target
* Visitor <----------- Proxy <-------------- Target
* 如 axios 拦截器, 代理模式和装饰者模式很像,但是代理模式是会对目标对象的访问进行控制,axios拦截器可以取消
* 请求,而装饰者模式是在目标对象上添加新的功能
*
* vue2.x 通过劫持各个属性的 getter 和 setter ,在数据变化时,通过发布订阅模式发布消息给订阅者
* 触发相应的监听回调,实现数据响应式
* vu2.x 的缺点:
* 1. 无法监听利用索引直接设置数组的项
* 2. 无法监听数组的长度变化
* 3. 无法监听ES6的 Set, WeakSet, Map, WeakMap 的变化
* 4. 无法监听Class 类型的数据
* 5. 无法监听对象属性的新增或者删除
*/
// const proxy = new Proxy(target, handler)
const SuperStar = {
name: '小鲜肉',
scheduleFlag: false, // 档期标识 默认没空
playAdvertisement(ad) {
console.log(ad)
}
}
const ProxyAssistant = {
name: '经纪人张某',
scheduleTime(ad) {
const schedule = new Proxy(SuperStar, {
set(obj, prop, val) {
if (prop !== prop.scheduleFlag) return
if (!obj.scheduleFlag && val) {
obj.scheduleFlag = true
obj.playAdvertisement(ad) // 安排
}
}
})
setTimeout(() => {
console.log('小鲜鲜有空了')
schedule.scheduleFlag = true
}, 2000)
},
playAdvertisement(reward, ad) {
if (reward > 1000000) {
console.log('没问题,我们小鲜鲜最喜欢拍广告了')
ProxyAssistant.scheduleTime(ad)
} else {
console.log('没空,滚')
}
}
}
ProxyAssistant.playAdvertisement(10000, '广告')
/**
* @description 享元模式 公用某些资源
*/
let examCardNum = 0 // 驾考车总数
class ExamCar {
constructor(carType) {
examCardNum++
this.cardId = examCardNum
this.carType = carType ? '手动挡' : '自动挡'
this.usingState = false // 是否正在占用
}
// 在本车上考试
examine(candidateId) {
return new Promise(resolve => {
this.usingState = true
console.log(`考生 ${candidateId} 在 ${this.carType}驾考车-${this.cardId}上考试`)
setTimeout(() => { // 0 - 2s 考完后
this.usingState = false
resolve()
}, Math.random() * 2000)
})
}
}
// 手动挡汽车对象池
ManualExamCarPool = {
_pool: [], // 驾考车对象池
_candidateQueue: [], // 考生队列
// 注册考生 ID 列表
registCandidates(candidateList) {
candidateList.forEach(candidateId => this.registCandidate(candidateId))
},
// 注册手动挡考生
registCandidate(candidateId) {
const examCar = this.getManualExamCar() // 找一个未被占用的手动挡驾考车
if (examCar) {
examCar.examine(candidateId) // 开始考试, 考完了让队列中的下一个考生开始考试
.then(() => {
const nextCandidateId = this._candidateQueue.length && this._candidateQueue.shift()
nextCandidateId && this.registCandidate(nextCandidateId)
})
} else { // 如果没有空车,将考生推入队列
this._candidateQueue.push(candidateId)
}
},
// 注册手动挡车
initManualExamCar(manualExamCarNum) {
for(let i = 0; i <= manualExamCarNum; i++) {
this._pool.push(new ExamCar(true))
}
},
// 获取状态为未被占用的手动挡车
getManualExamCar() {
return this._pool.find(car => !car.usingState)
}
}
ManualExamCarPool.initManualExamCar(3) // 一共三辆考车
ManualExamCarPool.registCandidates([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) // 10个考生来考试
// 数据库连接池
const mysql = require('mysql')
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
database: 'mydb',
port: '3306'
})
// 连接回调,在回调中增删改查
connection.connect()
// 关闭连接
connection.end()
// 连接池的创建
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: '123456',
database: 'mydb',
port: '3306'
})
pool.getConnection((err, connection) => {
// 数据库操作
connection.release() // 将连接释放回连接池中
})
// 关闭连接池
pool.end()
/**
* @description REST 以及 6个限制
* REST是一种风格,万维网网络架构风格 Representational State Transfer
* Representational 表示,数据的表现形式 如json xml
* State 状态 当前的状态 可变的状态
* Transfer 传输 数据在互联网上传输
*
* 6 个限制
* 1. 客户端--服务器(Client-Server) CS架构
* 服务端专注数据存储,提升简单性 ---形容词来自于REST作者的博士论文
* 前段转出用户界面,提升可移植性
* 2. 无状态
* 所有的用户会话信息都保存在客户端
* 每次请求必须包含所有信息,不能依赖上下文信息
* 服务端不用保存会话信息,提升了简单性,可靠性,可见性(每次请求要包含很多信息)
* 3. 缓存(Cache)
* 所有服务端响应都要被表位可缓存或不可缓存
* 如 静态资源,至少在一个版本内不会变
* 减少交互,提升速度
* 4. 统一接口 (Uniform Interface)
* 接口与实现解耦,前后端可以独立开发迭代
* 5. 分层系统(Layered System)
* 每层只知道相邻的一层,后面隐藏的就不知道了
* 客户端不知道是和代理还是很真实服务器通信
* 中间件,不用关心业务逻辑,只要做系统中的分层
* 其它层:安全层, 负载均衡(分发流量),缓存层
* 6. 按需代码(code on Demand 可选)
* 客户端可以下载运行服务端传来的代码(如, js)
* eval() 可以执行
* 简化客户端
*
*
* 统一接口 的限制:
* 1. 资源的标识
* 资源是任何可以命名的事物,如,用户,评论
* 每个资源可以通过URI被唯一标识 URI 是统一资源标识符,URI最常见的形式是统一资源定位符,URL
* 2. 通过表述来操作资源
* 表述 如 JSON XML 传给服务端,服务端来操作
* 3. 自描述信息
* 媒体类型(application/json)
* HTTP方法
* 是否缓存 Cache-Control
*
*
* 控制器的本质是中间件,中间件的本质是函数
*
* koa 断点调试
* 在vscode中在要调试的文件界面按下F5就启动了
*
* 错误处理
* 1. 运行时错误,语法没有错误的前提下,返回500
* 2. 逻辑错误,找不到404
* 先决条件失败402 (请求某个ID不存在)
* 无法处理的实体 422 (参数格式不对)
* 4开头的大多是逻辑错误
*
* 防止程序挂掉,try catch
* 告诉用户错误信息
* 便于开发调试
*
* koa自带的错误处理
* koa找不到的情况下默认返回404
*
*
*/
// 123456789876543212345678987654321
const getNum = n => {
let num = 0
let flag = true
for(let i = 0; i <= n; i++) {
if (num == 1) flag = true
if (num == 9) flag = false
flag ? num++ : num--
}
return num
}
// promise 用法
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('fullfilled')
}, 1000)
})
/**
* 构造函数,必须接受一个函数作为参数,函数又接收 resolve, reject 两个参数,也是函数
* 1. 必须是函数
* 2. 状态值
* promise 对象存在三种状态
* - Pending 进行中
* - Fullfilled 已成功
* - Rejected 已失败
* 状态只能由 Pending 变为Fullfilled 或者 Pending 变为 Rejected
* 且状态改变之后不会发生变化,会一直保存这个状态
* Promise 的值是指状态改变时,传给回调函数的值
*/
// 判断变量是否为函数
const isFunction = val => typeof(val) === 'function'
// 定义三个常量 标记Promise对象的三种状态和值
const PENDING = 'PENDING'
const FULLFILLED = 'FULLFILLED'
const REJECTED = 'REJECTED'
// 定义我们自己的 promise
class MyPromise {
constructor(exector) {
if (!isFunction(exector)) {
throw new Error('MyPromise must accept a function as a parameter')
}
// 添加状态
this._status = PENDING
this._value = undefined
// 添加成功回调函数队列
this._fullfilledQueues = []
// 添加失败回调函数队列
this._rejectedQueues = []
try {
exector(this._resolve.bind(this), this._reject.bind(this))
} catch (err) {
this._reject(err)
}
}
// 添加resolve时执行的函数
_resolve(val) {
if (this._status !== PENDING) return
// 依次执行成功队列中的函数
const run = () => {
this._status = FULLFILLED
this._value = val
this._fullfilledQueues.map(cb => cb())
}
// 为了支持同步的Promise 这里采用异步调用
setTimeout(() => run(), 0)
}
// 添加reject时执行的函数
_reject(err) {
if (this._status !== PENDING) return
const run = () => {
this._status = REJECTED
this._value = err
let cb
while (cb = this._rejectedQueues.shift()) {
cb(err)
}
}
setTimeout(run, 0)
}
// 添加 then 方法
then(onFullfilled, onRejected) {
const { _value, _status } = this
// 返回一个新的 Promise
return MyPromise((onFullfilledNext, onRejectedNext) => {
// 封装一个成功时执行的函数
let fullfilled = value => {
try {
if (!isFunction(onFullfilled)) {
onFullfilledNext(value)
} else {
let res = onFullfilled(value)
if (res instanceof MyPromise) {
// 如果当前回调函数返回MyPromise对象,必须等待其状态改变后再执行
res.then(onFullfilledNext, onRejectedNext)
} else {
// 否则将返回的结果作为参数,传入下一个then回调,并立即执行下一个then
onFullfilledNext(res)
}
}
} catch (err) {
// 如果函数执行出错,新的promise对象状态为失败
onRejectedNext(err)
}
}
// 封装一个失败时执行的函数
let rejected = error => {
try {
if (!isFunction(onRejected)) {
onRejectedNext(error)
} else {
let res = onRejected(error)
if (res instanceof MyPromise) {
res.then(onFullfilledNext, onRejectedNext)
} else {
onFullfilledNext(res)
}
}
} catch (err) {
onRejectedNext(err)
}
}
switch (_status) {
// 当状态为 pending 时,将then 方法回调函数计入执行队列等待
case PENDING:
this._fullfilledQueues.push(onFullfilled)
this._rejectedQueues.push(onRejected)
break
// 当状态已经改变时,立即执行对象的回调
case FULLFILLED:
fullfilled(_value)
break
case REJECTED:
rejected(_value)
break
}
})
}
}
// promise 的 then 方法接收两个参数 参数可选
// promise.then(onFullfilled, onRejected)
/**
* 多次调用then
* then 可以被同一个Promise对象调用多次
* 当promise成功状态时,所有onFullFilled需要按注册顺序依次回调
* 失败同上
* then方法必须返回一个新的promise对象
* promise支持链式调用
*/
let promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 1000)
})
promise2 = promise1.then(res => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('return promise')
}, 2000)
})
})
promise2.then(res => {
console.log(res)
})
/**
* @description 适配器
*/
var chinaPlug = {
type: '中国插头',
chinaInPlug() {
console.log('开始充电')
}
}
chinaPlug.chinaInPlug() // 开始供电
// 当我们到日本了,要用日本的适配
var japanPlug = {
type: '日本插头',
japanInPlug() {
console.log('开始供电')
}
}
// 日本插头电源适配器
function japanPlugAdapter(plug) {
return {
chinaInPlug() {
return plug.japanInPlug()
}
}
}
japanPlugAdapter(japanPlug).chinaInPlug() // 开始供电
/**
* @description 装饰者模式
*/
var originHouse = {
getDesc() {
console.log('毛坯房')
}
}
function furniture() {
console.log('搬入家具')
}
function painting() {
console.log('刷漆')
}
originHouse.getDesc = function() {
var getDesc = originHouse.getDesc
return function() {
getDesc()
furniture()
}
}
originHouse.getDesc = function() {
var getDesc = originHouse.getDesc
return function() {
getDesc()
painting()
}
}
originHouse.getDesc()
/**
* @description 外观模式 --- 一个统一的外观为复杂的子系统提供一个简单的高层功能接口
*/
var uav = {
// 电子调速器
diantiao1: {
up() {
console.log('电调1发送指令:电机1增大转速')
uav.dianji1.up()
},
down() {
console.log('电调1发送指令:电机1减少转速')
uav.dianji1.up()
}
},
diantiao2: {
up() {
console.log('电调2发送指令:电机1增大转速')
uav.dianji1.up()
},
down() {
console.log('电调2发送指令:电机1减少转速')
uav.dianji1.up()
}
}
}
// 遥控器
controller: {
up() {
uav.diantiao1.up()
uav.diantiao2.up()
}
down() {
uav.diantiao1.down()
uav.diantiao2.down()
}
// ...
}
// 函数参数重载
function domBinaEvent(nodes, type, selector, fn) {
if (fn === undefined) {
fn = selector
selector = null
}
// ...
}
domBinaEvent(node, 'click', '#div1', fn)
domBinaEvent(node, 'click', '#div1')
// vue 中的函数参数重载
export function createElement(
context,
tag,
data,
children,
normalizationType,
alwaysNormalize
) {
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children
children = data
data = undefined
}
}
/**
* @description 发布 订阅 又称观察者模式
* 比如:群聊,所有群聊的人都订阅了这个群,
* 当有人发消息时,群会主动通知给所有订阅这个群的人
* 1. Publisher: 发布者 当消息发生时,负责通知订阅者
* 2. Subscriber: 订阅者 当消息发生时被通知的对象
* 3. SubscriberMap: 储存所有订阅者的数组
* 4. type: 消息类型,订阅者可以订阅不同消息类型
* 5. subscribe: 将订阅者添加到SubscriberMap中
* 6. unsubscribe: 将SubscriberMap中删除订阅者
* 7. notidy: 遍历通知 SubscriberMap 中对象的每个订阅者
*/
const Publisher = (function() {
const _subsMap = {} // 储存订阅者
return {
// 消息订阅
subscribe(type, cb) {
if (_subsMap[type]) {
if (!_subsMap[type].includes(cb)) {
_subsMap[type].push(cb)
}
} else _subsMap[type] = [cb]
},
// 消息退订
unsubscribe(type, cb) {
if (!_subsMap[type] || !_subsMap[type].includes(cb)) return
const idx = _subsMap[type].indexOf(cb)
_subsMap[type].splice(idx, 1)
},
// 消息发布
notify(type, ...payload) {
if (!_subsMap[type]) return
_subsMap[type].forEach(cb => cb(...payload))
}
}
})()
Publisher.subscribe('运动鞋', message => console.log('33码 红色'))
Publisher.notify('运动鞋', '运动鞋到货啦...')
class Publisher {
constructor() {
this._subsMap = {}
}
// 消息订阅
subscribe(type, cb) {
if (_subsMap[type]) {
if (!_subsMap[type].includes(cb)) {
_subsMap[type].push(cb)
}
} else _subsMap[type] = [cb]
}
// 消息退订
unsubscribe(type, cb) {
if (!_subsMap[type] || !_subsMap[type].includes(cb)) return
const idx = _subsMap[type].indexOf(cb)
_subsMap[type].splice(idx, 1)
}
// 消息发布
notify(type, ...payload) {
if (!_subsMap[type]) return
_subsMap[type].forEach(cb => cb(...payload))
}
}
const adadis = new Publisher()
adadis.subscribe('运动鞋', message => console.log('33码 红色'))
Publadadisisher.notify('运动鞋', '运动鞋到货啦...')
/*
*
*/
/**
* @description 考察点,变量提升
*/
var a = 10
function foo () {
console.log(a)
var a = 20 // undefined
// let a = 20 // ReferenceError: a is not defined
}
foo() // undefined
/**
* @description 考察点
*/
var array = []
for (var i = 0; i < 3; i++) {
array.push(() => i)
}
var newArray = array.map(el => el())
console.log(array, newArray)
/**
* @description 考察点,setTimeout异步,让出当前线程
*/
function foo() {
foo(); //执行1000次左右会发生堆栈溢出的错误,
//setTimeout(foo, 0); //永远不会堆栈溢出
}
foo()
/**
* 执行settimeout函数这个时候虽然它又继续调用自己,
* 但是这里可以理解多线程操作了,
* 只是开启另一个线程来启动foo,
* 而当前线程仍然继续执行,
* 当当前线程的foo执行完成后,
* 自然就出栈了,每一次的FOO执行都是这个过程,
* 所以栈里不会容量超标的
*/
function foo(){ // 会造成页面卡死
return Promise.resolve().then(foo)
}
/**
* @description 如何让一个对象可迭代
*/
var obj = { x: 1, y: 2, z: 3 }
obj[Symbol.iterator] = function() {
return {
next: function() {
if (this._count === 3) {
return {
value: this._count,
done: true
}
}
this._count += 1
return {
value: this._count,
done: false
}
},
_count: 0
}
};
[...obj]