前台开发岗位面试中常考的源代码实现
手动撸个call/apply/bind
实现call
来看下call的原生体现形式:
function test(arg1, arg2) { console.log(arg1, arg2) console.log(this.a, this.b)}run.call({ a: 'a', b: 'b'}, 1, 2)web前台开发学习Q-q-u-n: 784783012 ,分享开发工具,零基础,进阶视频教程,希望新手少走弯路好了,开始手动实现我们的call2。在实现的过程有个关键:
假如一个函数作为一个对象的属性,那么通过对象的.运算符调用此函数,this就是此对象
let obj = { a: 'a', b: 'b', test: function (arg1, arg2) { console.log(arg1, arg2) // this.a 就是 a; this.b 就是 b console.log(this.a, this.b) }}obj.test(1, 2)web前台开发学习Q-q-u-n: 767273102 ,分享开发工具,零基础,进阶视频教程,希望新手少走弯路知道了实现关键,下面就是我们模拟的call:
Function.prototype.call2 = function(context) { if(typeof this !== 'function') { throw new TypeError('Error') } // 默认上下文是window context = context || window // 保存默认的fn const { fn } = context // 前面讲的关键,将函数本身作为对象context的属性调用,自动绑定this context.fn = this const args = [...arguments].slice(1) const result = context.fn(...args) // 恢复默认的fn context.fn = fn return result}// 以下是测试代码function test(arg1, arg2) { console.log(arg1, arg2) console.log(this.a, this.b)}test.call2({ a: 'a', b: 'b'}, 1, 2)实现apply
apply和call实现相似,只是传入的参数形式是数组形式,而不是逗号分隔的参数序列。
因而,借助es6提供的...运算符,即可以很方便的实现数组和参数序列的转化。
Function.prototype.apply2 = function(context) { if(typeof this !== 'function') { throw new TypeError('Error') } context = context || window const { fn } = context context.fn = this let result if(Array.isArray(arguments[1])) { // 通过...运算符将数组转换为用逗号分隔的参数序列 result = context.fn(...arguments[1]) } else { result = context.fn() } context.fn = fn return result}/** * 以下是测试代码 */function test(arg1, arg2) { console.log(arg1, arg2) console.log(this.a, this.b)}test.apply2({ a: 'a', b: 'b'}, [1, 2])web前台开发学习Q-q-u-n: 784783012 ,分享开发工具,零基础,进阶视频教程,希望新手少走弯路实现bind
bind的实现有点意思,它有两个特点:
- 本身返回一个新的函数,所以要考虑
new的情况 - 可以“保留”参数,内部实现了参数的拼接
Function.prototype.bind2 = function(context) { if(typeof this !== 'function') { throw new TypeError('Error') } const that = this // 保留之前的参数,为了下面的参数拼接 const args = [...arguments].slice(1) return function F() { // 假如被new创立实例,不会被改变上下文! if(this instanceof F) { return new that(...args, ...arguments) } // args.concat(...arguments): 拼接之前和现在的参数 // 注意:arguments是个类Array的Object, 用解构运算符..., 直接拿值拼接 return that.apply(context, args.concat(...arguments)) }}/** * 以下是测试代码 */function test(arg1, arg2) { console.log(arg1, arg2) console.log(this.a, this.b)}const test2 = test.bind2({ a: 'a', b: 'b'}, 1) // 参数 1test2(2) // 参数 2实现深拷贝函数
实现一个对象的深拷贝函数,需要考虑对象的元素类型以及对应的处理方案:
- 基础类型:这种最简单,直接赋值就可
- 对象类型:递归调用拷贝函数
- 数组类型:这种最难,由于数组中的元素可能是基础类型、对象还可能数组,因而要专门做一个函数来解决数组的深拷贝
/** * 数组的深拷贝函数 * @param {Array} src * @param {Array} target */function cloneArr(src, target) { for(let item of src) { if(Array.isArray(item)) { target.push(cloneArr(item, [])) } else if (typeof item === 'object') { target.push(deepClone(item, {})) } else { target.push(item) } } return target}/** * 对象的深拷贝实现 * @param {Object} src * @param {Object} target * @return {Object} */function deepClone(src, target) { const keys = Reflect.ownKeys(src) let value = null for(let key of keys) { value = src[key] if(Array.isArray(value)) { target[key] = cloneArr(value, []) } else if (typeof value === 'object') { // 假如是对象而且不是数组, 那么递归调用深拷贝 target[key] = deepClone(value, {}) } else { target[key] = value } } return target}这段代码是不是比网上看到的多了很多?由于考虑很周全,请看下面的测试用例:
// 这个对象a是一个囊括以上所有情况的对象let a = { age: 1, jobs: { first: "FE" }, schools: [ { name: 'shenda' }, { name: 'shiyan' } ], arr: [ [ { value: '1' } ], [ { value: '2' } ], ]};let b = {}deepClone(a, b)a.jobs.first = 'native'a.schools[0].name = 'SZU'a.arr[0][0].value = '100'console.log(a.jobs.first, b.jobs.first) // output: native FEconsole.log(a.schools[0], b.schools[0]) // output: { name: 'SZU' } { name: 'shenda' }console.log(a.arr[0][0].value, b.arr[0][0].value) // output: 100 1console.log(Array.isArray(a.arr[0])) // output: true看到测试用例,应该会有人奇怪为什么最后要输出Array.isArray(a.arr[0])。这主要是由于网上很多实现方法没有针对array做解决,直接将其当成object,这样拷贝后尽管值没问题,但是array的元素会被转化为object。这显然是错误的做法。
而上面所说的深拷贝函数就处理了这个问题。
基于ES5/ES6实现“双向绑定”
要想实现,就要先看看什么是“双向数据绑定”,它和“单向数据绑定”有什么区别?这样才能知道要实现什么效果嘛。
双向绑定:视图(View)的变化能实时让数据模型(Model)发生变化,而数据的变化也能实时升级到视图层。
单向数据绑定:只有从数据到视图这一方向的关系。
ES5的Object.defineProperty
<!DOCTYPE html><html lang="en"><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>Document</title> <script> const obj = { value: '' } function onKeyUp(event) { obj.value = event.target.value } // 对 obj.value 进行阻拦 Object.defineProperty(obj, 'value', { get: function() { return value }, set: function(newValue) { value = newValue document.querySelector('#value').innerHTML = newValue // 升级视图层 document.querySelector('input').value = newValue // 数据模型改变 } }) </script></head><body> <p> 值是:<span id="value"></span> </p> <input type="text" onkeyup="onKeyUp(event)"></body></html>web前台开发学习Q-q-u-n: 784783012 ,分享开发工具,零基础,进阶视频教程,希望新手少走弯路ES6的Proxy
随着,vue3.0放弃支持了IE浏览器。而且Proxy兼容性越来越好,能支持13种劫持操作。
因而,vue3.0选择使用Proxy来实现双向数据绑定,而不再使用Object.defineProperty。
<!DOCTYPE html><html lang="en"><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>Document</title> <script> const obj = {} const newObj = new Proxy(obj, { get: function(target, key, receiver) { return Reflect.get(target, key, receiver) }, set: function(target, key, value, receiver) { if(key === 'value') { document.querySelector('#value').innerHTML = value document.querySelector('input').value = value } return Reflect.set(target, key, value, receiver) } }) function onKeyUp(event) { newObj.value = event.target.value } </script></head><body> <p> 值是:<span id="value"></span> </p> <input type="text" onkeyup="onKeyUp(event)"></body></html>instanceof原理与实现
instanceof是通过原型链来进行判断的,所以只需不断地通过访问__proto__,即可以拿到构造函数的原型prototype。直到null中止。
/** * 判断left是不是right类型的对象 * @param {*} left * @param {*} right * @return {Boolean} */function instanceof2(left, right) { let prototype = right.prototype; // 沿着left的原型链, 看看能否有何prototype相等的节点 left = left.__proto__; while(1) { if(left === null || left === undefined) { return false; } if(left === prototype) { return true; } left = left.__proto__; }}/** * 测试代码 */console.log(instanceof2([], Array)) // output: truefunction Test(){}let test = new Test()console.log(instanceof2(test, Test)) // output: true实现支持绑定、解绑和派发的事件类
实现思路:这里涉及了“订阅/发布模式”的相关知识。参考addEventListener(type, func)和removeEventListener(type, func)的具体效果来实现就可。
// 数组置空:// arr = []; arr.length = 0; arr.splice(0, arr.length)class Event { constructor() { this._cache = {}; } // 注册事件:假如不存在此种type,创立相关数组 on(type, callback) { this._cache[type] = this._cache[type] || []; let fns = this._cache[type]; if (fns.indexOf(callback) === -1) { fns.push(callback); } return this; } // 触发事件:对于一个type中的所有事件函数,均进行触发 trigger(type, ...data) { let fns = this._cache[type]; if (Array.isArray(fns)) { fns.forEach(fn => { fn(...data); }); } return this; } // 删除事件:删除事件类型对应的array off(type, callback) { let fns = this._cache[type]; // 检查能否存在type的事件绑定 if (Array.isArray(fns)) { if (callback) { // 卸载指定的回调函数 let index = fns.indexOf(callback); if (index !== -1) { fns.splice(index, 1); } } else { // 一律清空 fns = []; } } return this; }}// 以下是测试函数const event = new Event();event .on("test", a => { console.log(a); }) .trigger("test", "hello");web前台开发学习Q-q-u-n: 784783012 ,分享开发工具,零基础,进阶视频教程,希望新手少走弯路1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 前台开发岗位面试中常考的源代码实现