下面介绍一些常用的源码实现
实现一个深拷贝
实现 new 操作符
实现instanceof
防抖
节流
函数柯里化
实现 call,apply
实现 bind
实现Ajax
Promise.all
实现一个深拷贝
深拷贝为对象创建一个相同的副本,两者的引用地址不同。
一般方法:JSON的序列化和反序列化(JSON.Parse(JSON.stringify(obj))),但是这种方式有两个不足:
1.undefined, null, symbol 类型的值会被删除。
2.碰见循环引用的时候回报错
循环引用解决方案:可以使用一个WeakMap结构存储已经被拷贝的对象,每一次进行拷贝的时候就先向WeakMap查询该对象是否已经被拷贝,如果已经被拷贝则取出该对象并返回.
下面我们简单实现一个深拷贝
function isObj(obj) {
return (typeof obj === 'object' || typeof obj === 'function') && obj !== null
}
function deepCopy(obj, hash = new WeakMap()) {
if(hash.has(obj)) return hash.get(obj)
let cloneObj = Array.isArray(obj) ? [] : {}
hash.set(obj, cloneObj)
for (let key in obj) {
cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key], hash) : obj[key];
}
return cloneObj
}
// 测试
let o1 = {
a: 1,
b: null,
c: undefined,
d: "hello",
e: [111,222,333,444],
f: true,
s: Symbol('ww'),
}
o1.oo = o1 // 循环引用
let o2 = deepCopy(o1)
console.log(o2)
实现 new 操作符
大体可以分为3步
1.创建一个新的对象
2.将新对象的的 _ proto _ 指向 构造函数的 prototype 对象
3.执行构造函数,并将 this 指向新的对象
function myNew(fn, ...args) {
if(typeof fn !== 'function') {
throw fn + 'is not a constructor'
}
let obj = {};
obj.__proto__ = fn.prototype;
// let obj = Object.create(null) 也使用 ES5 的 Object.create() 来代替上面两行代码
let res = fn.apply(obj, args);
return res instanceof Object ? res : obj;
// return 的时候需要对返回的东西进行判断,若是对象则返回,如果不是对象,则返回新创建的对象。
}
// 测试
function Person(name, age) {
this.name = name;
this.age = age
}
console.log(myNew(Person, "dingding", 100))
实现instanceof
a instanceof b 用于判断 构造函数 b 的 prototype 是否存在于 a 的原型链上
常用于判断引用类型
function myInstanceof(left, right) {
left = left.__proto__;
while(left !== right.prototype) {
left = left.__proto__
if(left === null)
return false
}
return true;
}
// 测试
var a = []
var b = {}
function Foo(){}
var c = new Foo()
function child(){}
function father(){}
child.prototype = new father()
var d = new child()
console.log(instance_of(a, Array)) // true
console.log(instance_of(b, Object)) // true
console.log(instance_of(b, Array)) // false
console.log(instance_of(a, Object)) // true
console.log(instance_of(c, Foo)) // true
console.log(instance_of(d, child)) // true
防抖
在事件被触发n秒后再执行回调函数,如果在这n秒内又被触发,则重新计时
function ajax() {
console.log("我是 ajax")
}
function debounce(cb, delay) {
let timer = null;
return function(args) {
let that = this; // 获得函数的作用域
clearTimeout(timer); // 每次事件被触发,都会清除当前的timeer,然后重写设置超时调用
timer = setTimeout(function(){
cb.apply(that, args);
}, delay)
}
}
// 测试,模拟一个在 2100毫秒内每隔半秒就重复触发的事件
var rsu = debounce(ajax, 1000,);
let itv = setInterval(()=> {
rsu(888)
}, 500)
setTimeout(()=> {
clearInterval(itv)
}, 2100)
节流
使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数
function jl(fn, time, ...args) {
let lock = false;
return function() {
console.log(1111)
if(lock) return;
setTimeout(() => {
fn.apply(this, ...args);
lock = true;
}, time)
}
}
function ajax() {
console.log(999999999)
}
// 测试
var fun = jl(ajax, 2000);
let st = setInterval(()=> {
fun()
}, 600)
setTimeout(()=> {
clearInterval(st)
}, 2000)
函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
函数柯里化
实现 sum(11, 22, 33) => sum(11, 22)(33)() = 66 的效果
传入参数时,不执行,而是先记忆起来,延迟计算,什么时候想要计算,直接 sum() 就行
var currying = function (fn) {
var args = []
return function() {
if(arguments.length === 0) {
return fn.apply(this, args)
}
// [].slice.call(arguments) 将函数的实际参数转化成数组
Array.prototype.push.apply(args, [].slice.call(arguments))
// arguments.callee 返回当前匿名函数
// rguments 的主要用途是保存函数参数, 但这个对象还有一个名叫 callee 的属性,
// 返回正被执行的 Function 对象,也就是所指定的 Function 对象的正文,这有利于匿名函数的递归或者保证函数的封装性
return arguments.callee
}
}
var tempFun = function() {
var sum = 0;
for(var i = 0; i < arguments.length; i++) {
sum += arguments[i]
}
return sum
}
// 测试
var sum = currying(tempFun);
sum(11,22)
sum(33)
sum()
call 和 apply
用法:https://www.jianshu.com/p/aa2eeecd8b4f
原本操作
var o1 = {
name: 'dingding'
}
function say() {
console.log(222, this.name)
}
o1.fun = say;
o1.fun() // 222 dingding
如果不使用 o1.fun = say 进项绑定
手写apply
Function.prototype.myapply = function(context, [...args]) {
context = context || window; // 判断上下文是否为空,若为空则指向window 对象
context.fn = this;
context.fn(...args);
delete context.fn; // 要删除fn,不能影响原对象
}
// 测试
var o1 = {
name: 'dingding'
}
function say() {
console.log(222, this.name)
}
say.apply(o1)
手写call
Function.mycall = function(context) {
context = context || window;
let args = [...arguments].slice(1); // 截取arguments,从下标1开始的
context.fn = this;
context.fn(args);
delete context.fn;
}
bind
bind()方法主要就是将函数绑定到某个对象,bind()会创建一个函数,函数体内的this对象的值会被绑定到传入bind()第一个参数的值,
例如,f.bind(obj),实际上可以理解为obj.f(),这时,f函数体内的this自然指向的是obj
bind 基础用法:https://www.jianshu.com/p/25a855c01896
下面实现myBind 方法可以绑定对象,可以传参, 还要注意函数柯里化的情况
Function.prototype.myBind = function(context) {
const self = this;
let args = [...arguments].slice(1); // args: [7, 8]
return function() {
// 考虑函数柯里化的情况
let newArgs = [...arguments]; //newArgs: [9]
return self.apply(context, newArgs.concat(args))
}
}
// 测试
function a(m, n, o){
return this.name + ' ' + m + ' ' + n + ' ' + o;
}
var b = {name : 'kong'};
console.log(a.myBind(b, 7, 8)(9));
实现Ajax
实现一个ajax其实主要是一个XMLHttpRequest对象以及其API方法的一个使用的问题。而在这里我建议尽量封装成promise的形式,方便使用。
function ajax({url, methods, body, headers}) {
return new Promise((resolve, reject) => {
let request = new XMLHttpRequest();
request.open(url, methods);
for(let key in headers) {
let value = headers[key]
request.setRequestHeader(key, value);
}
request.send(body)
request.onreadystatechange = () => {
if(request.readyState === 4) {
if(request.status >= '200' && request.status < 300) {
resolve(request.responeText);
} else {
reject(request)
}
}
}
})
}
Promise.all
// 假设一下Promise其他所有函数都正常工作,但Promise.all功能失效了,我们现在就要为程序重写一个Promise.all
Promise.all = function(promises) {
let results = [];
let promiseCount = 0;
return new Promise((resolve, reject) => {
for(let i = 0; i < promises.length; i++) { // 使用let保证promise顺序执行
Promise.resolve(promises[i]).then(res => { // 传入的 promises 元素可能不是 Promise 类型的,使用 Promise.resolve(arr[i]) 转换。
promiseCount++;
results[i] = res;
if(promiseCount === promises.length) { // 当所有函数都正确执行了,resolve输出所有返回结果。
resolve(results);
}
}, err => {
reject(err);
})
}
})
}
let p1 = new Promise((resolve) => {
setTimeout(()=> {
console.log("p1 resolve");
resolve(111);
}, 1000)
})
let p2 = new Promise((resolve) => {
console.log('p2 resolve');
resolve(222);
})
let p3 = new Promise((resolve) => {
console.log('p3 resolve');
resolve(333);
})
var p = Promise.all([p1, p2, p3]);
console.log(1212, p)
p.then(e => {
console.log(e)
});
若有错误,欢迎留言~~
网友评论