5.1. 如何在 JS 中“深冻结”对象
如果咱们想要确保对象被深冻结,就必须创建一个递归函数来冻结对象类型的每个属性:
2. 没有深冻结let person = {name: "Leonardo",profession: {name: "developer"}};Object.freeze(person);person.profession.name = "doctor";console.log(person);//output { name: 'Leonardo', profession: { name: 'doctor' } }
3.深冻结
function deepFreeze(object) {let propNames = Object.getOwnPropertyNames(object);for (let name of propNames) {let value = object[name];object[name] = value && typeof value === "object" ? deepFreeze(value) : value;}return Object.freeze(object);}let person = {name: "Leonardo",profession: {name: "developer"}};deepFreeze(person);person.profession.name = "doctor"; // TypeError: Cannot assign to read only property
5.2. 手写call()
/*自定义函数对象的call方法*/export function call (fn, obj, ...args) {// 如果传入的是null/undefined, this指定为windowif (obj===null || obj===undefined) {obj = obj || window}// 给obj添加一个方法: 属性名任意, 属性值必须当前调用call的函数对象obj.tempFn = fn// 通过obj调用这个方法const result = obj.tempFn(...args)// 删除新添加的方法delete obj.tempFn// 返回函数调用的结果return result}
5.3. 手写apply()
/*自定义函数对象的apply方法*/export function apply (fn, obj, args) {// 如果传入的是null/undefined, this指定为windowif (obj===null || obj===undefined) {obj = obj || window}// 给obj添加一个方法: 属性名任意, 属性值必须当前调用call的函数对象obj.tempFn = fn// 通过obj调用这个方法const result = obj.tempFn(...args)// 删除新添加的方法delete obj.tempFn// 返回函数调用的结果return result}
5.4. 手写bind()
import {call} from './call'/*自定义函数对象的bind方法重要技术:高阶函数闭包call()三点运算符*/export function bind (fn, obj, ...args) {if (obj===null || obj===undefined) {obj = obj || window}return function (...args2) {call(fn, obj, ...args, ...args2)}}
5.5. 手写一个防抖函数
/*实现函数防抖的函数*/export function debounce(callback, delay) {return function () {// console.log('debounce 事件...')// 保存this和argumentsconst that = thisconst args = arguments// 清除待执行的定时器任务if (callback.timeoutId) {clearTimeout(callback.timeoutId)}// 每隔delay的时间, 启动一个新的延迟定时器, 去准备调用callbackcallback.timeoutId = setTimeout(function () {callback.apply(that, args)// 如果定时器回调执行了, 删除标记delete callback.timeoutId}, delay)}}
5.6. 手写一个节流函数
/*实现函数节流的函数*/export function throttle(callback, delay) {let start = 0 // 必须保存第一次点击立即调用return function () {// 它的this是谁就得让callback()中的this是谁, 它接收的所有实参都直接交给callback()console.log('throttle 事件')const current = Date.now()if (current - start > delay) { // 从第2次点击开始, 需要间隔时间超过delaycallback.apply(this, arguments)start = current}}}
5.7. 手写一个深拷贝函数
```javascript / 1). 大众乞丐版 问题1: 函数属性会丢失 问题2: 循环引用会出错 / export function deepClone1(target) { return JSON.parse(JSON.stringify(target)) }
/ 获取数据的类型字符串名 / function getType(data) { return Object.prototype.toString.call(data).slice(8, -1) }
/ 2). 面试基础版本 解决问题1: 函数属性还没丢失 / export function deepClone2(target) { const type = getType(target)
if (type===’Object’ || type===’Array’) { const cloneTarget = type === ‘Array’ ? [] : {} for (const key in target) { if (target.hasOwnProperty(key)) { cloneTarget[key] = deepClone2(target[key]) } } return cloneTarget } else { return target } }
/ 3). 面试加强版本 解决问题2: 循环引用正常 / export function deepClone3(target, map = new Map()) { const type = getType(target) if (type===’Object’ || type===’Array’) { let cloneTarget = map.get(target) if (cloneTarget) { return cloneTarget } cloneTarget = type===’Array’ ? [] : {} map.set(target, cloneTarget) for (const key in target) { if (target.hasOwnProperty(key)) { cloneTarget[key] = deepClone3(target[key], map) } } return cloneTarget } else { return target } }
/ 4). 面试加强版本2(优化遍历性能) 数组: while | for | forEach() 优于 for-in | keys()&forEach() 对象: for-in 与 keys()&forEach() 差不多 / export function deepClone4(target, map = new Map()) { const type = getType(target) if (type===’Object’ || type===’Array’) { let cloneTarget = map.get(target) if (cloneTarget) { return cloneTarget }
if (type==='Array') {cloneTarget = []map.set(target, cloneTarget)target.forEach((item, index) => {cloneTarget[index] = deepClone4(item, map)})} else {cloneTarget = {}map.set(target, cloneTarget)Object.keys(target).forEach(key => {cloneTarget[key] = deepClone4(target[key], map)})}return cloneTarget
} else { return target } }
<a name="qZiUX"></a>## 5.9. 自定义new工具函数```javascript/*自定义new工具函数语法: newInstance(Fn, ...args)功能: 创建Fn构造函数的实例对象实现: 创建空对象obj, 调用Fn指定this为obj, 返回obj*/export function newInstance(Fn, ...args) {// 创建一个新的对象const obj = {}// 执行构造函数const result = Fn.apply(obj, args) // 相当于: obj.Fn()// 如果构造函数执行的结果是对象, 返回这个对象if (result instanceof Object) {return result}// 如果不是, 返回新创建的对象obj.__proto__.constructor = Fn // 让原型对象的构造器属性指向Fnreturn obj}
5.10. 手写axios函数
/*1. 函数的返回值为promise, 成功的结果为response, 失败的结果为error2. 能处理多种类型的请求: GET/POST/PUT/DELETE3. 函数的参数为一个配置对象{url: '', // 请求地址method: '', // 请求方式GET/POST/PUT/DELETEparams: {}, // GET/DELETE请求的query参数data: {}, // POST或DELETE请求的请求体参数}4. 响应json数据自动解析为js的对象/数组*//* 发送任意类型请求的函数 */function axios({url,method='GET',params={},data={}}) {// 返回一个promise对象return new Promise((resolve, reject) => {// 处理method(转大写)method = method.toUpperCase()// 处理query参数(拼接到url上) id=1&xxx=abc/*{id: 1,xxx: 'abc'}*/let queryString = ''Object.keys(params).forEach(key => {queryString += `${key}=${params[key]}&`})if (queryString) { // id=1&xxx=abc&// 去除最后的&queryString = queryString.substring(0, queryString.length-1)// 接到urlurl += '?' + queryString}// 1. 执行异步ajax请求// 创建xhr对象const request = new XMLHttpRequest()// 打开连接(初始化请求, 没有请求)request.open(method, url, true)// 发送请求if (method==='GET') {request.send()} else if (method==='POST' || method==='PUT' || method==='DELETE'){request.setRequestHeader('Content-Type', 'application/json;charset=utf-8') // 告诉服务器请求体的格式是jsonrequest.send(JSON.stringify(data)) // 发送json格式请求体参数}// 绑定状态改变的监听request.onreadystatechange = function () {// 如果请求没有完成, 直接结束if (request.readyState!==4) {return}// 如果响应状态码在[200, 300)之间代表成功, 否则失败const {status, statusText} = request// 2.1. 如果请求成功了, 调用resolve()if (status>=200 && status<=299) {// 准备结果数据对象responseconst response = {data: JSON.parse(request.response),status,statusText}resolve(response)} else { // 2.2. 如果请求失败了, 调用reject()reject(new Error('request error status is ' + status))}}})}/* 发送特定请求的静态方法 */axios.get = function (url, options) {return axios(Object.assign(options, {url, method: 'GET'}))}axios.delete = function (url, options) {return axios(Object.assign(options, {url, method: 'DELETE'}))}axios.post = function (url, data, options) {return axios(Object.assign(options, {url, data, method: 'POST'}))}axios.put = function (url, data, options) {return axios(Object.assign(options, {url, data, method: 'PUT'}))}export default axios
5.11. 自定义事件总线
/** 自定义事件总线*/const eventBus = {}/*{add: [callback1, callback2]delete: [callback3]}*/let callbacksObj = {}/*绑定事件监听*/eventBus.on = function (eventName, callback) {const callbacks = callbacksObj[eventName]if (callbacks) {callbacks.push(callback)} else {callbacksObj[eventName] = [callback]}}/*分发事件*/eventBus.emit = function (eventName, data) {const callbacks = callbacksObj[eventName]if (callbacks && callbacks.length > 0) {callbacks.forEach(callback => {callback(data)})}}/*移除事件监听*/eventBus.off = function (eventName) {if (eventName) {delete callbacksObj[eventName]} else {callbacksObj = {}}}export default eventBus
5.12. 自定义消息订阅与发布
/*自定义消息订阅与发布*/const PubSub = {}/*{add: {token1: callback1,token2: callback2},update: {token3: callback3}}*/let callbacksObj = {} // 保存所有回调的容器let id = 0 // 用于生成token的标记// 1. 订阅消息PubSub.subscribe = function (msgName, callback) {// 确定tokenconst token = 'token_' + ++id// 取出当前消息对应的callbacksconst callbacks = callbacksObj[msgName]if (!callbacks) {callbacksObj[msgName] = {[token]: callback}} else {callbacks[token] = callback}// 返回tokenreturn token}// 2. 发布异步的消息PubSub.publish = function (msgName, data) {// 取出当前消息对应的callbackslet callbacks = callbacksObj[msgName]// 如果有值if (callbacks) {// callbacks = Object.assign({}, callbacks)// 启动定时器, 异步执行所有的回调函数setTimeout(() => {Object.values(callbacks).forEach(callback => {callback(data)})}, 0)}}// 3. 发布同步的消息PubSub.publishSync = function (msgName, data) {// 取出当前消息对应的callbacksconst callbacks = callbacksObj[msgName]// 如果有值if (callbacks) {// 立即同步执行所有的回调函数Object.values(callbacks).forEach(callback => {callback(data)})}}/*4. 取消消息订阅1). 没有传值, flag为undefined2). 传入token字符串3). msgName字符串*/PubSub.unsubscribe = function (flag) {// 如果flag没有指定或者为null, 取消所有if (flag === undefined) {callbacksObj = {}} else if (typeof flag === 'string') {if (flag.indexOf('token_') === 0) { // flag是token// 找到flag对应的callbacksconst callbacks = Object.values(callbacksObj).find(callbacks => callbacks.hasOwnProperty(flag))// 如果存在, 删除对应的属性if (callbacks) {delete callbacks[flag]}} else { // flag是msgNamedelete callbacksObj[flag]}} else {throw new Error('如果传入参数, 必须是字符串类型')}}export default PubSub
5.13. 自定义数组声明式系列方法
/*实现数组声明式处理系列工具函数*//*实现map()*/export function map (array, callback) {const arr = []for (let index = 0; index < array.length; index++) {arr.push(callback(array[index], index))}return arr}/*实现reduce()*/export function reduce (array, callback, initValue) {let result = initValuefor (let index = 0; index < array.length; index++) {// 调用回调函数将返回的结果赋值给resultresult = callback(result, array[index], index)}return result}/*实现filter()*/export function filter(array, callback) {const arr = []for (let index = 0; index < array.length; index++) {if (callback(array[index], index)) {arr.push(array[index])}}return arr}/*实现find()*/export function find (array, callback) {for (let index = 0; index < array.length; index++) {if (callback(array[index], index)) {return array[index]}}return undefined}/*实现findIndex()*/export function findIndex (array, callback) {for (let index = 0; index < array.length; index++) {if (callback(array[index], index)) {return index}}return -1}/*实现every()*/export function every (array, callback) {for (let index = 0; index < array.length; index++) {if (!callback(array[index], index)) { // 只有一个结果为false, 直接返回falsereturn false}}return true}/*实现some()*/export function some (array, callback) {for (let index = 0; index < array.length; index++) {if (callback(array[index], index)) { // 只有一个结果为true, 直接返回truereturn true}}return false}export function test() {console.log('test()222')}
5.14. 手写Promise
const PENDING = 'pending' // 初始未确定的状态const RESOLVED = 'resolved' // 成功的状态const REJECTED = 'rejected' // 失败的状态/*Promise构造函数*/function Promise(excutor) {const self = this // Promise的实例对象self.status = PENDING // 状态属性, 初始值为pending, 代表初始未确定的状态self.data = undefined // 用来存储结果数据的属性, 初始值为undefinedself.callbacks = [] // {onResolved(){}, onRejected(){}}/*将promise的状态改为成功, 指定成功的value*/function resolve(value) {// 如果当前不是pending, 直接结束if (self.status !== PENDING) returnself.status = RESOLVED // 将状态改为成功self.data = value // 保存成功的value// 异步调用所有缓存的待执行成功的回调函数if (self.callbacks.length > 0) {// 启动一个延迟时间为0的定时器, 在定时器的回调中执行所有成功的回调setTimeout(() => {self.callbacks.forEach(cbsObj => {cbsObj.onResolved(value)})})}}/*将promise的状态改为失败, 指定失败的reason*/function reject(reason) {// 如果当前不是pending, 直接结束if (self.status !== PENDING) returnself.status = REJECTED // 将状态改为失败self.data = reason // 保存reason数据// 异步调用所有缓存的待执行失败的回调函数if (self.callbacks.length > 0) {// 启动一个延迟时间为0的定时器, 在定时器的回调中执行所有失败的回调setTimeout(() => {self.callbacks.forEach(cbsObj => {cbsObj.onRejected(reason)})})}}// 调用excutor来启动异步任务try {excutor(resolve, reject)} catch (error) { // 执行器执行出错, 当前promise变为失败console.log('-----')reject(error)}}/*用来指定成功/失败回调函数的方法1). 如果当前promise是resolved, 异步执行成功的回调函数onResolved2). 如果当前promise是rejected, 异步执行成功的回调函数onRejected3). 如果当前promise是pending, 保存回调函数返回一个新的promise对象它的结果状态由onResolved或者onRejected执行的结果决定2.1). 抛出error ==> 变为rejected, 结果值为error2.2). 返回值不是promise ==> 变为resolved, 结果值为返回值2.3). 返回值是promise ===> 由这个promise的决定新的promise的结果(成功/失败)*/Promise.prototype.then = function (onResolved, onRejected) {const self = thisonResolved = typeof onResolved === 'function' ? onResolved : value => value // 将value向下传递onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason} // 将reason向下传递return new Promise((resolve, reject) => { // 什么时候改变它的状态/*1. 调用指定的回调函数2. 根据回调执行结果来更新返回promise的状态*/function handle(callback) {try {const result = callback(self.data)if (!(result instanceof Promise)) { // 2.2). 返回值不是promise ==> 变为resolved, 结果值为返回值resolve(result)} else { // 2.3). 返回值是promise ===> 由这个promise的决定新的promise的结果(成功/失败)result.then(value => resolve(value),reason => reject(reason))// result.then(resolve, reject)}} catch (error) { // 2.1). 抛出error ==> 变为rejected, 结果值为errorreject(error)}}if (self.status === RESOLVED) {setTimeout(() => {handle(onResolved)})} else if (self.status === REJECTED) {setTimeout(() => {handle(onRejected)})} else { // PENDINGself.callbacks.push({onResolved(value) {handle(onResolved)},onRejected(reason) {handle(onRejected)}})}})}/*用来指定失败回调函数的方法catch是then的语法糖*/Promise.prototype.catch = function (onRejected) {return this.then(undefined, onRejected)}/*用来返回一个指定vlaue的成功的promisevalue可能是一个一般的值, 也可能是promise对象*/Promise.resolve = function (value) {return new Promise((resolve, reject) => {// 如果value是一个promise, 最终返回的promise的结果由value决定if (value instanceof Promise) {value.then(resolve, reject)} else { // value不是promise, 返回的是成功的promise, 成功的值就是valueresolve(value)}})}/*用来返回一个指定reason的失败的promise*/Promise.reject = function (reason) {return new Promise((resolve, reject) => {reject(reason)})}/*返回一个promise, 只有当数组中所有promise都成功才成功, 否则失败*/Promise.all = function (promises) {return new Promise((resolve, reject) => {let resolvedCount = 0 // 已经成功的数量const values = new Array(promises.length) // 用来保存成功promise的value值// 遍历所有promise, 取其对应的结果promises.forEach((p, index) => {p.then(value => {resolvedCount++values[index] = valueif (resolvedCount === promises.length) { // 都成功了resolve(values)}},reason => reject(reason))})})}/*返回一个promise, 由第一个完成promise决定*/Promise.race = function (promises) {return new Promise((resolve, reject) => {// 遍历所有promise, 取其对应的结果promises.forEach(p => {// 返回的promise由第一个完成p来决定其结果p.then(resolve, reject)})})}/*返回一个延迟指定时间才成功(也可能失败)的promise*/Promise.resolveDelay = function (value, time) {return new Promise((resolve, reject) => {setTimeout(() => {// 如果value是一个promise, 最终返回的promise的结果由value决定if (value instanceof Promise) {value.then(resolve, reject)} else { // value不是promise, 返回的是成功的promise, 成功的值就是valueresolve(value)}}, time)})}/*返回一个延迟指定时间才失败的promise*/Promise.rejectDelay = function (reason, time) {return new Promise((resolve, reject) => {setTimeout(() => {reject(reason)}, time)})}export default Promise
