前言
- 某某原生 Api 的实现方式,如果让你实现,你会怎么做 ?
- 你知道 new 具体做了哪些事儿嘛 ?
- call 是如何改变 this 的指向的 ?
- ......
有没有似曾相识 ~ 有没有苦不堪言 ~ 有没有 ?
new
引用 MDN 的描述就是:
- 创建一个空的JavaScript对象(即{})
- 空对象的_proto_指向构造函数的prototype,也就是将 obj.constructor 指向构造函数
- 将this指向创建的对象上下文
- 返回对象 5.(译注:关于对象的 constructor,参见 Object.prototype.constructor)
function _new(func) {
// 第一步 创建新对象
let obj= {};
// 第二步 空对象的_proto_指向构造函数的prototype
// 也就是将 obj.constructor 指向 func
obj.__proto__ = func.prototype;
// 前两步可以简写成
// let obj = Object.create(func.prototype)
// 第三步 使用 apply 将 this 指向 obj 对象上下文
let result = func.apply(obj);
// 第四步返回对象
if (result && (typeof (result) == "object" || typeof (result) == "function")) {
// 如果构造函数执行的结果返回的是一个对象,那么返回这个对象
return result;
}
// 如果构造函数返回的不是一个对象,返回创建的新对象
return obj;
}
call
call 的作用是指定 this 值调用某个方法,也可以说是将某个方法作用到指定的this指向上,逻辑也比较简单
- 在Function 原型上挂载_call 方法,参数接收要指定this的目标对象和方法执行的传参
- 将 this 指向目标对象 target
- 执行 fn , 传入参数
- 删除目标对象的自定义临时变量 fn , 因为多次调用
- 返回执行结果
Function.prototype._call = function (target,...arg) {
// 这里的 this 指向调用它的那个函数,就是 func._call(....) 里的 func 函数
target.fn = this;
// 到这里 fn 指向上边的 func 函数,this 指向调用它的对象 => target
let result = target.fn(...arg);
// 移除 fn , 因为每次调用都会重新创建
delete target.fn;
// 返回结果,其实就是一句话 this 指向调用它的那个对象
return result;
}
apply
apply 同理,只是传参是数组
Function.prototype._apply = function (target,arg) {
target.fn = this;
let result = target.fn(arg);
delete target.fn;
return result;
}
Array.map
map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值,方法按照原始数组元素顺序依次处理元素。逻辑也比较简单
- 在 Array 的原型上挂载一个_map方法,接收一个处理函数,处理函数接收三个值
- 原始数组的每一个项
- 下标
- 原始数组
- 声明结果数组 result , 用于返回新的数组
- 判断调用_map 的是不是数组,然后进行遍历,依次调用处理函数 push 进结果数组 result
- 返回新数组
Array.prototype._map = function (fn) {
var result = [];
if(this instanceof Array){
for(var i =0;i<this.length;i++){
result.push(fn(this[i],i,this))
}
}
return result;
}
注,这里的 this 是原始数组,因为 this 指向调用它的那个对象
instanceof
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。逻辑是这样的
- target 实例对象 , pro 构造函数,proto 是对象的隐式原型 , prototype 是函数的显示原型
- 使用一个 while 无线循环,判断构造函数的显示原型是否出现在实例对象的原型链上
- 每循环一次,将实例对象的__proto__赋值构造函数的当前隐式原型
- 直到原型链顶端 null 或者出现相等,返回结果
function _instance_of(target,pro) {
while (true) {
if(target.__proto__ === pro.prototype){
return true
}
target.__proto__ = pro.__proto__;
if(target.__proto__ == null) {
return false;
}
}
}
如果这里对原型,原型链不太了解的童鞋,可以查看我的一篇Javascript 核心进阶,里边详细描述了原型,原项链Javascript 核心进阶
防抖
函数防抖其实是在规定时间内,频繁触发该事件,以最后一次触发为准; 实现方式就是创建一个定时器,每次执行的时候,清除旧定时器,并创建一个新的定时器重新记录时间
//参数:要执行的函数和间隔的毫秒数
function debounce(fn, time) {
var timer = null; // 声明 timer
return function() {
clearTimeout(timer) // 清除定时器
timer = setTimeout(function() { // 创建定时器赋给局部变量 timer
fn.apply(this)
}, time)
}
}
// 在规定时间内,触发多次,以最后那一次为准,
// 因为每一次触发,旧的定时还没有执行就被清除了,又创建了新的定时器
节流
函数节流其实是在规定时间内,事件只被触发一次 ; 实现方式就是通过时间戳,
当前的时间戳 - 最后一次执行的时间戳 > 设置规定时间,则生效一次;也就说在频繁触发的情况下,该事件触发的频率会降低
// 参数:要执行的函数和毫秒数
function throttle(fn, time) {
var lastTime = 0; // 初始化最后一次执行时间
return function() {
var nowTime = Date.now(); // 获取当前时间毫秒数
if (nowTime - lastTime > time) { // 当前时间毫秒数 - 最后一次执行时间毫秒数 > 设置规定时间
fn.call(this);
lastTime = nowTime; // 更新最后一字执行时间毫秒数
}
}
}
// 每一次执行,当前时间就会减去最后一次执行时间
// 如果大于设置时间,就触发一次,有效的节省了事件触发频率。
发布订阅
通俗一点说就是一个发布通知,一个接收通知做对应的事儿
先定义一个类 EventBus
class EventBus {
constructor() {
this.events = this.events || {};
}
}
定义一个发布者
// 挂载在类 EventBus 的原型上
// type : 发布的事件类型,也可以说是事件名称
// args : 携带参数
EventBus.prototype.emit = function (type, ...args) {
const e = this.events[type]; // 返回一个数组
if (e instanceof Array) {
e.forEach( c => {
// 改变 this 调用对应的发布事件
c.apply(this, args);
})
}
};
定义一个订阅者
// type : 订阅的事件类型,也可以说是事件名称
// 回调函数 : dosomething
EventBus.prototype.on = function (type, fun) {
const e = this.events[type]; // 同上
if (!e) {
// 如果没有该订阅事件,则自定添加,并赋值对应的操作
this.events[type] = [fun];
} else {
e.push(fun);
}
};
定义一个移除事件
// 移除对应的事件
EventBus.prototype.off = function (type) {
delete this.events[type];
};
实践
// new 一个实例
const ev = new EventBus();
//订阅 fn 事件 , 并接收参数
ev.on("fn",(args) => {console.log("订阅到了 fn 事件",args)})
// 发布 fn 事件 , 并传测试参数
ev.emit("fn",{arg:"测试参数"})
//移除 fn 事件
ev.off("fn")
// 打印看结果
console.log(ev);
这篇文章会持续迭代 ~ ~ ~ biu biu biu ~ ~ ~
欢迎点赞,小小鼓励,大大成长