【前端知识之手写JS】JS基础中常见的手写代码汇总

  • Post author:
  • Post category:其他




前言

本系列主要整理前端面试中需要掌握的知识点。本节介绍JS基础中常见的手写代码汇总。




一、手写Object.create

具体的方法解释见博客:

【前端知识之JS】Object.create和new的区别以及Object.create的实现

// Object.create能够创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
// 代码重点:
function mycreate(obj){
    // 创建一个对象
    function F(){}
    // 使用现有的对象来提供新创建的对象的__proto__
    F.prototype = obj
    // 返回F的实例
    return new F()
}
//测试代码
function A() {
    this.name = 'abc';
}
A.prototype.a = 'a';
A.prototype.showName = function () {
    return this.name;
}
var a1 = new A();
var a2 = Object.create(A.prototype);
var a3 = mycreate(A.prototype)
console.log('new方法:');
console.log(a1);    //name: "abc" [[Prototype]]: Object
console.log('create方法:');
console.log(a2);   // [[Prototype]]: Object
console.log('mycreate方法:');
console.log(a3);  //[[Prototype]]: Object



二、手写instanceof方法

数据类型判断的具体解释见博客

【前端知识之JS】typeof 与 instanceof 区别以及instanceof的手写

// instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
// left:实例
// right:数据类型
function myInstanceof(left,right){
	// 1. getPrototypeOf是获取实例原型的
	// 2.prototype用于建立由 new C() 创建的对象的原型。
    let proto = Object.getPrototypeOf(left)
    let prototype = right.prototype
    while(true){
        if(!proto) return false
        if(proto === prototype) return true
        else{
            proto = Object.getPrototypeOf(proto)
        }
    }
}
console.log(myInstanceof('123',Array));  //false
console.log(myInstanceof([1,2,3],Array)); //true



三、手写new操作符

具体关于new的解释见博客

【前端知识之JS】JS中new关键字的具体操作

function myNew(Func,...args){
    // 1.创建一个新对象
    const obj = {};
    // 2.使新对象的原型等于传入对象的原型
    obj.__proto__ = Func.prototype;
    // 3.this指向当前对象
    let result = Func.apply(obj,args);
    // 4.判断返回值是什么,不是对象就返回新建的对象
    return result instanceof Object ? result : obj
}
function Person(name,age){
    this.name = name;
    this.age = age;
}
Person.prototype.sayName = function(){
    console.log(this.name);
}
let xiaoming = myNew(Person,'xiaoming',19)



四、手写防抖函数

防抖的解释与应用见博客

【前端知识之JS】防抖知识

/*
防抖思路:事件触发后,要等待n秒才能触发下次,如果n秒内被触发了,就要重新计时
总体思路:首先要定义一个定时器,用来保留上一次的定时器,每次触发都提前清空定时器,然后重新用延时函数包裹触发事件
完善思路:由于每个防抖内部包含的回调事件以及参数都是不同的,因为这些最好在外部定义,在防抖内部直接调用即可。
*/
var input = document.getElementById('input')
// 两个参数分别是延时时间,以及触发事件的回调函数
function debounce(delay,fn){
    // 保留上次的定时器
    let timer;
    // 用闭包延长timer的变量周期
    const _debounce =  function (value){
        // 清除定时器
        clearTimeout(timer);
        // 重新计时
        timer = setTimeout(function(){
            fn(value);
        },delay)
    }
    return _debounce
}

// 定义回调函数
const fn = function(value){
    console.log(value);
}

var debounceFunc = debounce(2000,fn)
input.addEventListener('keyup',function(e){debounceFunc(e.target.value)})



五、手写节流函数

节流的解释与应用见博客

【前端知识之JS】节流知识

/*
节流思路:在一段时间内,同一个事件只能被触发一次
总体思路:首先用timer保存上次点击的定时器,然后判断timer是否为空,如果timer为空,那么才执行回调函数,并且同时生成一个定时器
完善思路:将回调函数放在节流的外面进行定义。
*/
var btn = document.getElementById('button')
function throttle(wait,fn){
    // 用timer保存上次点击的定时器
    let timer;
    return function(value){
        if(!timer){
            timer = setTimeout(()=>{
                fn(value);
                // 等待时间过后就把定时器清空
                timer = null;
            },wait)
        }
    }
}

const fn = function(value){
    console.log(value);
}

const throttleFunc = throttle(2000,fn)
btn.addEventListener('click',function(e){
    throttleFunc(e.target.innerText)
})



六、手写类型判断函数

/*
思路:先判断null,再判断Object,最后判断基本数据类型,但是typeof能够把function的类型识别出来,所以function是和基本数据类型一起判断的。
*/
function getType(value){
    if(value === null) return null+"";
    else if(typeof value === "object"){
        let valueClass = Object.prototype.toString.call(value).split(" ")[1].split("]")[0]
        return valueClass
    }
    else{
        return typeof value
    }
}

console.log(getType(123)) //number



七、手写call函数

call , apply , bind几种函数的具体解释见博客

【前端知识之JS】bind, call, apply的区别,以及如何实现bind

/* 
 call函数功能:第一个参数是this的目标,后面几个参数是参数列表
 1.判断调用对象是否为函数;
 2.判断传入上下文对象是否存在,不存在就设置为window;(传入的上下文就是this的新指向)
 3.截取参数slice(1),call和apply的区别就是参数的类型不同。
 4.将函数作为上下文对象的一个属性;
 5.使用上下文对象来调用这个方法,并保存返回结果
 6.删除刚刚新增的属性
 7.返回结果
  */
 Function.prototype.myCall = function(context){
     if(typeof this !== "function"){
         console.error("type error");
     }
     let args = [...arguments].slice(1);
     let result = null;
     context = context || window;
     context.fn = this;
     result = context.fn(...args)
     delete context.fn;
     return result
 }
 
 function fn(...args) {
     console.log(this, args);
 }
 let obj = {
     myname: "anada"
 }

 fn.myCall(obj, 1, 2, 3)



八、手写apply函数

/* 
apply函数功能:第一个参数是this的目标,第二个参数是一个数组,里面是函数的参数
1.判断调用对象是否为函数;
2.判断传入上下文对象是否存在,不存在就设置为window;(传入的上下文就是this的新指向)
3.截取参数[...arguments][1],call和apply的区别就是参数的类型不同。
4.将函数作为上下文对象的一个属性;
5.使用上下文对象来调用这个方法,并保存返回结果
6.删除刚刚新增的属性
7.返回结果
 */
Function.prototype.myApply = function(context){
    if(typeof this !== "function"){
        console.error("type error");
    }
    context = context || window;
    let args = [...arguments][1];
    let result = null;
    context.fn = this;
    result = context.fn(...args);
    delete context.fn;
    return result;
}

function fn(...args) {
    console.log(this, args);
}
let obj = {
    myname: "anada"
}

fn.myApply(obj,[1,2,3])



九、手写bind函数

/* 
bind函数功能:第一个参数是this的目标指向,接下来是一个参数列表,bind不能立即执行,但是可以分次执行。
实现思路:
1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
2. 保存当前函数的引用,获取其余传入参数值。
3. 创建一个函数返回
4. 函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象。   
*/

Function.prototype.myBind = function(context){
    if(typeof this !== "function"){
        console.error("type error");
    }
    let args = [...arguments].splice(1);
    let fn = this;
    return function Fn(){
        return fn.apply(this instanceof Fn ? this : context,args.concat(...arguments))
    }
}

function fn(...args){
    console.log(this,args);
}

const obj = {
    name:'ananda'
}

bindFunc = fn.myBind(obj,1,2,3)                           
bindFunc()
bindFunc(6,7,8)



十、手写ajax请求

关于ajax相关的知识见博客

【前端知识之JS】ajax原理及其手写

/* 
1.创建一个XMLHttpRequest对象;
2.在这个对象上使用open方法创建一个HTTP请求,open方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息
3.在发起请求之前,可以为这个对象添加一些信息和监听函数。通过setRequestHeader方法来为请求添加头信息。
4.XMLHttpRequest状态变化时会触发onreadystatechange事件,可以通过设置监听函数,来处理请求成功后的结果。
当对象的readystate变为4时,服务器接受数据完成,如果时2xx或者304的话则代表返回正常,可以通过response中的数据来对页面进行更新了。
5.当对象的属性和监听函数设置完成后,最后调用sent方法来向服务器发起请求,可以传入参数作为发送的数据体。
*/
let xhr = new XMLHttpRequest();
xhr.open('GET',url,true);
xhr.responseType = "json"
xhr.setRequestHeader("Accept","application/json")
xhr.onreadystatechange = function(){
if(this.readyState !== 4) return;
if(this.status === 200){
    handle(this.response)
}
else{
    console.error(this.statusText);
}
}
xhr.onerror = function(){
console.error(this.statusText);
}
xhr.send(null)



十一、使用Promise对ajax进行封装

function getJSON(url){
    let promise = new Promise(function(resolve,reject){
        let xhr = new XMLHttpRequest();
        xhr.open('GET',url,true);
        xhr.responseType = "json";
        xhr.setRequestHeader("Accept","application/json");
        xhr.onreadystatechange = function(){
            if(this.readyState !== 4) return;
            if(this.status === 200){
                resolve(this.response)
            }
            else{
                reject(this.statusText)
            }
        }
        xhr.onerror = function(){
            reject(this.statusText)
        }
        xhr.send(null)
    })
    return promise
}

getJSON('./data.json').then(function(result){
    console.log(result);
})



十二、手写实现浅拷贝

关于深浅拷贝的知识见博客

【前端知识之JS】JS中的深拷贝与浅拷贝

/* 
浅拷贝:一个新的对象对原始对象的属性值进行精确地拷贝,如果拷贝的是基本数据类型,拷贝的就是基本数据类型的值,如果是引用数据类型,拷贝的就是内存地址。
如果其中一个对象的引用内存地址发生改变,另一个对象也会跟着改变。
1. Object.assign 是浅拷贝
2. 扩展运算符是浅拷贝
3. slice是浅拷贝
4. concat是浅拷贝
*/
// 浅拷贝的实现
function shallowCopy(object){
    // 如果不是对象就不拷贝
    if(!object || typeof object !== "object") return;
    let newObject = Array.isArray(object)?[]:{}
    for(let key in object){
        if(object.hasOwnProperty(key)){
            newObject[key] = object[key];
        }
    }
    return newObject;
}
console.log(shallowCopy([1,2,3]));



十三、手写实现深拷贝

/* 
深拷贝:新建一个引用类型并将对应的值赋值给它,因此对象获得一个新的引用类型而不是一个原有类型的引用。
JSON.stringfy()是深拷贝
函数库lodash的_.cloneDeep方法是深拷贝
*/
// 深拷贝的实现
function deepCopy(object){
    if(!object || typeof object !== "object") return;
    var newObject = Array.isArray(object)? []:{};
    for(let key in object){
        if(object.hasOwnProperty(key)){
            newObject[key] = typeof object[key] === "object" ? deepCopy(object[key]) : object[key]
        }
    }
    return newObject;
}
let object = {b:2,c:{d:4}}
let target = deepCopy(object)
object.c.d = 88;
console.log(object);
console.log(target);



十四、手写Object.assign

/* 
Object.assign的作用是将几个对象整理到目标对象中,使用的是浅拷贝
*/
Object.myAssign = function(target,...source){
if(target == null || target == undefined) console.error('Cannot convert undefined or null to object');
let result = Object(target)
source.forEach(function(obj){
    if(obj!=null){
        for(let key in obj){
            if(obj.hasOwnProperty(key)){
                result[key] = obj[key]
            }
        }
    }
})
return result
}
obj0 = {g:1}
obj1 = {a:1,b:{c:2,d:5}}
obj2 = Object.myAssign(obj0,obj1)
console.log(obj2);



版权声明:本文为weixin_44337386原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。