Promise
// 模拟实现Promise | |
// Promise利用三大手段解决回调地狱: | |
// 1. 回调函数延迟绑定 | |
// 2. 返回值穿透 | |
// 3. 错误冒泡 | |
// 定义三种状态 | |
const PENDING = 'PENDING'; // 进行中 | |
const FULFILLED = 'FULFILLED'; // 已成功 | |
const REJECTED = 'REJECTED'; // 已失败 | |
class Promise { | |
constructor(exector) { | |
// 初始化状态 | |
this.status = PENDING; | |
// 将成功、失败结果放在this上,便于then、catch访问 | |
this.value = undefined; | |
this.reason = undefined; | |
// 成功态回调函数队列 | |
this.onFulfilledCallbacks = []; | |
// 失败态回调函数队列 | |
this.onRejectedCallbacks = []; | |
const resolve = value => { | |
// 只有进行中状态才能更改状态 | |
if (this.status === PENDING) { | |
this.status = FULFILLED; | |
this.value = value; | |
// 成功态函数依次执行 | |
this.onFulfilledCallbacks.forEach(fn => fn(this.value)); | |
} | |
} | |
const reject = reason => { | |
// 只有进行中状态才能更改状态 | |
if (this.status === PENDING) { | |
this.status = REJECTED; | |
this.reason = reason; | |
// 失败态函数依次执行 | |
this.onRejectedCallbacks.forEach(fn => fn(this.reason)) | |
} | |
} | |
try { | |
// 立即执行executor | |
// 把内部的resolve和reject传入executor,用户可调用resolve和reject | |
exector(resolve, reject); | |
} catch(e) { | |
// executor执行出错,将错误内容reject抛出去 | |
reject(e); | |
} | |
} | |
then(onFulfilled, onRejected) { | |
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; | |
onRejected = typeof onRejected === 'function'? onRejected : | |
reason => { throw new Error(reason instanceof Error ? reason.message : reason) } | |
// 保存this | |
const self = this; | |
return new Promise((resolve, reject) => { | |
if (self.status === PENDING) { | |
self.onFulfilledCallbacks.push(() => { | |
// try捕获错误 | |
try { | |
// 模拟微任务 | |
setTimeout(() => { | |
const result = onFulfilled(self.value); | |
// 分两种情况: | |
// 1. 回调函数返回值是Promise,执行then操作 | |
// 2. 如果不是Promise,调用新Promise的resolve函数 | |
result instanceof Promise ? result.then(resolve, reject) : resolve(result); | |
}) | |
} catch(e) { | |
reject(e); | |
} | |
}); | |
self.onRejectedCallbacks.push(() => { | |
// 以下同理 | |
try { | |
setTimeout(() => { | |
const result = onRejected(self.reason); | |
// 不同点:此时是reject | |
result instanceof Promise ? result.then(resolve, reject) : resolve(result); | |
}) | |
} catch(e) { | |
reject(e); | |
} | |
}) | |
} else if (self.status === FULFILLED) { | |
try { | |
setTimeout(() => { | |
const result = onFulfilled(self.value); | |
result instanceof Promise ? result.then(resolve, reject) : resolve(result); | |
}); | |
} catch(e) { | |
reject(e); | |
} | |
} else if (self.status === REJECTED) { | |
try { | |
setTimeout(() => { | |
const result = onRejected(self.reason); | |
result instanceof Promise ? result.then(resolve, reject) : resolve(result); | |
}) | |
} catch(e) { | |
reject(e); | |
} | |
} | |
}); | |
} | |
catch(onRejected) { | |
return this.then(null, onRejected); | |
} | |
static resolve(value) { | |
if (value instanceof Promise) { | |
// 如果是Promise实例,直接返回 | |
return value; | |
} else { | |
// 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED | |
return new Promise((resolve, reject) => resolve(value)); | |
} | |
} | |
static reject(reason) { | |
return new Promise((resolve, reject) => { | |
reject(reason); | |
}) | |
} | |
static all(promiseArr) { | |
const len = promiseArr.length; | |
const values = new Array(len); | |
// 记录已经成功执行的promise个数 | |
let count = 0; | |
return new Promise((resolve, reject) => { | |
for (let i = 0; i < len; i++) { | |
// Promise.resolve()处理,确保每一个都是promise实例 | |
Promise.resolve(promiseArr[i]).then( | |
val => { | |
values[i] = val; | |
count++; | |
// 如果全部执行完,返回promise的状态就可以改变了 | |
if (count === len) resolve(values); | |
}, | |
err => reject(err), | |
); | |
} | |
}) | |
} | |
static race(promiseArr) { | |
return new Promise((resolve, reject) => { | |
promiseArr.forEach(p => { | |
Promise.resolve(p).then( | |
val => resolve(val), | |
err => reject(err), | |
) | |
}) | |
}) | |
} | |
} |
请实现一个 add 函数,满足以下功能
add(1); // 1 | |
add(1)(2); // 3 | |
add(1)(2)(3);// 6 | |
add(1)(2, 3); // 6 | |
add(1, 2)(3); // 6 | |
add(1, 2, 3); // 6 | |
function add(...args) { | |
// 在内部声明一个函数,利用闭包的特性保存并收集所有的参数值 | |
let fn = function(...newArgs) { | |
return add.apply(null, args.concat(newArgs)) | |
} | |
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回 | |
fn.toString = function() { | |
return args.reduce((total,curr)=> total + curr) | |
} | |
return fn | |
} |
考点:
- 使用闭包, 同时要对JavaScript 的作用域链(原型链)有深入的理解
- 重写函数的
toSting()
方法
// 测试,调用toString方法触发求值 | |
add(1).toString(); // 1 | |
add(1)(2).toString(); // 3 | |
add(1)(2)(3).toString();// 6 | |
add(1)(2, 3).toString(); // 6 | |
add(1, 2)(3).toString(); // 6 | |
add(1, 2, 3).toString(); // 6 |
实现Array.of方法
Array.of()
方法用于将一组值,转换为数组
- 这个方法的主要目的,是弥补数组构造函数
Array()
的不足。因为参数个数的不同,会导致Array()
的行为有差异。 Array.of()
基本上可以用来替代Array()
或new Array()
,并且不存在由于参数不同而导致的重载。它的行为非常统一
Array.of(3, 11, 8) // [3,11,8] | |
Array.of(3) // [3] | |
Array.of(3).length // 1 |
实现
function ArrayOf(){ | |
return [].slice.call(arguments); | |
} |
实现async/await
分析
// generator生成器 生成迭代器iterator | |
// 默认这样写的类数组是不能被迭代的,缺少迭代方法 | |
let likeArray = {'0': 1, '1': 2, '2': 3, '3': 4, length: 4} | |
// // 使用迭代器使得可以展开数组 | |
// // Symbol有很多元编程方法,可以改js本身功能 | |
// likeArray[Symbol.iterator] = function () { | |
// // 迭代器是一个对象 对象中有next方法 每次调用next 都需要返回一个对象 {value,done} | |
// let index = 0 | |
// return { | |
// next: ()=>{ | |
// // 会自动调用这个方法 | |
// console.log('index',index) | |
// return { | |
// // this 指向likeArray | |
// value: this[index], | |
// done: index++ === this.length | |
// } | |
// } | |
// } | |
// } | |
// let arr = [...likeArray] | |
// console.log('arr', arr) | |
// 使用生成器返回迭代器 | |
// likeArray[Symbol.iterator] = function *() { | |
// let index = 0 | |
// while (index != this.length) { | |
// yield this[index++] | |
// } | |
// } | |
// let arr = [...likeArray] | |
// console.log('arr', arr) | |
// 生成器 碰到yield就会暂停 | |
// function *read(params) { | |
// yield 1; | |
// yield 2; | |
// } | |
// 生成器返回的是迭代器 | |
// let it = read() | |
// console.log(it.next()) | |
// console.log(it.next()) | |
// console.log(it.next()) | |
// 通过generator来优化promise(promise的缺点是不停的链式调用) | |
const fs = require('fs') | |
const path = require('path') | |
// const co = require('co') // 帮我们执行generator | |
const promisify = fn=>{ | |
return (...args)=>{ | |
return new Promise((resolve,reject)=>{ | |
fn(...args, (err,data)=>{ | |
if(err) { | |
reject(err) | |
} | |
resolve(data) | |
}) | |
}) | |
} | |
} | |
// promise化 | |
let asyncReadFile = promisify(fs.readFile) | |
function * read() { | |
let content1 = yield asyncReadFile(path.join(__dirname,'./data/name.txt'),'utf8') | |
let content2 = yield asyncReadFile(path.join(__dirname,'./data/' + content1),'utf8') | |
return content2 | |
} | |
// 这样写太繁琐 需要借助co来实现 | |
// let re = read() | |
// let {value,done} = re.next() | |
// value.then(data=>{ | |
// // 除了第一次传参没有意义外 剩下的传参都赋予了上一次的返回值 | |
// let {value,done} = re.next(data) | |
// value.then(d=>{ | |
// let {value,done} = re.next(d) | |
// console.log(value,done) | |
// }) | |
// }).catch(err=>{ | |
// re.throw(err) // 手动抛出错误 可以被try catch捕获 | |
// }) | |
// 实现co原理 | |
function co(it) {// it 迭代器 | |
return new Promise((resolve,reject)=>{ | |
// 异步迭代 需要根据函数来实现 | |
function next(data) { | |
// 递归得有中止条件 | |
let {value,done} = it.next(data) | |
if(done) { | |
resolve(value) // 直接让promise变成成功 用当前返回的结果 | |
} else { | |
// Promise.resolve(value).then(data=>{ | |
// next(data) | |
// }).catch(err=>{ | |
// reject(err) | |
// }) | |
// 简写 | |
Promise.resolve(value).then(next,reject) | |
} | |
} | |
// 首次调用 | |
next() | |
}) | |
} | |
co(read()).then(d=>{ | |
console.log(d) | |
}).catch(err=>{ | |
console.log(err,'--') | |
}) |
整体看一下结构
function asyncToGenerator(generatorFunc) { | |
return function() { | |
const gen = generatorFunc.apply(this, arguments) | |
return new Promise((resolve, reject) => { | |
function step(key, arg) { | |
let generatorResult | |
try { | |
generatorResult = gen[key](arg) | |
} catch (error) { | |
return reject(error) | |
} | |
const { value, done } = generatorResult | |
if (done) { | |
return resolve(value) | |
} else { | |
return Promise.resolve(value).then(val => step('next', val), err => step('throw', err)) | |
} | |
} | |
step("next") | |
}) | |
} | |
} |
分析
function asyncToGenerator(generatorFunc) { | |
// 返回的是一个新的函数 | |
return function() { | |
// 先调用generator函数 生成迭代器 | |
// 对应 var gen = testG() | |
const gen = generatorFunc.apply(this, arguments) | |
// 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的 | |
// var test = asyncToGenerator(testG) | |
// test().then(res => console.log(res)) | |
return new Promise((resolve, reject) => { | |
// 内部定义一个step函数 用来一步一步的跨过yield的阻碍 | |
// key有next和throw两种取值,分别对应了gen的next和throw方法 | |
// arg参数则是用来把promise resolve出来的值交给下一个yield | |
function step(key, arg) { | |
let generatorResult | |
// 这个方法需要包裹在try catch中 | |
// 如果报错了 就把promise给reject掉 外部通过.catch可以获取到错误 | |
try { | |
generatorResult = gen[key](arg) | |
} catch (error) { | |
return reject(error) | |
} | |
// gen.next() 得到的结果是一个 { value, done } 的结构 | |
const { value, done } = generatorResult | |
if (done) { | |
// 如果已经完成了 就直接resolve这个promise | |
// 这个done是在最后一次调用next后才会为true | |
// 以本文的例子来说 此时的结果是 { done: true, value: 'success' } | |
// 这个value也就是generator函数最后的返回值 | |
return resolve(value) | |
} else { | |
// 除了最后结束的时候外,每次调用gen.next() | |
// 其实是返回 { value: Promise, done: false } 的结构, | |
// 这里要注意的是Promise.resolve可以接受一个promise为参数 | |
// 并且这个promise参数被resolve的时候,这个then才会被调用 | |
return Promise.resolve( | |
// 这个value对应的是yield后面的promise | |
value | |
).then( | |
// value这个promise被resove的时候,就会执行next | |
// 并且只要done不是true的时候 就会递归的往下解开promise | |
// 对应gen.next().value.then(value => { | |
// gen.next(value).value.then(value2 => { | |
// gen.next() | |
// | |
// // 此时done为true了 整个promise被resolve了 | |
// // 最外部的test().then(res => console.log(res))的then就开始执行了 | |
// }) | |
// }) | |
function onResolve(val) { | |
step("next", val) | |
}, | |
// 如果promise被reject了 就再次进入step函数 | |
// 不同的是,这次的try catch中调用的是gen.throw(err) | |
// 那么自然就被catch到 然后把promise给reject掉啦 | |
function onReject(err) { | |
step("throw", err) | |
}, | |
) | |
} | |
} | |
step("next") | |
}) | |
} | |
} |
基于Generator函数实现async/await原理
核心:传递给我一个Generator
函数,把函数中的内容基于Iterator
迭代器的特点一步步的执行
function readFile(file) { | |
return new Promise(resolve => { | |
setTimeout(() => { | |
resolve(file); | |
}, 1000); | |
}) | |
}; | |
function asyncFunc(generator) { | |
const iterator = generator(); // 接下来要执行next | |
// data为第一次执行之后的返回结果,用于传给第二次执行 | |
const next = (data) => { | |
let { value, done } = iterator.next(data); // 第二次执行,并接收第一次的请求结果 data | |
if (done) return; // 执行完毕(到第三次)直接返回 | |
// 第一次执行next时,yield返回的 promise实例 赋值给了 value | |
value.then(data => { | |
next(data); // 当第一次value 执行完毕且成功时,执行下一步(并把第一次的结果传递下一步) | |
}); | |
} | |
next(); | |
}; | |
asyncFunc(function* () { | |
// 生成器函数:控制代码一步步执行 | |
let data = yield readFile('a.js'); // 等这一步骤执行执行成功之后,再往下走,没执行完的时候,直接返回 | |
data = yield readFile(data + 'b.js'); | |
return data; | |
}) |
实现Vue reactive响应式
// Dep module | |
class Dep { | |
static stack = [] | |
static target = null | |
deps = null | |
constructor() { | |
this.deps = new Set() | |
} | |
depend() { | |
if (Dep.target) { | |
this.deps.add(Dep.target) | |
} | |
} | |
notify() { | |
this.deps.forEach(w => w.update()) | |
} | |
static pushTarget(t) { | |
if (this.target) { | |
this.stack.push(this.target) | |
} | |
this.target = t | |
} | |
static popTarget() { | |
this.target = this.stack.pop() | |
} | |
} | |
// reactive | |
function reactive(o) { | |
if (o && typeof o === 'object') { | |
Object.keys(o).forEach(k => { | |
defineReactive(o, k, o[k]) | |
}) | |
} | |
return o | |
} | |
function defineReactive(obj, k, val) { | |
let dep = new Dep() | |
Object.defineProperty(obj, k, { | |
get() { | |
dep.depend() | |
return val | |
}, | |
set(newVal) { | |
val = newVal | |
dep.notify() | |
} | |
}) | |
if (val && typeof val === 'object') { | |
reactive(val) | |
} | |
} | |
// watcher | |
class Watcher { | |
constructor(effect) { | |
this.effect = effect | |
this.update() | |
} | |
update() { | |
Dep.pushTarget(this) | |
this.value = this.effect() | |
Dep.popTarget() | |
return this.value | |
} | |
} | |
// 测试代码 | |
const data = reactive({ | |
msg: 'aaa' | |
}) | |
new Watcher(() => { | |
console.log('===> effect', data.msg); | |
}) | |
setTimeout(() => { | |
data.msg = 'hello' | |
}, 1000) |
参考 前端进阶面试题详细解答
实现观察者模式
观察者模式(基于发布订阅模式) 有观察者,也有被观察者
观察者需要放到被观察者中,被观察者的状态变化需要通知观察者 我变化了 内部也是基于发布订阅模式,收集观察者,状态变化后要主动通知观察者
class Subject { // 被观察者 学生 | |
constructor(name) { | |
this.state = 'happy' | |
this.observers = []; // 存储所有的观察者 | |
} | |
// 收集所有的观察者 | |
attach(o){ // Subject. prototype. attch | |
this.observers.push(o) | |
} | |
// 更新被观察者 状态的方法 | |
setState(newState) { | |
this.state = newState; // 更新状态 | |
// this 指被观察者 学生 | |
this.observers.forEach(o => o.update(this)) // 通知观察者 更新它们的状态 | |
} | |
} | |
class Observer{ // 观察者 父母和老师 | |
constructor(name) { | |
this.name = name | |
} | |
update(student) { | |
console.log('当前' + this.name + '被通知了', '当前学生的状态是' + student.state) | |
} | |
} | |
let student = new Subject('学生'); | |
let parent = new Observer('父母'); | |
let teacher = new Observer('老师'); | |
// 被观察者存储观察者的前提,需要先接纳观察者 | |
student. attach(parent); | |
student. attach(teacher); | |
student. setState('被欺负了'); |
实现一个双向绑定
defineProperty 版本
// 数据 | |
const data = { | |
text: 'default' | |
}; | |
const input = document.getElementById('input'); | |
const span = document.getElementById('span'); | |
// 数据劫持 | |
Object.defineProperty(data, 'text', { | |
// 数据变化 --> 修改视图 | |
set(newVal) { | |
input.value = newVal; | |
span.innerHTML = newVal; | |
} | |
}); | |
// 视图更改 --> 数据变化 | |
input.addEventListener('keyup', function(e) { | |
data.text = e.target.value; | |
}); |
proxy 版本
// 数据 | |
const data = { | |
text: 'default' | |
}; | |
const input = document.getElementById('input'); | |
const span = document.getElementById('span'); | |
// 数据劫持 | |
const handler = { | |
set(target, key, value) { | |
target[key] = value; | |
// 数据变化 --> 修改视图 | |
input.value = value; | |
span.innerHTML = value; | |
return value; | |
} | |
}; | |
const proxy = new Proxy(data, handler); | |
// 视图更改 --> 数据变化 | |
input.addEventListener('keyup', function(e) { | |
proxy.text = e.target.value; | |
}); |
手写防抖函数
函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
// 函数防抖的实现 | |
function debounce(fn, wait) { | |
let timer = null; | |
return function() { | |
let context = this, | |
args = arguments; | |
// 如果此时存在定时器的话,则取消之前的定时器重新记时 | |
if (timer) { | |
clearTimeout(timer); | |
timer = null; | |
} | |
// 设置定时器,使事件间隔指定事件后执行 | |
timer = setTimeout(() => { | |
fn.apply(context, args); | |
}, wait); | |
}; | |
} |
实现深拷贝
- 浅拷贝: 浅拷贝指的是将一个对象的属性值复制到另一个对象,如果有的属性的值为引用类型的话,那么会将这个引用的地址复制给对象,因此两个对象会有同一个引用类型的引用。浅拷贝可以使用 Object.assign 和展开运算符来实现。
- 深拷贝: 深拷贝相对浅拷贝而言,如果遇到属性值为引用类型的时候,它新建一个引用类型并将对应的值复制给它,因此对象获得的一个新的引用类型而不是一个原有类型的引用。深拷贝对于一些对象可以使用 JSON 的两个函数来实现,但是由于 JSON 的对象格式比 js 的对象格式更加严格,所以如果属性值里边出现函数或者 Symbol 类型的值时,会转换失败
(1)JSON.stringify()
JSON.parse(JSON.stringify(obj))
是目前比较常用的深拷贝方法之一,它的原理就是利用JSON.stringify
将js
对象序列化(JSON字符串),再使用JSON.parse
来反序列化(还原)js
对象。- 这个方法可以简单粗暴的实现深拷贝,但是还存在问题,拷贝的对象中如果有函数,undefined,symbol,当使用过
JSON.stringify()
进行处理之后,都会消失。
let obj1 = { a: 0, | |
b: { | |
c: 0 | |
} | |
}; | |
let obj2 = JSON.parse(JSON.stringify(obj1)); | |
obj1.a = 1; | |
obj1.b.c = 1; | |
console.log(obj1); // {a: 1, b: {c: 1}} | |
console.log(obj2); // {a: 0, b: {c: 0}} |
(2)函数库lodash的_.cloneDeep方法
该函数库也有提供_.cloneDeep用来做 Deep Copy
var _ = require('lodash'); | |
var obj1 = { | |
a: 1, | |
b: { f: { g: 1 } }, | |
c: [1, 2, 3] | |
}; | |
var obj2 = _.cloneDeep(obj1); | |
console.log(obj1.b.f === obj2.b.f);// false |
(3)手写实现深拷贝函数
// 深拷贝的实现 | |
function deepCopy(object) { | |
if (!object || typeof object !== "object") return; | |
let newObject = Array.isArray(object) ? [] : {}; | |
for (let key in object) { | |
if (object.hasOwnProperty(key)) { | |
newObject[key] = | |
typeof object[key] === "object" ? deepCopy(object[key]) : object[key]; | |
} | |
} | |
return newObject; | |
} |
数组去重方法汇总
首先:我知道多少种去重方式
1. 双层 for 循环
function distinct(arr) { | |
for (let i=0, len=arr.length; i<len; i++) { | |
for (let j=i+1; j<len; j++) { | |
if (arr[i] == arr[j]) { | |
arr.splice(j, 1); | |
// splice 会改变数组长度,所以要将数组长度 len 和下标 j 减一 | |
len--; | |
j--; | |
} | |
} | |
} | |
return arr; | |
} |
思想: 双重for
循环是比较笨拙的方法,它实现的原理很简单:先定义一个包含原始数组第一个元素的数组,然后遍历原始数组,将原始数组中的每个元素与新数组中的每个元素进行比对,如果不重复则添加到新数组中,最后返回新数组;因为它的时间复杂度是O(n^2)
,如果数组长度很大,效率会很低
2. Array.filter() 加 indexOf/includes
function distinct(a, b) { | |
let arr = a.concat(b); | |
return arr.filter((item, index)=> { | |
//return arr.indexOf(item) === index | |
return arr.includes(item) | |
}) | |
} |
思想: 利用indexOf
检测元素在数组中第一次出现的位置是否和元素现在的位置相等,如果不等则说明该元素是重复元素
3. ES6 中的 Set 去重
function distinct(array) { | |
return Array.from(new Set(array)); | |
} |
思想: ES6 提供了新的数据结构 Set,Set 结构的一个特性就是成员值都是唯一的,没有重复的值。
4. reduce 实现对象数组去重复
var resources = [ | |
{ name: "张三", age: "18" }, | |
{ name: "张三", age: "19" }, | |
{ name: "张三", age: "20" }, | |
{ name: "李四", age: "19" }, | |
{ name: "王五", age: "20" }, | |
{ name: "赵六", age: "21" } | |
] | |
var temp = {}; | |
resources = resources.reduce((prev, curv) => { | |
// 如果临时对象中有这个名字,什么都不做 | |
if (temp[curv.name]) { | |
}else { | |
// 如果临时对象没有就把这个名字加进去,同时把当前的这个对象加入到prev中 | |
temp[curv.name] = true; | |
prev.push(curv); | |
} | |
return prev | |
}, []); | |
console.log("结果", resources); |
这种方法是利用高阶函数reduce
进行去重, 这里只需要注意initialValue
得放一个空数组[],不然没法push
实现apply方法
apply原理与call很相似,不多赘述
// 模拟 apply | |
Function.prototype.myapply = function(context, arr) { | |
var context = Object(context) || window; | |
context.fn = this; | |
var result; | |
if (!arr) { | |
result = context.fn(); | |
} else { | |
var args = []; | |
for (var i = 0, len = arr.length; i < len; i++) { | |
args.push("arr[" + i + "]"); | |
} | |
result = eval("context.fn(" + args + ")"); | |
} | |
delete context.fn; | |
return result; | |
}; |
原生实现
function ajax() { | |
let xhr = new XMLHttpRequest() //实例化,以调用方法 | |
xhr.open('get', 'https://www.google.com') //参数2,url。参数三:异步 | |
xhr.onreadystatechange = () => { //每当 readyState 属性改变时,就会调用该函数。 | |
if (xhr.readyState === 4) { //XMLHttpRequest 代理当前所处状态。 | |
if (xhr.status >= 200 && xhr.status < 300) { //200-300请求成功 | |
let string = request.responseText | |
//JSON.parse() 方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象 | |
let object = JSON.parse(string) | |
} | |
} | |
} | |
request.send() //用于实际发出 HTTP 请求。不带参数为GET请求 | |
} |
将js对象转化为树形结构
// 转换前: | |
source = [{ | |
id: 1, | |
pid: 0, | |
name: 'body' | |
}, { | |
id: 2, | |
pid: 1, | |
name: 'title' | |
}, { | |
id: 3, | |
pid: 2, | |
name: 'div' | |
}] | |
// 转换为: | |
tree = [{ | |
id: 1, | |
pid: 0, | |
name: 'body', | |
children: [{ | |
id: 2, | |
pid: 1, | |
name: 'title', | |
children: [{ | |
id: 3, | |
pid: 1, | |
name: 'div' | |
}] | |
} | |
}] |
代码实现:
function jsonToTree(data) { | |
// 初始化结果数组,并判断输入数据的格式 | |
let result = [] | |
if(!Array.isArray(data)) { | |
return result | |
} | |
// 使用map,将当前对象的id与当前对象对应存储起来 | |
let map = {}; | |
data.forEach(item => { | |
map[item.id] = item; | |
}); | |
// | |
data.forEach(item => { | |
let parent = map[item.pid]; | |
if(parent) { | |
(parent.children || (parent.children = [])).push(item); | |
} else { | |
result.push(item); | |
} | |
}); | |
return result; | |
} |
原型继承
这里只写寄生组合继承了,中间还有几个演变过来的继承但都有一些缺陷
function Parent() { | |
this.name = 'parent'; | |
} | |
function Child() { | |
Parent.call(this); | |
this.type = 'children'; | |
} | |
Child.prototype = Object.create(Parent.prototype); | |
Child.prototype.constructor = Child; |
实现forEach方法
Array.prototype.myForEach = function(callback, context=window) { | |
// this=>arr | |
let self = this, | |
i = 0, | |
len = self.length; | |
for(;i<len;i++) { | |
typeof callback == 'function' && callback.call(context,self[i], i) | |
} | |
} |
实现迭代器生成函数
我们说迭代器对象全凭迭代器生成函数帮我们生成。在ES6
中,实现一个迭代器生成函数并不是什么难事儿,因为ES6早帮我们考虑好了全套的解决方案,内置了贴心的 生成器 (Generator
)供我们使用:
// 编写一个迭代器生成函数 | |
function *iteratorGenerator() { | |
yield '1号选手' | |
yield '2号选手' | |
yield '3号选手' | |
} | |
const iterator = iteratorGenerator() | |
iterator.next() | |
iterator.next() | |
iterator.next() |
丢进控制台,不负众望:
写一个生成器函数并没有什么难度,但在面试的过程中,面试官往往对生成器这种语法糖背后的实现逻辑更感兴趣。下面我们要做的,不仅仅是写一个迭代器对象,而是用ES5
去写一个能够生成迭代器对象的迭代器生成函数(解析在注释里):
// 定义生成器函数,入参是任意集合 | |
function iteratorGenerator(list) { | |
// idx记录当前访问的索引 | |
var idx = 0 | |
// len记录传入集合的长度 | |
var len = list.length | |
return { | |
// 自定义next方法 | |
next: function() { | |
// 如果索引还没有超出集合长度,done为false | |
var done = idx >= len | |
// 如果done为false,则可以继续取值 | |
var value = !done ? list[idx++] : undefined | |
// 将当前值与遍历是否完毕(done)返回 | |
return { | |
done: done, | |
value: value | |
} | |
} | |
} | |
} | |
var iterator = iteratorGenerator(['1号选手', '2号选手', '3号选手']) | |
iterator.next() | |
iterator.next() | |
iterator.next() |
此处为了记录每次遍历的位置,我们实现了一个闭包,借助自由变量来做我们的迭代过程中的“游标”。
运行一下我们自定义的迭代器,结果符合预期:
实现斐波那契数列
// 递归 | |
function fn (n){ | |
if(n==0) return 0 | |
if(n==1) return 1 | |
return fn(n-2)+fn(n-1) | |
} | |
// 优化 | |
function fibonacci2(n) { | |
const arr = [1, 1, 2]; | |
const arrLen = arr.length; | |
if (n <= arrLen) { | |
return arr[n]; | |
} | |
for (let i = arrLen; i < n; i++) { | |
arr.push(arr[i - 1] + arr[ i - 2]); | |
} | |
return arr[arr.length - 1]; | |
} | |
// 非递归 | |
function fn(n) { | |
let pre1 = 1; | |
let pre2 = 1; | |
let current = 2; | |
if (n <= 2) { | |
return current; | |
} | |
for (let i = 2; i < n; i++) { | |
pre1 = pre2; | |
pre2 = current; | |
current = pre1 + pre2; | |
} | |
return current; | |
} |
Promise.race
Promise.race = function(promiseArr) { | |
return new Promise((resolve, reject) => { | |
promiseArr.forEach(p => { | |
// 如果不是Promise实例需要转化为Promise实例 | |
Promise.resolve(p).then( | |
val => resolve(val), | |
err => reject(err), | |
) | |
}) | |
}) | |
} |
滚动加载
原理就是监听页面滚动事件,分析clientHeight、scrollTop、scrollHeight三者的属性关系。
window.addEventListener('scroll', function() { | |
const clientHeight = document.documentElement.clientHeight; | |
const scrollTop = document.documentElement.scrollTop; | |
const scrollHeight = document.documentElement.scrollHeight; | |
if (clientHeight + scrollTop >= scrollHeight) { | |
// 检测到滚动至页面底部,进行后续操作 | |
// ... | |
} | |
}, false); |