js常见手写功能

135 阅读3分钟

js常见手写功能

o_2001210816401565149939605.png

一,call 函数 apply 实现及常用范围

1,call

let Person = {
    name:'孙三',
    say(){
            console.log(`my name is ${this.name}`);
    }
}

let son = {
    name:'孙琬'
}

Function.prototype.myCall = function(context){

        console.log(this);      // 谁调用谁就是this,此时调用者为Person.say,故this当为此函数
        console.log(context);   // context是调用传入的对象

        // 因为此方法是要改变调用者的指向,因此这个this必定为一函数
        if (typeof this !== 'function') 
                {throw new Error('error')
        }

        // 调用时如果传入参数,context 就是要改变的对象 不然此时的context 当为window
        context = context || window

        // 拿到除了第一个参数之外的参数
        let args = [...arguments].slice(1)

        console.log(args);

        context.fn = this  //关键点 【假设在此对象context上有一函数fn让他是当前调用的函数,】

        context.fn(...args)   // 调用fn 即为this执行函数

        delete context[fn]   // 应及时将增加的fn方法删除
}


Person.say.myApply(son,[2,2,3])

call函数可用于构造函数继承 类型判断

Object.prototype.toString.call({}) // '[object Object]' Object.prototype.toString.call([]) // '[object Array]'

2,apply ( 接受数组 )

call 接收多个参数,第一个为函数上下文也就是this,后边参数为函数的参数,只是此参数需要传递数组。

// apply  写法一致只是传参为数组
Function.prototype.myApply = function(context){
    if (typeof this !== 'function') 
            {throw new Error('error')
    }

    context = context || window

    let args = [...arguments].slice(1)

    context.fn = this  //关键点 【假设在此对象context上有一函数fn让他是当前调用的函数,】

    context.fn(...args)   // 调用fn 即为this执行函数

    delete context[fn]   // 应及时将增加的fn方法删除
}

Person.say.myApply(son,[2,2,3])

apply 函数常用于函数的节流,防抖,去改变this 指向

debounce(fn,delay){     
    let timer     
    return function(...args){         
        if(timer){             
            clearTimeout(timer)         
        }         
        timer=setTimeout(()=>{             
            fn.apply(this,args)         
        },delay)     
    }     
},

二,js继承方式

1, 原型链继承

重点:让父类的实例作为子类的原型

缺点:引用值类型的数据会被实例共享,只要某个实例加入某个属性,其他的实列上面也会有该属性

function Person(name,age){
    this.name = name,
    this.age = age
}

Person.prototype.run = function(){
    console.log('跑步');
}

function Student(){
    this.score = 99
}

Student.prototype = new Person()

console.log(new Student());

2,构造函数继承

重点:在子类构造函数中使用 call() 调用父类构造函数

优点:解决了原型链继承中不能传参且引用值共享问题

缺点:不能调用父类原型上的方法

function Person(name,age){
    this.name = name,
    this.age = age
}

Person.prototype.run = function(){
    console.log('跑步');
}

function Student(name,age){
    this.score = 99
    Person.call(this,name,age)
}

console.log(new Student('张三',20));

3,组合式继承(构造函数和原型链)

重点:使用 call() 调用父类的属性,使用 new 获取父类原型上的方法

优点:解决了不能调用父类原型上的方法问题

function Person(name,age){
    this.name = name,
    this.age = age
}

Person.prototype.run = function(){
    console.log('跑步');
}

function Student(name,age){
    this.score = 99
    Person.call(this,name,age)
}

Student.prototype = new Person()

console.log(new Student('张三',20));

三,深浅拷贝实现

1,引言:

基本数据类型的特点:直接存储在栈(stack)中的数据

引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里

  • 引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

  • 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

2,浅拷贝实现方式

数据类型为对象:{...对象}

数据类型为数组:{...对象}

obj2 = Object.assgin(obj2, obj1)

3,深拷贝

    obj2 = JSON.parse( JSON.stringify(obj1))

通过递归实现(推荐)

deepClone(obj){
    if(!obj || typeof obj!=='object')  return
    let newObj = Array.isArray(obj) ? [] : {}
    for (const key in obj) {
        // 判断对象本身是否有此属性,而不是继承自原型
        if (obj.hasOwnProperty(key)) {              
            newObj[key] = typeof obj[key] == 'object' ? this.deepClone(obj[key]) : obj[key]           
        }
    }
    return newObj
},

 let obj = {
    name: 'xiyang',
    age: 25,
    hoppy: {
        type: 'wan',
        num:{
            n:2
        }
    }
}

let newobj = this.deepClone(obj)
newobj.name = 'lisi'
console.log(newobj);   // obj:{ name:lisi, age:25 }
console.log(obj);      // obj:{ name:xiyang, age:25 }

结语

一生思破红尘路,剑藏庐轩隐迷踪。