Preface
Now the front-end threshold is higher and higher, and it's not as simple as writing pages. Modularity, automation, cross end development and so on are gradually becoming requirements, but these need to be built on our solid foundation. No matter how the framework and model change, only by laying down the basic principles can we quickly adapt to the changes in the market. Here are some common source code implementations:
- call implementation
- bind implementation
- new implementation
- instanceof implementation
- Object.create implementation
- Deep copy implementation
- Publish and subscribe mode
call
call is used to change the direction of this function and execute the function
Generally, whoever calls a function points to this. Using this feature, we can change the direction of this function by calling the function as the attribute of the object, which is called implicit binding. apply implements the same principle by changing the input parameter form.
let obj = { name: 'JoJo' } function foo(){ console.log(this.name) } obj.fn = foo obj.fn() // log: JOJO
Realization
Function.prototype.mycall = function () { if(typeof this !== 'function'){ throw 'caller must be a function' } let othis = arguments[0] || window othis._fn = this let arg = [...arguments].slice(1) let res = othis._fn(...arg) Reflect.deleteProperty(othis, '_fn') //Remove the fn attribute return res }
Use
let obj = { name: 'JoJo' } function foo(){ console.log(this.name) } foo.mycall(obj) // JoJo
bind
bind is used to change the direction of this function and return a function
Note:
- this as a constructor call points to
- Maintain prototype chain
Function.prototype.mybind = function (oThis) { if(typeof this != 'function'){ throw 'caller must be a function' } let fThis = this //Array.prototype.slice.call converts a class array to an array let arg = Array.prototype.slice.call(arguments,1) let NOP = function(){} let fBound = function(){ let arg_ = Array.prototype.slice.call(arguments) // new binding level is higher than explicit binding // When called as a constructor, keep the pointer unchanged // Use instanceof to determine whether it is a constructor call return fThis.apply(this instanceof fBound ? this : oThis, arg.concat(arg_)) } // Maintenance prototype if(this.prototype){ NOP.prototype = this.prototype } fBound.prototype = new NOP() return fBound }
Use
let obj = { msg: 'JoJo' } function foo(msg){ console.log(msg + '' + this.msg) } let f = foo.mybind(obj) f('hello') // hello JoJo
new
new uses the constructor to create an instance object and add this property and method to the instance object
new process:
- create new object
- New object? proto? Points to constructor prototype
- New object add attribute method (this points to)
- Returns the new object this points to
function new_(){ let fn = Array.prototype.shift.call(arguments) if(typeof fn != 'function'){ throw fn + ' is not a constructor' } let obj = {} obj.__proto__ = fn.prototype let res = fn.apply(obj, arguments) return typeof res === 'object' ? res : obj }
instanceof
instanceof determines whether the prototype on the left exists in the prototype chain on the right.
Implementation idea: look up the prototype layer by layer. If the final prototype is null, it is proved that it does not exist in the prototype chain, otherwise it exists.
function instanceof_(left, right){ left = left.__proto__ while(left !== right.prototype){ left = left.__proto__ // Find the prototype and judge again while if(left === null){ return false } } return true }
Object.create
Object.create creates a new object, and uses the existing object to provide the "proto" of the newly created object. The second optional parameter is the property description object
function objectCreate_(proto, propertiesObject = {}){ if(typeof proto !== 'object' || typeof proto !== 'function' || proto !== null){ throw('Object prototype may only be an Object or null:'+proto) } let res = {} res.__proto__ = proto Object.defineProperties(res, propertiesObject) return res }
Deep copy
A deep copy creates an identical copy of an object with different reference addresses. When you want to use an object, but do not want to modify the original object, deep copy is a good choice. A basic version is implemented here, which only makes deep copies of objects and arrays.
Implementation idea: traverse the object, use recursion to copy the reference type, and assign the basic type directly
function deepClone(origin) { let toStr = Object.prototype.toString let isInvalid = toStr.call(origin) !== '[object Object]' && toStr.call(origin) !== '[object Array]' if (isInvalid) { return origin } let target = toStr.call(origin) === '[object Object]' ? {} : [] for (const key in origin) { if (origin.hasOwnProperty(key)) { const item = origin[key]; if (typeof item === 'object' && item !== null) { target[key] = deepClone(item) } else { target[key] = item } } } return target }
Publish and subscribe mode
Publish and subscribe mode can realize complete decoupling between modules in actual development, and modules only need to pay attention to event registration and triggering.
The publish and subscribe mode implements EventBus:
class EventBus{ constructor(){ this.task = {} } on(name, cb){ if(!this.task[name]){ this.task[name] = [] } typeof cb === 'function' && this.task[name].push(cb) } emit(name, ...arg){ let taskQueen = this.task[name] if(taskQueen && taskQueen.length > 0){ taskQueen.forEach(cb=>{ cb(...arg) }) } } off(name, cb){ let taskQueen = this.task[name] if(taskQueen && taskQueen.length > 0){ let index = taskQueen.indexOf(cb) index != -1 && taskQueen.splice(index, 1) } } once(name, cb){ function callback(...arg){ this.off(name, cb) cb(...arg) } typeof cb === 'function' && this.on(name, callback) } }
Use
let bus = new EventBus() bus.on('add', function(a,b){ console.log(a+b) }) bus.emit('add', 10, 20) //30