JS突击考点

146 阅读4分钟

手写apply,bind,call

这几个函数最主要的功能是规定了this的指向。其中call的实现最简单,虽然call和apply需要传入参数,bind不用,但是bind需要返回一个函数,call和apply直接执行返回结果。

Function.prototype.myCall(obj){ //函数调用,所有函数都要能使用,因此定义在原型链上
    //this check 保证是一个函数调用此函数
    if(typeof this !== 'function'){
        throw new TypeError('Error');
    }
    obj = obj || window //如果没有输入 就把window当作this
    obj.fn = this; //func 现在是this,fn要指向调用本函数的this
    
    //提取参数
    args = [...arguments].slice(1);
    let result = obj.fn(...args);
    delete obj.fn;
    return result;
}

总结一下,call传入的第一个参数是this,后续参数不一定有多少。首先要确保是一个函数调用的call,因此用this确认。this指向的函数需要挂载到新的this,也就是传入的第一个参数上。然后要提取参数,使用arguments和扩展运算符。使用新挂载的运算符和参数得到结果,然后删除挂载的函数。

这里小知识点是函数的传参和扩展运算符。js函数可以接受超出规定的参数而不报错,想要获得完整的传入需要使用arguments。而使用扩展运算符可以以Array的形式一次传入不定的参数并且只有规定的参数能被接受,按顺序接收。

apply

apply和call相比,功能差异主要体现在参数上,call显式地接受多个参数,而apply只显式接受前两个参数,第一个是this,第二个是参数数组。

Function.prototype.myApply(obj,args){ //函数调用,所有函数都要能使用,因此定义在原型链上
    //this check 保证是一个函数调用此函数
    if(typeof this !== 'function'){
        throw new TypeError('Error');
    }
    obj = obj || window //如果没有输入 就把window当作this
    obj.fn = this; //func 现在是this,fn要指向调用本函数的this
    
    //提取参数
    if(args){
        let result = obj.fn(args);
    }else{
        let result = obj.fn();
    }
    delete obj.fn;
    return result;
}

bind

bind要返回一个新的函数,有点麻烦。由于bind返回的是一个函数,函数有两种用法,直接调用和new 创建新对象。后者的this指向window。

Function.prototype.myBind = function(obj){
    if(typeof this !== 'function'){
        throw new TypeError('Error')
    }
    
    const _this = this;
    const args = [...arguments].slice(1);
    
    return function F(){
        if(this instanceof F){
            return _this(...args,...arguments);
        }
        
        return _this.apply(obj,args.concat(...arguments));
    }
    
}

原型和继承,如何实现?

组合继承

组合继承是最原始的继承方式,主要使用call和原型赋值的方式实现

function a(){
    //...
}
a.prototype.funca = ()=>{...}

function b(){
    a.call(this,arguments)
}
b.prototype = new a();

优点是构造函数可以传参,不会与父类引用属性共享。但是会给子类添加不需要的父类属性,造成性能的浪费

寄生组合继承

主要目标就是优化掉不需要的父类类型。

function a(){
    //...
}
a.prototype.funca = ()=>{...}

function b(){
    a.call(this,arguments)
}
b.prototype = Object.create(a.prototype, {
    constructor:{
        value: b,
        enumerable: false,
        writable: true,
        configurable: true,
    } 
})

ES6 class

function a(){
    //...
}
a.prototype.funca = ()=>{...}

class b extend a{
    constructor(value){
        supuer(value)
    }
}

new 和 Object.create

Object.create创建的对象没有构造函数,也就是说该对象一般作为对象的原型使用,这样就不难理解为什么要用这个函数去除组合继承里面的构造函数了,之后人为创建一个构造函数并且赋值几个属性,

  • emumberable: false for in 遍历的时候不会取到这些在原型里面的函数
  • writable,confiugurable 要设置为true。writable决定是否可以改变属性,configureable决定是否可以使用Object.defineProperty来更改属性描述符,删除属性。使用defineProperty设置configureable为false是单向的,再也改不回去。

那么使用new 创建新对象的过程中发生了什么呢?

  • 创建新对象
  • 连接到原型
  • 绑定this
  • 如果构造函数不返回其他数据,返回该对象

如何自己实现一个new?

instanceof

instanceof的原理是通过类型的原型链进行判断。对象实例的隐式原型是函数的显式原型,对象的隐式原型一层层递推上去,直到null为止,看看有没有相等的。 最后判断对象的隐式原型与Object.prototype的关系

function deepcopy(obj){
    if( typeof obj !== 'object'){
        return obj;
    }
    let newObj = {};
    for(keys in obj){
        if(obj.hasOwnproperty()){
            
        }
    }
    
}

==比较顺序与类型隐式转换

类型隐式转换是存在三种情况,转number,string,boolean

在比较的时候也会发生隐式类型转换:

  • 首先判断两边是否是null和undefined。
  • 然后是string和number,优先将string=>number.
  • 再然后是boolean,将boolean转换为number
  • 最后是Object,如果另一方是string,number,symbol,则转换为这几种基本类型。

然后看看这道题 []==![]