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

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.深拷贝

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 操作符都做了什么事,即构造函数的内部原理:

1.创建一个新对象;
2.链接到原型(将构造函数的 prototype 赋值给新对象的 __proto__);
3.绑定this(构造函数中的this指向新对象并且调用构造函数)
4.返回新对象

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

function realizeNew () {
    
    
    //创建一个新对象
    let obj  = {
    
    };
    //获得构造函数
    let Con = [].shift.call(arguments);
    //链接到原型(给obj这个新生对象的原型指向它的构造函数的原型)
    obj.__proto__ = Con.prototype;
    //绑定this
    let result = Con.apply(obj,arguments);
    //确保new出来的是一个对象
    return typeof result === "object" ? result : obj
}

我们实现的 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://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;
    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的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的使用如下:

猜你喜欢

转载自blog.csdn.net/Handsome2013/article/details/108046776