循环打印红黄绿
下面来看一道比较典型的问题,通过这个问题来对比几种异步编程方法:红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯不断交替重复亮灯?
三个亮灯函数:
| function red() { |
| console.log('red'); |
| } |
| function green() { |
| console.log('green'); |
| } |
| function yellow() { |
| console.log('yellow'); |
| } |
这道题复杂的地方在于需要“交替重复”亮灯,而不是“亮完一次”就结束了。
(1)用 callback 实现
| const task = (timer, light, callback) => { |
| setTimeout(() => { |
| if (light === 'red') { |
| red() |
| } |
| else if (light === 'green') { |
| green() |
| } |
| else if (light === 'yellow') { |
| yellow() |
| } |
| callback() |
| }, timer) |
| } |
| task(3000, 'red', () => { |
| task(2000, 'green', () => { |
| task(1000, 'yellow', Function.prototype) |
| }) |
| }) |
这里存在一个 bug:代码只是完成了一次流程,执行后红黄绿灯分别只亮一次。该如何让它交替重复进行呢?
上面提到过递归,可以递归亮灯的一个周期:
| const step = () => { |
| task(3000, 'red', () => { |
| task(2000, 'green', () => { |
| task(1000, 'yellow', step) |
| }) |
| }) |
| } |
| step() |
注意看黄灯亮的回调里又再次调用了 step 方法 以完成循环亮灯。
(2)用 promise 实现
| const task = (timer, light) => |
| new Promise((resolve, reject) => { |
| setTimeout(() => { |
| if (light === 'red') { |
| red() |
| } |
| else if (light === 'green') { |
| green() |
| } |
| else if (light === 'yellow') { |
| yellow() |
| } |
| resolve() |
| }, timer) |
| }) |
| const step = () => { |
| task(3000, 'red') |
| .then(() => task(2000, 'green')) |
| .then(() => task(2100, 'yellow')) |
| .then(step) |
| } |
| step() |
这里将回调移除,在一次亮灯结束后,resolve 当前 promise,并依然使用递归进行。
(3)用 async/await 实现
| const taskRunner = async () => { |
| await task(3000, 'red') |
| await task(2000, 'green') |
| await task(2100, 'yellow') |
| taskRunner() |
| } |
| taskRunner() |
手写 Promise
| const PENDING = "pending"; |
| const RESOLVED = "resolved"; |
| const REJECTED = "rejected"; |
| |
| function MyPromise(fn) { |
| |
| var self = this; |
| |
| |
| this.state = PENDING; |
| |
| |
| this.value = null; |
| |
| |
| this.resolvedCallbacks = []; |
| |
| |
| this.rejectedCallbacks = []; |
| |
| |
| function resolve(value) { |
| |
| if (value instanceof MyPromise) { |
| return value.then(resolve, reject); |
| } |
| |
| |
| setTimeout(() => { |
| |
| if (self.state === PENDING) { |
| |
| self.state = RESOLVED; |
| |
| |
| self.value = value; |
| |
| |
| self.resolvedCallbacks.forEach(callback => { |
| callback(value); |
| }); |
| } |
| }, 0); |
| } |
| |
| |
| function reject(value) { |
| |
| setTimeout(() => { |
| |
| if (self.state === PENDING) { |
| |
| self.state = REJECTED; |
| |
| |
| self.value = value; |
| |
| |
| self.rejectedCallbacks.forEach(callback => { |
| callback(value); |
| }); |
| } |
| }, 0); |
| } |
| |
| |
| try { |
| fn(resolve, reject); |
| } catch (e) { |
| |
| reject(e); |
| } |
| } |
| |
| MyPromise.prototype.then = function(onResolved, onRejected) { |
| |
| onResolved = |
| typeof onResolved === "function" |
| ? onResolved |
| : function(value) { |
| return value; |
| }; |
| |
| onRejected = |
| typeof onRejected === "function" |
| ? onRejected |
| : function(error) { |
| throw error; |
| }; |
| |
| |
| if (this.state === PENDING) { |
| this.resolvedCallbacks.push(onResolved); |
| this.rejectedCallbacks.push(onRejected); |
| } |
| |
| |
| |
| if (this.state === RESOLVED) { |
| onResolved(this.value); |
| } |
| |
| if (this.state === REJECTED) { |
| onRejected(this.value); |
| } |
| }; |
实现双向数据绑定
| let obj = {} |
| let input = document.getElementById('input') |
| let span = document.getElementById('span') |
| |
| Object.defineProperty(obj, 'text', { |
| configurable: true, |
| enumerable: true, |
| get() { |
| console.log('获取数据了') |
| }, |
| set(newVal) { |
| console.log('数据更新了') |
| input.value = newVal |
| span.innerHTML = newVal |
| } |
| }) |
| |
| input.addEventListener('keyup', function(e) { |
| obj.text = e.target.value |
| }) |
Array.prototype.filter()
| Array.prototype.filter = function(callback, thisArg) { |
| if (this == undefined) { |
| throw new TypeError('this is null or not undefined'); |
| } |
| if (typeof callback !== 'function') { |
| throw new TypeError(callback + 'is not a function'); |
| } |
| const res = []; |
| |
| const O = Object(this); |
| |
| const len = O.length >>> 0; |
| for (let i = 0; i < len; i++) { |
| |
| if (i in O) { |
| |
| if (callback.call(thisArg, O[i], i, O)) { |
| res.push(O[i]); |
| } |
| } |
| } |
| return res; |
| } |
实现类数组转化为数组
类数组转换为数组的方法有这样几种:
- 通过 call 调用数组的 slice 方法来实现转换
Array.prototype.slice.call(arrayLike);
- 通过 call 调用数组的 splice 方法来实现转换
Array.prototype.splice.call(arrayLike, 0);
- 通过 apply 调用数组的 concat 方法来实现转换
Array.prototype.concat.apply([], arrayLike);
Array.from(arrayLike);
手写 call 函数
call 函数的实现步骤:
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
- 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
- 处理传入的参数,截取第一个参数后的所有参数。
- 将函数作为上下文对象的一个属性。
- 使用上下文对象来调用这个方法,并保存返回结果。
- 删除刚才新增的属性。
- 返回结果。
| |
| Function.prototype.myCall = function(context) { |
| |
| if (typeof this !== "function") { |
| console.error("type error"); |
| } |
| |
| let args = [...arguments].slice(1), |
| result = null; |
| |
| context = context || window; |
| |
| context.fn = this; |
| |
| result = context.fn(...args); |
| |
| delete context.fn; |
| return result; |
| }; |
实现一个迷你版的vue
入口
| |
| class Vue { |
| constructor (options) { |
| |
| this.$options = options || {} |
| this.$data = options.data || {} |
| this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el |
| |
| this._proxyData(this.$data) |
| |
| new Observer(this.$data) |
| |
| new Compiler(this) |
| } |
| _proxyData (data) { |
| |
| Object.keys(data).forEach(key => { |
| |
| Object.defineProperty(this, key, { |
| enumerable: true, |
| configurable: true, |
| get () { |
| return data[key] |
| }, |
| set (newValue) { |
| if (newValue === data[key]) { |
| return |
| } |
| data[key] = newValue |
| } |
| }) |
| }) |
| } |
| } |
实现Dep
| class Dep { |
| constructor () { |
| // 存储所有的观察者 |
| this.subs = [] |
| } |
| // 添加观察者 |
| addSub (sub) { |
| if (sub && sub.update) { |
| this.subs.push(sub) |
| } |
| } |
| // 发送通知 |
| notify () { |
| this.subs.forEach(sub => { |
| sub.update() |
| }) |
| } |
| } |
实现watcher
| class Watcher { |
| constructor (vm, key, cb) { |
| this.vm = vm |
| |
| this.key = key |
| |
| this.cb = cb |
| |
| |
| Dep.target = this |
| |
| this.oldValue = vm[key] |
| Dep.target = null |
| } |
| |
| update () { |
| let newValue = this.vm[this.key] |
| if (this.oldValue === newValue) { |
| return |
| } |
| this.cb(newValue) |
| } |
| } |
实现compiler
| class Compiler { |
| constructor (vm) { |
| this.el = vm.$el |
| this.vm = vm |
| this.compile(this.el) |
| } |
| |
| compile (el) { |
| let childNodes = el.childNodes |
| Array.from(childNodes).forEach(node => { |
| |
| if (this.isTextNode(node)) { |
| this.compileText(node) |
| } else if (this.isElementNode(node)) { |
| |
| this.compileElement(node) |
| } |
| |
| |
| if (node.childNodes && node.childNodes.length) { |
| this.compile(node) |
| } |
| }) |
| } |
| |
| compileElement (node) { |
| |
| |
| Array.from(node.attributes).forEach(attr => { |
| |
| let attrName = attr.name |
| if (this.isDirective(attrName)) { |
| |
| attrName = attrName.substr(2) |
| let key = attr.value |
| this.update(node, key, attrName) |
| } |
| }) |
| } |
| |
| update (node, key, attrName) { |
| let updateFn = this[attrName + 'Updater'] |
| updateFn && updateFn.call(this, node, this.vm[key], key) |
| } |
| |
| |
| textUpdater (node, value, key) { |
| node.textContent = value |
| new Watcher(this.vm, key, (newValue) => { |
| node.textContent = newValue |
| }) |
| } |
| |
| modelUpdater (node, value, key) { |
| node.value = value |
| new Watcher(this.vm, key, (newValue) => { |
| node.value = newValue |
| }) |
| |
| node.addEventListener('input', () => { |
| this.vm[key] = node.value |
| }) |
| } |
| |
| |
| compileText (node) { |
| |
| |
| let reg = /\{\{(.+?)\}\}/ |
| let value = node.textContent |
| if (reg.test(value)) { |
| let key = RegExp.$1.trim() |
| node.textContent = value.replace(reg, this.vm[key]) |
| |
| |
| new Watcher(this.vm, key, (newValue) => { |
| node.textContent = newValue |
| }) |
| } |
| } |
| |
| isDirective (attrName) { |
| return attrName.startsWith('v-') |
| } |
| |
| isTextNode (node) { |
| return node.nodeType === 3 |
| } |
| |
| isElementNode (node) { |
| return node.nodeType === 1 |
| } |
| } |
实现Observer
| class Observer { |
| constructor (data) { |
| this.walk(data) |
| } |
| walk (data) { |
| |
| if (!data || typeof data !== 'object') { |
| return |
| } |
| |
| Object.keys(data).forEach(key => { |
| this.defineReactive(data, key, data[key]) |
| }) |
| } |
| defineReactive (obj, key, val) { |
| let that = this |
| |
| let dep = new Dep() |
| |
| this.walk(val) |
| Object.defineProperty(obj, key, { |
| enumerable: true, |
| configurable: true, |
| get () { |
| |
| Dep.target && dep.addSub(Dep.target) |
| return val |
| }, |
| set (newValue) { |
| if (newValue === val) { |
| return |
| } |
| val = newValue |
| that.walk(newValue) |
| |
| dep.notify() |
| } |
| }) |
| } |
| } |
使用
| <!DOCTYPE html> |
| <html lang="cn"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <meta http-equiv="X-UA-Compatible" content="ie=edge"> |
| <title>Mini Vue</title> |
| </head> |
| <body> |
| <div id="app"> |
| <h1>差值表达式</h1> |
| <h3>{{ msg }}</h3> |
| <h3>{{ count }}</h3> |
| <h1>v-text</h1> |
| <div v-text="msg"></div> |
| <h1>v-model</h1> |
| <input type="text" v-model="msg"> |
| <input type="text" v-model="count"> |
| </div> |
| <script src="./js/dep.js"></script> |
| <script src="./js/watcher.js"></script> |
| <script src="./js/compiler.js"></script> |
| <script src="./js/observer.js"></script> |
| <script src="./js/vue.js"></script> |
| <script> |
| let vm = new Vue({ |
| el: '#app', |
| data: { |
| msg: 'Hello Vue', |
| count: 100, |
| person: { name: 'zs' } |
| } |
| }) |
| console.log(vm.msg) |
| |
| vm.test = 'abc' |
| </script> |
| </body> |
| </html> |
数组扁平化
数组扁平化是指将一个多维数组变为一个一维数组
| const arr = [1, [2, [3, [4, 5]]], 6]; |
| |
方法一:使用flat()
const res1 = arr.flat(Infinity);
方法二:利用正则
const res2 = JSON.stringify(arr).replace(/\[|\]/g, '').split(',');
但数据类型都会变为字符串
方法三:正则改良版本
const res3 = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']');
方法四:使用reduce
| const flatten = arr => { |
| return arr.reduce((pre, cur) => { |
| return pre.concat(Array.isArray(cur) ? flatten(cur) : cur); |
| }, []) |
| } |
| const res4 = flatten(arr); |
方法五:函数递归
| const res5 = []; |
| const fn = arr => { |
| for (let i = 0; i < arr.length; i++) { |
| if (Array.isArray(arr[i])) { |
| fn(arr[i]); |
| } else { |
| res5.push(arr[i]); |
| } |
| } |
| } |
| fn(arr); |
Promise并行限制
就是实现有并行限制的Promise调度器问题
| class Scheduler { |
| constructor() { |
| this.queue = []; |
| this.maxCount = 2; |
| this.runCounts = 0; |
| } |
| add(promiseCreator) { |
| 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 timeout = time => new Promise(resolve => { |
| setTimeout(resolve, time); |
| }) |
| |
| const scheduler = new Scheduler(); |
| |
| const addTask = (time,order) => { |
| scheduler.add(() => timeout(time).then(()=>console.log(order))) |
| } |
| |
| |
| addTask(1000, '1'); |
| addTask(500, '2'); |
| addTask(300, '3'); |
| addTask(400, '4'); |
| scheduler.taskStart() |
| |
| |
| |
| |
实现 jsonp
| |
| function addScript(src) { |
| const script = document.createElement('script'); |
| script.src = src; |
| script.type = "text/javascript"; |
| document.body.appendChild(script); |
| } |
| addScript("http://xxx.xxx.com/xxx.js?callback=handleRes"); |
| |
| function handleRes(res) { |
| console.log(res); |
| } |
| |
| handleRes({a: 1, b: 2}); |
实现发布-订阅模式
| class EventCenter{ |
| |
| let handlers = {} |
| |
| |
| addEventListener(type, handler) { |
| |
| if (!this.handlers[type]) { |
| this.handlers[type] = [] |
| } |
| |
| this.handlers[type].push(handler) |
| } |
| |
| |
| dispatchEvent(type, params) { |
| |
| if (!this.handlers[type]) { |
| return new Error('该事件未注册') |
| } |
| |
| this.handlers[type].forEach(handler => { |
| handler(...params) |
| }) |
| } |
| |
| |
| removeEventListener(type, handler) { |
| if (!this.handlers[type]) { |
| return new Error('事件无效') |
| } |
| if (!handler) { |
| |
| delete this.handlers[type] |
| } else { |
| const index = this.handlers[type].findIndex(el => el === handler) |
| if (index === -1) { |
| return new Error('无该绑定事件') |
| } |
| |
| this.handlers[type].splice(index, 1) |
| if (this.handlers[type].length === 0) { |
| delete this.handlers[type] |
| } |
| } |
| } |
| } |
JSONP
script标签不遵循同源协议,可以用来进行跨域请求,优点就是兼容性好但仅限于GET请求
| const jsonp = ({ url, params, callbackName }) => { |
| const generateUrl = () => { |
| let dataSrc = ''; |
| for (let key in params) { |
| if (Object.prototype.hasOwnProperty.call(params, key)) { |
| dataSrc += `${key}=${params[key]}&`; |
| } |
| } |
| dataSrc += `callback=${callbackName}`; |
| return `${url}?${dataSrc}`; |
| } |
| return new Promise((resolve, reject) => { |
| const scriptEle = document.createElement('script'); |
| scriptEle.src = generateUrl(); |
| document.body.appendChild(scriptEle); |
| window[callbackName] = data => { |
| resolve(data); |
| document.removeChild(scriptEle); |
| } |
| }) |
| } |
实现千位分隔符
| // 保留三位小数 |
| parseToMoney(1234.56); // return '1,234.56' |
| parseToMoney(123456789); // return '123,456,789' |
| parseToMoney(1087654.321); // return '1,087,654.321' |
| function parseToMoney(num) { |
| num = parseFloat(num.toFixed(3)); |
| let [integer, decimal] = String.prototype.split.call(num, '.'); |
| integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,'); |
| return integer + '.' + (decimal ? decimal : ''); |
| } |
手写类型判断函数
| function getType(value) { |
| |
| if (value === null) { |
| return value + ""; |
| } |
| |
| if (typeof value === "object") { |
| let valueClass = Object.prototype.toString.call(value), |
| type = valueClass.split(" ")[1].split(""); |
| type.pop(); |
| return type.join("").toLowerCase(); |
| } else { |
| |
| return typeof value; |
| } |
| } |
用正则写一个根据name获取cookie中的值的方法
| function getCookie(name) { |
| var match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]*)')); |
| if (match) return unescape(match[2]); |
| } |
- 获取页面上的
cookie
可以使用 document.cookie
这里获取到的是类似于这样的字符串:
'username=poetry; user-id=12345; user-roles=home, me, setting'
可以看到这么几个信息:
- 每一个cookie都是由
name=value
这样的形式存储的 - 每一项的开头可能是一个空串
''
(比如username
的开头其实就是), 也可能是一个空字符串' '
(比如user-id
的开头就是) - 每一项用
";"
来区分 - 如果某项中有多个值的时候,是用
","
来连接的(比如user-roles
的值) - 每一项的结尾可能是有
";"
的(比如username
的结尾),也可能是没有的(比如user-roles
的结尾) - 所以我们将这里的正则拆分一下:
'(^| )'
表示的就是获取每一项的开头,因为我们知道如果^
不是放在[]
里的话就是表示开头匹配。所以这里(^| )
的意思其实就被拆分为(^)
表示的匹配username
这种情况,它前面什么都没有是一个空串(你可以把(^)
理解为^
它后面还有一个隐藏的''
);而|
表示的就是或者是一个" "
(为了匹配user-id
开头的这种情况)+name+
这没什么好说的=([^;]*)
这里匹配的就是=
后面的值了,比如poetry
;刚刚说了^
要是放在[]
里的话就表示"除了^后面的内容都能匹配"
,也就是非的意思。所以这里([^;]*)
表示的是除了";"
这个字符串别的都匹配(*
应该都知道什么意思吧,匹配0次或多次)- 有的大佬等号后面是这样写的
'=([^;]*)(;|$)'
,而最后为什么可以把'(;|$)'
给省略呢?因为其实最后一个cookie
项是没有';'
的,所以它可以合并到=([^;]*)
这一步。 - 最后获取到的
match
其实是一个长度为4的数组。比如:
| [ |
| "username=poetry;", |
| "", |
| "poetry", |
| ";" |
| ] |
- 第0项:全量
- 第1项:开头
- 第2项:中间的值
- 第3项:结尾
所以我们是要拿第2项match[2]
的值。
- 为了防止获取到的值是
%xxx
这样的字符序列,需要用unescape()
方法解码。
手写 Promise.then
then
方法返回一个新的 promise
实例,为了在 promise
状态发生变化时(resolve
/ reject
被调用时)再执行 then
里的函数,我们使用一个 callbacks
数组先把传给then的函数暂存起来,等状态改变时再调用。
那么,怎么保证后一个 **then**
里的方法在前一个 **then**
(可能是异步)结束之后再执行呢? 我们可以将传给 then
的函数和新 promise
的 resolve
一起 push
到前一个 promise
的 callbacks
数组中,达到承前启后的效果:
- 承前:当前一个
promise
完成后,调用其 resolve
变更状态,在这个 resolve
里会依次调用 callbacks
里的回调,这样就执行了 then
里的方法了 - 启后:上一步中,当
then
里的方法执行完成后,返回一个结果,如果这个结果是个简单的值,就直接调用新 promise
的 resolve
,让其状态变更,这又会依次调用新 promise
的 callbacks
数组里的方法,循环往复。。如果返回的结果是个 promise
,则需要等它完成之后再触发新 promise
的 resolve
,所以可以在其结果的 then
里调用新 promise
的 resolve
| then(onFulfilled, onReject){ |
| |
| const self = this; |
| return new MyPromise((resolve, reject) => { |
| |
| let fulfilled = () => { |
| try{ |
| const result = onFulfilled(self.value); |
| return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); |
| }catch(err){ |
| reject(err) |
| } |
| } |
| |
| let rejected = () => { |
| try{ |
| const result = onReject(self.reason); |
| return result instanceof MyPromise? result.then(resolve, reject) : reject(result); |
| }catch(err){ |
| reject(err) |
| } |
| } |
| switch(self.status){ |
| case PENDING: |
| self.onFulfilledCallbacks.push(fulfilled); |
| self.onRejectedCallbacks.push(rejected); |
| break; |
| case FULFILLED: |
| fulfilled(); |
| break; |
| case REJECT: |
| rejected(); |
| break; |
| } |
| }) |
| } |
注意:
- 连续多个
then
里的回调方法是同步注册的,但注册到了不同的 callbacks
数组中,因为每次 then
都返回新的 promise
实例(参考上面的例子和图) - 注册完成后开始执行构造函数中的异步事件,异步完成之后依次调用
callbacks
数组中提前注册的回调
字符串最长的不重复子串
题目描述
| 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。 |
| |
| |
| 示例 1: |
| |
| 输入: s = "abcabcbb" |
| 输出: 3 |
| 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 |
| |
| 示例 2: |
| |
| 输入: s = "bbbbb" |
| 输出: 1 |
| 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 |
| |
| 示例 3: |
| |
| 输入: s = "pwwkew" |
| 输出: 3 |
| 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 |
| 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。 |
| |
| 示例 4: |
| |
| 输入: s = "" |
| 输出: 0 |
答案
| const lengthOfLongestSubstring = function (s) { |
| if (s.length === 0) { |
| return 0; |
| } |
| |
| let left = 0; |
| let right = 1; |
| let max = 0; |
| while (right <= s.length) { |
| let lr = s.slice(left, right); |
| const index = lr.indexOf(s[right]); |
| |
| if (index > -1) { |
| left = index + left + 1; |
| } else { |
| lr = s.slice(left, right + 1); |
| max = Math.max(max, lr.length); |
| } |
| right++; |
| } |
| return max; |
| }; |
Promise.all
Promise.all
是支持链式调用的,本质上就是返回了一个Promise实例,通过resolve
和reject
来改变实例状态。
| Promise.myAll = function(promiseArr) { |
| return new Promise((resolve, reject) => { |
| const ans = []; |
| let index = 0; |
| for (let i = 0; i < promiseArr.length; i++) { |
| promiseArr[i] |
| .then(res => { |
| ans[i] = res; |
| index++; |
| if (index === promiseArr.length) { |
| resolve(ans); |
| } |
| }) |
| .catch(err => reject(err)); |
| } |
| }) |
| } |
Object.is
Object.is
解决的主要是这两个问题:
| +0 === -0 |
| NaN === NaN |
| const is= (x, y) => { |
| if (x === y) { |
| |
| return x !== 0 || y !== 0 || 1/x === 1/y; |
| } else { |
| return x !== x && y !== y; |
| } |
| } |
手写 apply 函数
apply 函数的实现步骤:
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
- 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
- 将函数作为上下文对象的一个属性。
- 判断参数值是否传入
- 使用上下文对象来调用这个方法,并保存返回结果。
- 删除刚才新增的属性
- 返回结果
| |
| Function.prototype.myApply = function(context) { |
| |
| if (typeof this !== "function") { |
| throw new TypeError("Error"); |
| } |
| let result = null; |
| |
| context = context || window; |
| |
| context.fn = this; |
| |
| if (arguments[1]) { |
| result = context.fn(...arguments[1]); |
| } else { |
| result = context.fn(); |
| } |
| |
| delete context.fn; |
| return result; |
| }; |