前端手写ajax,前端手写代码

下面介绍一些常用的源码实现

实现一个深拷贝

实现 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

原本操作

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)

});

若有错误,欢迎留言~~

1dfa2800161e

image.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值