предисловие
Сейчас порог фронтенда становится все выше и выше, и это уже не так просто, как просто написание страниц. Модульность, автоматизация, кросс-энд разработка и т. д. постепенно становятся требованиями, но они должны быть построены на нашем прочном фундаменте. Как бы ни менялись рамки и модели, только заложив прочный фундамент в основных принципах, можно быстро адаптироваться к изменениям на рынке. Вот некоторые часто используемые реализации исходного кода:
- реализация вызова
- привязать реализацию
- новая реализация
- экземпляр реализации
- Реализация Object.create
- реализация глубокого копирования
- модель публикации-подписки
call
call
для изменения функцииthis
указать и выполнить функцию
В общем, кто вызывает функцию, тотthis
указать кому. Воспользовавшись этой возможностью, функцию можно изменить, вызвав функцию как атрибут объекта.this
указать, это называется неявным связыванием.apply
Чтобы добиться того же, просто измените форму входного параметра.
let obj = {
name: 'JoJo'
}
function foo(){
console.log(this.name)
}
obj.fn = foo
obj.fn() // log: JoJo
выполнить
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') //删除_fn属性
return res
}
использовать
let obj = {
name: 'JoJo'
}
function foo(){
console.log(this.name)
}
foo.mycall(obj) // JoJo
bind
bind
для изменения функцииthis
указывает и возвращает функцию
будь осторожен:
- это указывает на вызов конструктора
- Поддерживать цепочку прототипов
Function.prototype.mybind = function (oThis) {
if(typeof this != 'function'){
throw 'caller must be a function'
}
let fThis = this
//Array.prototype.slice.call 将类数组转为数组
let arg = Array.prototype.slice.call(arguments,1)
let NOP = function(){}
let fBound = function(){
let arg_ = Array.prototype.slice.call(arguments)
// new 绑定等级高于显式绑定
// 作为构造函数调用时,保留指向不做修改
// 使用 instanceof 判断是否为构造函数调用
return fThis.apply(this instanceof fBound ? this : oThis, arg.concat(arg_))
}
// 维护原型
if(this.prototype){
NOP.prototype = this.prototype
fBound.prototype = new NOP()
}
return fBound
}
использовать
let obj = {
msg: 'JoJo'
}
function foo(msg){
console.log(msg + '' + this.msg)
}
let f = foo.mybind(obj)
f('hello') // hello JoJo
new
new
Используйте конструктор для создания объекта экземпляра, добавьте к объекту экземпляраthis
свойства и методы
new
процесс:
- создать новый объект
- Новый объект __proto__ указывает на прототип конструктора
- Новый метод добавления свойства объекта (это указывает на)
- Возвращает новый объект, на который указывает this
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
}
В ES5 мы можем использоватьObject.create
Чтобы упростить процесс:
function new_(){
let fn = Array.prototype.shift.call(arguments)
if(typeof fn != 'function'){
throw fn + ' is not a constructor'
}
let obj = Object.create(fn.prototype)
let res = fn.apply(obj, arguments)
return typeof res === 'object' ? res : obj
}
instanceof
instanceof
Осуществляется, присутствует ли прототип слева в цепочке прототипа справа.
Идея реализации: Найти прототип слой за слоем, если окончательный прототипnull
, доказательство не существует в цепочке прототипов, иначе оно существует.
function instanceof_(left, right){
left = left.__proto__
while(left !== right.prototype){
left = left.__proto__ // 查找原型,再次while判断
if(left === null){
return false
}
}
return true
}
Object.create
Object.create
Создайте новый объект, используйте существующий объект, чтобы предоставить __proto__ вновь созданного объекта, вторым необязательным параметром является объект описания свойства.
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
}
глубокая копия
Глубокая копия создает идентичную копию объекта с другими ссылочными адресами. Глубокое копирование — хороший выбор, когда вы хотите использовать объект, но не хотите изменять исходный объект. Здесь реализована базовая версия, которая делает только глубокие копии объектов и массивов.
Идея реализации: обход объектов, использование рекурсии для продолжения копирования ссылочных типов и непосредственное назначение базовых типов
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
}
модель публикации-подписки
Модель «публикация-подписка» может обеспечить полное разделение между модулями в фактической разработке, и модулям нужно только обращать внимание на регистрацию и инициирование событий.
Модель публикации-подписки реализует 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 taskQueue = this.task[name]
if(taskQueue && taskQueue.length > 0){
taskQueue.forEach(cb=>{
cb(...arg)
})
}
}
off(name, cb){
let taskQueue = this.task[name]
if(taskQueue && taskQueue.length > 0){
if(typeof cb === 'function'){
let index = taskQueue.indexOf(cb)
index != -1 && taskQueue.splice(index, 1)
}
if(typeof cb === 'undefined'){
this.task[name].length = 0
}
}
}
once(name, cb){
let callback = (...arg) => {
this.off(name, callback)
cb(...arg)
}
typeof cb === 'function' && this.on(name, callback)
}
}
использовать
let bus = new EventBus()
bus.on('add', function(a,b){
console.log(a+b)
})
bus.emit('add', 10, 20) //30