(前端编程题:(手撕代码)

1.节流和防抖

函数节流:一个函数执行一次后,只有大于设定的执行周期后才会执行第二次

function throttle(fn, delay) {
    // 记录上一次函数触发的时间
    var lastTime = 0;
    return function() {
        // 记录当前函数触发的时间
        var nowTime = Date.now();
        if (nowTime - lastTime > delay) {
        // 修正this指向问题
            fn.call(this);
        // 同步时间
          lastTime = nowTime;
        }
    }
}

作者:浪里行舟
链接:https://juejin.im/post/6844903727900409870
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
function throttle(fn, interval = 300) {
    let canRun = true;
    return function () {
        if (!canRun) return;
        canRun = false;
        setTimeout(() => {
            fn.apply(this, arguments);
            canRun = true;
        }, interval);
    };
}

函数的节流就是通过闭包保存一个标记(canRun = true),在函数的开头判断这个标记是否为 true,如果为 true 的话就继续执行函数,否则则 return 掉,判断完标记后立即把这个标记设为 false,然后把外部传入的函数的执行包在一个 setTimeout 中,最后在 setTimeout 执行完毕后再把标记设置为 true(这里很关键),表示可以执行下一次的循环了。当 setTimeout 还未执行的时候,canRun 这个标记始终为 false,在开头的判断中被 return 掉

防抖函数:一个需要频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效。

function debounce(fn, delay) {
    // 记录上一次的延时器
   var timer = null;
    return function() {
    // 清除上一次延时器
    clearTimeout(timer)
    timer = setTimeout(function() {
            fn.apply(this)
        }, delay)
    }
}

作者:浪里行舟
链接:https://juejin.im/post/6844903727900409870
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
function debounce(fn, interval = 300) {
    let timeout = null;
    return function () {
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            fn.apply(this, arguments);
        }, interval);
    };
}

其实函数防抖的原理也非常地简单,通过闭包保存一个标记来保存 setTimeout 返回的值,每当用户输入的时候把前一个 setTimeout clear 掉,然后又创建一个新的 setTimeout,这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数了。

2.深拷贝

热门的函数库lodash,也有提供_.cloneDeep用来做深拷贝
jquery 提供一个$.extend可以用来做深拷贝
JSON.parse(JSON.stringify()),不能拷贝函数类型
手写递归方法

//定义检测数据类型的功能函数
function checkedType(target) {
  return Object.prototype.toString.call(target).slice(8, -1)
}
//实现深度克隆---对象/数组
function clone(target) {
  //判断拷贝的数据类型
  //初始化变量result 成为最终克隆的数据
  let result,
    targetType = checkedType(target)
  if (targetType === 'Object') {
    result = {}
  } else if (targetType === 'Array') {
    result = []
  } else {
    return target
  }
  //遍历目标数据
  for (let i in target) {
    //获取遍历数据结构的每一项值。
    let value = target[i]
    //判断目标结构里的每一值是否存在对象/数组
    if (checkedType(value) === 'Object' || checkedType(value) === 'Array') {
      //对象/数组里嵌套了对象/数组
      //继续遍历获取到value值
      result[i] = clone(value)
    } else {
      //获取到value值是基本的数据类型或者是函数。
      result[i] = value
    }
  }
  return result
}

3.js的连续赋值

来自:https://www.cnblogs.com/Gavin257/p/9562214.html
首先来一个经典案例:

var a = {n: 1}; 
var b = a;
a.x = a = {n: 2}; 
console.log(a.x); //undefined 
console.log(b.x); //{n: 2} 

说明:
1)此处的a,b是引用类型
2)在javascript中字段访问操作符".“的优先级高于赋值操作符”="
3)出现多个赋值操作符"="时,运算顺序为从右向左

第一行和第二行执行后:a和b同时指向同一地址(存放对象{n:1}
在这里插入图片描述
第三行最先执行的是a.x
在这里插入图片描述
第三行然后执行:
a = {n: 2},使得a指向另一个地址,让a指向对象{n:2}的引用
在这里插入图片描述
第三行最后一步:就是最左边的赋值
(a.x )= (a = {n: 2});
实际上是给对象
{
n:1
x:undefined
}
中的x赋值,
{
n:1
x:{n:2}
}
在这里插入图片描述
总结:在运行完上述命令后,变量a指向了新对象{n : 2};变量b 的地址没有发生改变,因而仍指向修改后的对象{n: 1;x: {n : 2}}。
4.闭包和作用域
来自:https://www.baidu.com/link?url=EQyHYLvrPKsw4ekagYIjv7aThqd0zfpWN_ptzT0E-xnQCbvvi_tICqqg5R0GyiyAYmr-8rffvjgsn_bHd1OkSa&wd=&eqid=b6ccc59500199d1d000000065f425808

  var a = 0,  
            b = 0;
        function A(a) {
            A = function (b) {
                console.log(a + b++)
            }
            console.log(a++)
        }
        A(1)//1
        A(2)//4

闭包机制:闭包创建后,可以保存创建时的活动对象。
自加操作符:++,当++作为后缀操作符时,调用++的表达式的值为自加前的自加对象的值。

此处说明 ++操作符的特性。

var i = 0;
var eg = i++
console.log(i, eg) // 1 0

第一次调用A时,执行到console.log(a++)时,a已经完成自加,此时a的值为2,a++的值为1。

A(1)=1

第二次调用A时,A已经被重新赋值,指向了一个新的函数引用;

由于标识符A是在全局作用域定义的,所以在函数内部被重新赋值,在全局作用域也可以访问到重新赋值后的函数。

此时,也创建了一个闭包,该闭包保存了创建环境的活动对象。

此时活动对象:{ a: 2 },同时,根据传入的数值2,确定 b = 2,b++值为3。

执行到 console.log(a + b++),故而输出4

4.实现new 操作符

来自:https://blog.csdn.net/q1424966670/article/details/92839918
要手动实现一个 new 操作符,首先要知道 new 操作符都做了什么事,即构造函数的内部原理:
前置知识:

作者:刘狗蛋
链接:https://www.zhihu.com/question/34183746/answer/124279182
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

__proto__ 、prototype傻傻分不清楚? 记住以下两点:1. __proto__是每个对象都有的一个属性,而prototype是函数才会有的属性。2. __proto__指向的是当前对象的原型对象,而prototype指向的,是以当前函数作为构造函数构造出来的对象的原型对象。看起来有点绕,我 show you the code,下面我们用右手作为原型来给自己构造一个女朋友://在JavaScript的世界中,所有的函数都能作为构造函数,构造出一个对象
   //下面我给自己构造一个女朋友
   function GirlFriend () {
     this.name = "Alice";
   }
   //现在我设置GirlFriend()这个函数的prototype属性
   //一般来说直接用匿名的对象就行,我这里是为了方便理解,
   //先定义一个hand对象再把hand赋值给GirlFriend()的prototype
   var hand = {
     whichOne: "right hand",
     someFunction: function(){
       console.log("not safe for work.");
     }
   };
   GirlFriend.prototype = hand; 

   //这个时候,我们可以用GirlFriend()作为构造函数,构造出myObject对象
   var myObject = new GirlFriend();
   console.log(myObject.__proto__ === GirlFriend.prototype) //true
   好了,通过上面的代码,我们构建了一个女神对象myObject,而 myObject 的原型是 hand 对象,而刚好 myObject 的构造函数GirlFriend()的 prototype 属性也指向 hand 对象。现在我们知道,prototype 与__proto__ 的关系就是:你的__proto__来自你构造函数的prototype 还有,上面的例子中,myObject 是通过 new GirlFriend()创建的,而 hand 对象,则是赋值语句创建的,这有什么不同?   其实hand这种直接用赋值语句加花括号来创建的对象,叫做对象字面量,你可以想象JavaScript内置了一个叫Object()的构造函数,这个函数的prototype属性指向的是一个空对象:console.log(Object.prototype) //输出{}
   而所有对象字面量都是通过Object()构造出来的,换言之,对象字面量__proto__ 属性都指向Object.prototype, which is 一个空对象。   所以我们可以知道, hand.__proto__ 指向的是Object.prototype    再附送你一个fun fact:   Object.prototype这个对象,它的__proto__指向的是null,然后就没有然后了。console.log(Object.prototype.__proto__);//输出null
1.创建一个新对象;
2.链接到原型(将构造函数的 prototype 赋值给新对象的 __proto__);
3.绑定this(构造函数中的this指向新对象并且调用构造函数)
4.返回新对象

这样我们就可以手动实现一个 new 方法了

// 构造器函数
let Parent = function (name, age) {
    this.name = name;
    this.age = age;
};
Parent.prototype.sayName = function () {
    console.log(this.name);
};
//自己定义的new方法
let newMethod = function (Parent, ...rest) {
    // 1.以构造器的prototype属性为原型,创建新对象;
    let child = Object.create(Parent.prototype);
    // 2.将this和调用参数传给构造器执行
    let result = Parent.apply(child, rest);
    // 3.如果构造器没有手动返回对象,则返回第一步的对象
    return typeof result  === 'object' ? result : child;
};
//创建实例,将构造函数Parent与形参作为参数传入
const child = newMethod(Parent, 'echo', 26);
child.sayName() //'echo';

//最后检验,与使用new的效果相同
child instanceof Parent//true
child.hasOwnProperty('name')//true
child.hasOwnProperty('age')//true
child.hasOwnProperty('sayName')//false

我们实现的 realizeNew() 方法需要传入的参数是:构造函数 + 属性

1.首先我们创建一个新对象,

2.然后通过 arguments 类数组获取构造函数和其他参数

我们可以知道参数中包含了构造函数以及我们调用create时传入的其他参数,接下来就是要想如何得到其中这个构造函数和其他的参数,由于arguments是类数组,没有直接的方法可以供其使用,我们可以有以下两种方法:

1.Array.from(arguments).shift(); 转换成数组 使用数组的方法 shift 将第一项弹出
2.[].shift().call(arguments) ; 通过 call() 让arguments能够借用shift()方法

绑定this的时候需要注意:

1.给构造函数传入属性,注意构造函数的this属性
2.参数传进 Con 对 obj 的属性赋值,this要指向 obj 对象
3.在 Con 内部手动指定函数执行时的this 使用call、apply实现

4.最后我们需要返回一个对象

我们来测试一下:


function Person (name,age){
    this.name = name;
    this.age = age;
    this.say = function () {
        console.log("I am " + this.name)
    }
}
 
//通过new创建构造实例
let person1 = new Person("Curry",18);
console.log(person1.name);      //"Curry"
console.log(person1.age);       //18
person1.say();      //"I am Curry'
 
//通过realize()方法创造实例
let person2 = realizeNew (Person,"Curry",18);
console.log(person2.name);      //"Curry"
console.log(person2.age);       //18

5.Js – 函数柯里化

(参考和推荐:https://segmentfault.com/a/1190000012769779)

通用实现

一个通用实现:

function currying(fn, ...rest1) {
  return function(...rest2) {
    return fn.apply(null, rest1.concat(rest2))
  }
}

注意这里concat接受非数组元素参数将被当做调用者的一个元素传入

用它将一个sayHello函数柯里化试试:

function sayHello(name, age, fruit) {
  console.log(console.log(`我叫 ${name},我 ${age} 岁了, 我喜欢吃 ${fruit}`))
}

const curryingShowMsg1 = currying(sayHello, '小明')
curryingShowMsg1(22, '苹果')            // 我叫 小明,我 22 岁了, 我喜欢吃 苹果

const curryingShowMsg2 = currying(sayHello, '小衰', 20)
curryingShowMsg2('西瓜')               // 我叫 小衰,我 20 岁了, 我喜欢吃 西瓜

嘻嘻,感觉还行~

高阶柯里化函数

以上柯里化函数已经能解决一般需求了,但是如果要多层的柯里化总不能不断地进行currying函数的嵌套吧,我们希望经过柯里化之后的函数每次只传递一个或者多个参数,那该怎么做呢:

function curryingHelper(fn, len) {
  const length = len || fn.length  // 第一遍运行length是函数fn一共需要的参数个数,以后是剩余所需要的参数个数
  return function(...rest) {
    return rest.length >= length    // 检查是否传入了fn所需足够的参数
        ? fn.apply(this, rest)
        : curryingHelper(currying.apply(this, [fn].concat(rest)), length - rest.length)        // 在通用currying函数基础上
  }
}

function sayHello(name, age, fruit) { console.log(`我叫 ${name},我 ${age} 岁了, 我喜欢吃 ${fruit}`) }    

const betterShowMsg = curryingHelper(sayHello)
betterShowMsg('小衰', 20, '西瓜')      // 我叫 小衰,我 20 岁了, 我喜欢吃 西瓜
betterShowMsg('小猪')(25, '南瓜')      // 我叫 小猪,我 25 岁了, 我喜欢吃 南瓜
betterShowMsg('小明', 22)('倭瓜')      // 我叫 小明,我 22 岁了, 我喜欢吃 倭瓜
betterShowMsg('小拽')(28)('冬瓜')      // 我叫 小拽,我 28 岁了, 我喜欢吃 冬瓜

来自:https://blog.csdn.net/weixin_37680520/article/details/108371908
https://www.cnblogs.com/plBlog/p/12356042.html

维基百科上说道:柯里化,英语:Currying(果然是满满的英译中的既视
感),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个
参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

看这个解释有一点抽象

1.我们就拿被做了无数次示例的add函数,来做一个简单的实现。

// 普通的add函数
复制代码
function add(x, y) {
    return x + y
}

// Currying后
function curryingAdd(x) {
    return function (y) {
        return x + y
    }
}

add(1, 2)           // 3
curryingAdd(1)(2)   // 3

实际上就是把add函数的x,y两个参数变成了先用一个函数接收x然后返回一个函数去处理y参数。现在思路应该就比较清晰了,就是只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

但是问题来了费这么大劲封装一层,到底有什么用处呢?没有好处想让我们程序员多干事情是不可能滴,这辈子都不可能.

来列一列Currying有哪些好处呢?

2. 参数复用

// 正常正则验证字符串 reg.test(txt)

// 函数封装后
function check(reg, txt) {
    return reg.test(txt)
}

check(/\d+/g, 'test')       //false
check(/[a-z]+/g, 'test')    //true

// Currying后
function curryingCheck(reg) {
    return function(txt) {
        return reg.test(txt)
    }
}

var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)

hasNumber('test1')      // true
hasNumber('testtest')   // false
hasLetter('21212')      // false

上面的示例是一个正则的校验,正常来说直接调用check函数就可以了,但是如果我有很多地方都要校验是否有数字,其实就是需要将第一个参数reg进行复用,这样别的地方就能够直接调用hasNumber,hasLetter等函数,让参数能够复用,调用起来也更方便。

3.实现一个函数功能:sum(1,2,3,4…n)转化为 sum(1)(2)(3)(4)…(n)

// 使用柯里化 + 递归
注意:length 是js函数对象的一个属性值,该值是指 “该函数有多少个必须要传入的参数”,

function curry ( fn ) {
  var c = (...arg) => (fn.length === arg.length) ?
          fn (...arg) : (...arg1) => c(...arg, ...arg1)
  return c
}
var a=function(a,b,c){return a+b+c}
undefined
function curry ( fn ) {
  var c = (...arg) => (fn.length === arg.length) ?
          fn (...arg) : (...arg1) => c(...arg, ...arg1)
  return c
}
undefined
var b=curry(a)
undefined

a(1,2,3)
6
b(1)(2)(3)
6
a.length
3
curry(sum)

在这里插入图片描述

4.实现一个add方法,使计算结果能够满足如下预期:

add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = Array.prototype.slice.call(arguments);

    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    var _adder = function() {
        _args.push(...arguments);
        return _adder;
    };

    // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }
    return _adder;
}

add(1)(2)(3)                // 6
add(1, 2, 3)(4)             // 10
add(1)(2)(3)(4)(5)          // 15
add(2, 6)(1)                // 9

6.实现一个Array.prototype.flat()函数

1.方式1:

Array.prototype.myFlat = function(arr=[]) {
  if (Array.isArray(this)) {
    this.forEach(item => {
      if(Array.isArray(item)){
       
       item.myFlat(arr)
      } else {
        arr.push(item)
      }  
    });
    return arr;
  } else {
    throw tihs + ".flat is not a function";
  }
};
ƒ (arr1) {
  if (Array.isArray(this)) {
   let arr=arr1&&arr1.length>0?arr1:[];
    this.forEach(item => {
      if(Array.isArray(item)){
       
       item.myFlat(arr)
      } else {
        arr.push…
array.myFlat()
(7) [1, 2, 3, 4, 5, 6, 7]
var array=[1,[2,3],4,[5,6,7],[8,9,10,11,[12,13,
array.myFlat()
(20) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

2.升级版本:可以控制解开多少层。


Array.prototype.myFlat = function(num = 1) {
  if (Array.isArray(this)) {
    let arr = [];
    if (!Number(num) || Number(num) < 0) {
      return this;
    }
    this.forEach(item => {
      if(Array.isArray(item)){
        let count = num
        arr = arr.concat(item.myFlat(--count))
      } else {
        arr.push(item)
      }  
    });
    return arr;
  } else {
    throw tihs + ".flat is not a function";
  }
};


链接:https://zhuanlan.zhihu.com/p/108289604?utm_source=qq&utm_medium=social&utm_oi=951563727821062144

3.简洁写法:

function flatDeep(arr) {
    return arr.reduce((res, cur) => {
        if(Array.isArray(cur)){
            return [...res, ...flatDeep(cur)]
        }else{
            return [...res, cur]
        }
    },[])
}
var array=[1,[2,3],4,[5,6,7],[8,9,10,11,[12,13,[14,15,16],17,18],[19,20],]]
flatDeep(array)
(20) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
链接:https://juejin.im/post/6864398060702760968

7.数据双向绑定

来自:https://zhuanlan.zhihu.com/p/123012477

<!DOCTYPE html>
<html>

<head>
</head>
<body>
  <input type="text" id="input" />
  <br>
  <!-- span的值为:<span id="span"></span> -->

  <script>
    // 数据
    data = {
      text: 'defau44lt'
    };
    const input = document.getElementById('input');
    // const span = document.getElementById('span');
    // 数据劫持
    Object.defineProperty(data, 'text', {
      // 数据变化 —> 修改视图
      set(newVal) {
        input.value = newVal;
        // span.innerHTML = newVal;
      },
      get: function () {
        var value=document.getElementById('input').value
       
        return value
      },
    });
    // 视图更改 --> 数据变化
    input.addEventListener('keyup', function (e) {
  
      data.text = e.target.value;
      console.log(data.text);
    });
  </script>
</body>

</html>

在这里插入图片描述
在这里插入图片描述

8.创建一个Event类,并创建on、off、trigger、once方法

来自:https://www.cnblogs.com/hyshi/p/10918500.html

版本1:


一、创建一个Event.js

复制代码
class Event {
    constructor() {
        this.handlers = { // 记录所有的事件和处理函数

        }
    }
    /* *
    * on 添加事件监听
    * @param type 事件类型
    * @param handler 事件回调
    * on('click', ()=>{})
    * */
    on(type, handler, once=false) {
        if (!this.handlers[type]) {
            this.handlers[type] = [];
        }
        if (!this.handlers[type].includes(handler)) {
            this.handlers[type].push(handler);
            handler.once = once;
        }
    }
    /* *
    * off 取消事件监听
    * 
    *  */
    off(type, handler) {
        if (this.handlers[type]) {
            if (handler === undefined) {
                this.handlers[type] = []
            } else {
                this.handlers[type] = this.handlers[type].filter((f)=>{
                    return f!=handler
                })
            }
        }
    }
    /* *
    * @param type 要执行哪个类型的函数
    * @param eventData事件对象
    * @param point this指向
    * 
    *  */
    trigger(type, eventData = {}, point=this) {
        if (this.handlers[type]) {
            this.handlers[type].forEach(f => {
                f.call(point, eventData);
                if (f.once) {
                    this.off(type, f)
                }
            });
        }
    }
    /* *
    * once 函数执行一次
    * @param type 事件处理
    * @param handle 事件处理函数
    *  */
    once(type, handler) {
        this.on(type, handler, true);
    }
}
复制代码
二、使用Event.js

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        #box {
            position: absolute;
            top: 0;
            left: 0;
            width: 100px;
            height: 100px;
            background: red;
        }
    </style>
    <script src="./event.js"></script>
</head>
<body>
    <div id="box"></div>
    
    
    <script>
        /* 
        * 1.记录摁下时鼠标的位置和元素位置
        * 鼠标位置-摁下时的鼠标位置 = 鼠标移动的位置
        * 元素位置=鼠标移动距离+摁下时元素位置
        **/
        class Drag extends Event{
            // 构造函数
            constructor(el) {
                super(); // 继承
                this.el = el;
                this.startOffset = null; // 鼠标摁下时元素的位置
                this.startPoint = null; // 鼠标的坐标
                let move = (e)=>{
                    this.move(e)
                }
                let end = (e)=>{
                    document.removeEventListener('mousemove', move);
                    document.removeEventListener('mouseup', end);
                    this.end(e)
                }
                el.addEventListener('mousedown', (e)=> {
                    this.start(e);

                    document.addEventListener('mousemove', move);
                    document.addEventListener('mouseup', end);
                })
                
                
            }
            start(e) {
                let {el} = this;
                console.log(this)
                console.log(el)
                this.startOffset = {
                    x: el.offsetLeft,
                    y: el.offsetTop
                }
                this.startPoint = {
                    x: e.clientX,
                    y: e.clientY
                }
                this.trigger('dragstart', e, this.el)
            }
            end(e) {
                this.trigger('dragend',e, this.el)
            }
            move(e) {
                let {el, startOffset, startPoint} = this;
                let nowPoint = {
                    x: e.clientX,
                    y: e.clientY
                }
                let dis = {
                    x: nowPoint.x - startPoint.x,
                    y: nowPoint.y - startPoint.y
                }
                el.style.left = dis.x + startOffset.x + 'px';
                el.style.top = dis.y + startOffset.y + 'px';
                this.trigger('dragmove', e, el)
            }
        }
        
        (function() {
            let box = document.querySelector('#box');
            let dragBox = new Drag(box);

            dragBox.on('dragstart', function(e) {
                console.log(e);
                console.log(this);
                this.style.background = 'yellow';
            })
            dragBox.on('dragend', function(e) {
                console.log('b')
                this.style.background = 'blue';
            })
            dragBox.once('dragmove', function(e) {
                console.log('c')
                // this.style.background = 'blue';
            })
            console.log(dragBox)
        })()
    </script>
</body>
</html>
复制代码

参考第二个版本:https://juejin.im/post/6844903965646127117#heading-2

class EventEmitter {
    constructor(){
        this.events = {}
    }
    on(name,cb){
        if(!this.events[name]){
            this.events[name] = [cb];
        }else{
            this.events[name].push(cb)
        }
    }
    emit(name,...arg){
        if(this.events[name]){
            this.events[name].forEach(fn => {
                fn.call(this,...arg)
            })
        }
    }
    off(name,cb){
        if(this.events[name]){
            this.events[name] = this.events[name].filter(fn => {
                return fn != cb
            })
        }
    }
    once(name,fn){
        var onlyOnce = () => {
            fn.apply(this,arguments);
            this.off(name,onlyOnce)
        }
        this.on(name,onlyOnce);
        return this;
    }
}

9.实现call,apply,bind

作者:white_give
链接:https://juejin.cn/post/6874901113062031367
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1)call的实现:

Function.prototype.myCall = function (ctx, ...args) {
	ctx = ctx || window;
	ctx.fn = this;把调用call的方法作为指定的上下文对象的一个属性
    const result = ctx.fn(...args);
    delete ctx.fn;
    return result;
}


2)apply的实现

Function.prototype.myApply = function (ctx, args = []) {
	if (args && !Array.isArray(args)) {
        throw ('Uncaught TypeError: CreateListFromArrayLike called on non-object');
    }
	ctx = ctx || window;
    ctx.fn = this;
    const result = ctx.fn(...args);
    delete ctx.fn;
    return result;
}


3)bind的实现

Function.prototype.myBind = function (ctx, ...args1) {
  const that = this;
  const o = function () {};
  const newFn = function (...args2) {
    const args = args1.concat(args2);
    if (this instanceof o) {
      that.apply(this, args);
    } else {
      that.apply(ctx, args);
    }
  }
  o.prototype = that.prototype;
  newFc.prototype = new o;
  return newFn;
}


10.手写axios

a)先看看axios的使用:

getNewsList(){
      this.axios.get('api/getNewsList').then((response)=>{
        this.newsList=response.data.data;
      }).catch((response)=>{
        console.log(response);
      })
}

b)实现

axios 原理还是属于 XMLHttpRequest, 因此需要实现一个ajax。
还需要但会一个promise对象来对结果进行处理。
以get请求为例,实现一个axios

在我看来,写一个ajax就需要5步,也就是5个单词,这就是一个ajax的流程。
这五个单词分别为:new open setRequestHeader onreadystatechange send。记住这五个单词你就有了ajax的整体的框架了。

首先:new字

let ajax = fucntion (){
	let httpRequest;
	// IE7以上,以及其他主流浏览器
	if(window.XMLHttpRequest){
		httpRequest = new XMLHttpRequest();
		if(httpRequest.overrideMimiType){
			httpRequest.overrideMimeType('text/xml')
		}
	} else if(window.ActiveXObject){
		//  针对IE6,及IE5.5 ie5
		httpRequest = new ActiveXObject();
		let activeName = ['MSXML2.XMLHTTP', 'Microsoft.XMLHTTP']; //创建XMLHttpRequest对象控件
		for(let i = 0; i < activeName.length; i++){
			try {
				httpRequest = new ActiveXObject(activeName[i]);
				if(httpRequest){
					break;
				}	
			} catch(e){
				console.log(e)
			}
		}
	}
	return httpRequest;
}

以上是最麻烦的一步,后面的步骤就比较简单了。

然后是open

ajax.open('GET', 'url', true);
ajax.open('POST', 'url', true);

如果是GET方式就不需要了,如果是POST方式就需要设置请求头

ajax.setRequestHeader('Content-Type: application/x-www-form-urlencoded; charset = utf8')

响应主体

ajax.onreadystatechange = function(){
	if(ajax.readystate == 4){
		if(ajax.status == 200){
			alert(ajax.responseText)
		}
	}
}

send
send发送请求,如果是get就不用发送内容

ajax.send(null)

如果是post可以发送请求的条件

ajax.send('name=Wang')

介绍一下readystate的情况
0 (未初始化) 对象已建立,但是尚未初始化(尚未调用 open 方法)
1 (初始化) 对象已建立,尚未调用 send 方法

2 (发送数据) send方法已调用,但是当前的状态及http头未知

3 (数据传送中) 已接收部分数据,因为响应及http头不全,这时通过 responseBody 和 responseText 获取部分数据会出现错误,

4 (完成) 数据接收完毕,此时可以通过通过 responseXml 和 responseText 获取完整的回应数据
————————————————
版权声明:本文为CSDN博主「山上的仓鼠」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wangyoushuai/article/details/87973788

// 获取异步请求对象
function getXhr(){
var xhr=null;
if(window.XMLHttpRequest){
    xhr=new XMLHttprequest();
}else{
	// 如果浏览器版本是IE8以下浏览器
    xhr=new ActiveXObject('Microsoft.XMLHttp');
}
return xhr;}

// url:"url路径"  type:请求方式  data:请求参数类型  dataType:返回的字符串类型
function ajax({url,type,data,dataType}){
return new Promise(function(resolve,reject){
	//1. 创建异步请求对象
	var xhr=getXhr();
	// 备注:无需通过上面的方式,简单的创建异步请求对象的简化代码如下:
	// var xhr = window.XMLHttpRequest ? new XMLHttprequest() : new ActiveXObject('Microsoft.XMLHttp');
	//2.绑定监听事件
	xhr.onreadystatechange=function(){
		// 当异步请求状态变为4时,并且返回的状态码为200,接收响应成功
		if(xhr.readyState==4&&xhr.status==200){
			// 当返回接收的字符串类型为json串时,自动转换json串
			if(dataType!==undefined
				&&dataType.toLowerCase()==="json")
				var res=JSON.parse(xhr.responseText)
			else
				// 否则直接获取返回的响应文本中的内容
				var res=xhr.responseText
			// 通过Promise,将返回的数据向后传递,相当于获取到请求数据将数据return出来
			resolve(res);
		}
	}
	// 如果请求方式为get请求,则将请求参数拼接在url后
	if(type.toLowerCase()==="get"&&data!==undefined){
		url+="?"+data;
	}
	//3.打开连接
	xhr.open(type,url,true);
	// 如果请求方式为post请求,则修改请求消息头
	if(type.toLowerCase()==="post")
		//增加:设置请求消息头
		xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
	//4.发送请求
	if(type.toLowerCase()==="post"&&data!==undefined)
		xhr.send(data);
	else
		xhr.send(null);})

实现ajax的get请求

var Ajax={
        get: function(url, fn) {
            // XMLHttpRequest对象用于在后台与服务器交换数据
            var xhr = new XMLHttpRequest();
            xhr.open('GET', url, true);
            xhr.onreadystatechange = function() {
                // readyState == 4说明请求已完成
                if (xhr.readyState == 4 && xhr.status == 200) {
                    // 从服务器获得数据
                    fn.call(this, xhr.responseText);
                }
            };
            xhr.send();
        }
    }

封装Ajax,实现Axios进行回调

var Axios = {
        get: function(url) {
            return new Promise((resolve, reject) => {
                var xhr = new XMLHttpRequest();
                xhr.open('GET', url, true);
                xhr.onreadystatechange = function() {
                    // readyState == 4说明请求已完成
                    if (xhr.readyState == 4 && xhr.status == 200) {
                        // 从服务器获得数据
                        resolve(xhr.responseText)
                    }
                };
                xhr.send();
            })
        },
    }
 

axios的使用如下:

11.简单实现promise:(https://zhuanlan.zhihu.com/p/84757243)

class myPromise {
  constructor(callback) {
    this.msg = ""
    this.success = null
    this.fail = null
    //这个callback也就是我们传递的函数
    callback(
      success => {
        // 成功 success对应res()传递的参数
        this.msg = "SUCCESS"
        this.success = success
      },
      fail => {
        // 失败 success对应rej()传递的参数
        this.msg = "FAIL"
        this.fail = fail
      }
    )
  }
  then(success, fail) {
    if (this.msg === "SUCCESS") {
      success(this.success)
    }
    if (this.msg === "FAIL") {
      fail(this.fail)
    }
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值