前端JavaScript小知识(持续更新....)

195 阅读5分钟

call/apply/bind理解和应用

call/apply/bind是什么?

call,apply和bind方法就是Function原型中的方法,根据原型链的规则,所有的函数都可以使用原型中属性和方法。

call/apply/bind的意义?

  • Function.prototype.call(): call() 方法使用一个指定的this值和单独给出的一个或多个参数来调用一个函数。
  • Function.prototype.apply(): apply()  方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。
  • Function.prototype.bind(): bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。 从定义中我们发现,call,apply和bind的本质是改变函数执行时的上下文,再具体一点就是改变函数运行时的this指向
let obj = {
    name: 'lilei',
    age: '18'
};

function People(name) {
    this.name = name;
}

People.prototype = {
    constructor: People, 
    showName: function(){ 
        console.log(this.name); 
    },
    showAge: function(){
        console.log(this.name);
    }
}

var people = new People('xiaohong');
people.showName(); // xiaohong

// call,apply,bind使用
people.showName.call(obj); // lilei
people.showName.apply(obj); // lilei
let bind = people.showName.bind(obj); // 返回一个函数 bind();
bind() // lilei

这样我们改变函数执行上线文,可以通过其函数中的showName方法,输出了自己想要的信息,实现的方法的复用。

call/apply/bind的区别?

  • call和apply改变了函数的this上下文后便执行该函数,而bind则是返回改变了上下文后的一个函数。
  • call、apply的差别在于参数的区别,call和apply的第一个参数都是要改变上下文的对象,而call从第二个参数开始以参数列表的形式展现,apply则是把除了改变上下文对象的参数放在一个数组里面作为它的第二个参数。
let nums = [1, 2, 4, 5]; 
console.log(Math.max.call(null, 1,2,3,4,5)); // 5
console.log(Math.max.call(null, nums)); // NaN 
console.log(Math.max.apply(null, nums)); // 5 直接可以用nums传递进去

call/apply/bind的应用

  • 将伪数组转化为数组(含有length属性的对象,dom节点, 函数的参数arguments)

js中的伪数组(例如通过document.getElementsByTagName获取的元素、含有length属性的对象)具有length属性,并且可以通过0、1、2…下标来访问其中的元素,但是没有Array中的push、pop等方法。就可以利用call,apply来转化成真正的数组,就可以使用数组的方法了

// dom 节点
<div class="div1">1</div> 
<div class="div1">2</div> 
<div class="div1">3</div>
let div = document.getElementsByTagName('div'); 
// HTMLCollection(3) [div.div1, div.div1, div.div1] 里面包含length属性
let arr = Array.prototype.slice.call(div); console.log(arr); 
// 数组 [div.div1, div.div1, div.div1] 转化为数组可以用数组方法了

// 函数中的arguments
function argFn() { 
    return Array.prototype.slice.call(arguments); 
}
console.log(argFn(1,2,3,4,5)); 
// [1, 2, 3, 4, 5]

// 含有length属性的对象
let obj = {
    0: 1,
    1: 'lilei',
    2: 100,
    length: 3
} // 一定要有length属性
console.log(Array.prototype.slice.call(obj)); 
// [1, "lilei", 100]
  • 数组的拼接/添加
let arr1 = [1,2,3]; 
let arr2 = [4,5,6]; 

//数组的concat方法:返回一个新的数组 
let arr3 = arr1.concat(arr2); 
console.log(arr3); // [1, 2, 3, 4, 5, 6] 
console.log(arr1); // [1, 2, 3] 不变 
console.log(arr2); // [4, 5, 6] 不变 

// 用 apply方法 
[].push.apply(arr1,arr2); // 给arr1添加arr2 
console.log(arr1); // [1, 2, 3, 4, 5, 6] console.log(arr2); // 不变
  • 判断变量类型
// 判断类型的方式,这个最常用语判断array和object,null(因为typeof null等于object) 
console.log(Object.prototype.toString.call([1,2,3])); // [object Array]
console.log(Object.prototype.toString.call('lilei')); // [object String] 
console.log(Object.prototype.toString.call({name:'lilei'})); // [object Object]
console.log(Object.prototype.toString.call(null)); // [object Null]
  • 利用call和apply做继承
function People(name){ 
    this.name = name; 
    this.showName = function(){ 
        console.log(this.name); 
    } 
}
function Stutent(name){ 
    People.call(this, name); 
}
// People.call(this) 的意思就是使用this对象代替People对象,
// 则Stutent中就包含了People的所有属性和方法了,
// Stutent对象可以直接调用People中的方法和属性了 
var stutent = new Stutent("lilei");
stutent.showName(); // lilei
  • 多继承
function Class1(a,b) { 
    this.showclass1 = function(a,b) { 
        console.log(`class1: ${a},${b}`); 
    } 
} 
function Class2(a,b) { 
    this.showclass2 = function(a,b) { 
        console.log(`class2: ${a},${b}`); 
    } 
} 
function Class3(a,b,c) { 
    Class1.call(this);
    Class2.call(this); 
}
let arr = [1,2,3,4];
let demo = new Class3();
demo.showclass1.call(this,1); // class1: 1,undefined 
demo.showclass1.call(this,1,2); // class1: 1,2 
demo.showclass2.apply(this,arr); // class2: 1,2

常用的数组去重方法

数组去重的方法大致就分为两类, 一类是双层循环判断完成新数组的存储, 另一类是通过高阶函数或者语法特性进行存储一遍循环完成判断和新数组的存储; 里面需要主语的是一些边界值的特殊判断,比如 NaN {} null;其中NaN最为特殊,因为NaN不等于任何数值,包括自身也是不相等。

双层循环法
  • 双层循环,外层循环元素,内层循环时比较值。值相同时,则删去这个值。
    function unique(arr) {
        for (var i = 0; i < arr.length; i++) {
            for (var j = i + 1; j < arr.length; j++) {
                if (arr[i] === arr[j]) { //第一个等同于第二个,splice方法删除第二个
                    arr.splice(j, 1);
                    j--;
                }
            }
        }
        return arr;
    }
    
  • 利用sort()排序方法,然后根据排序后的结果进行遍历及相邻元素比对
    function unique(arr) {
        if (!Array.isArray(arr)) {
            console.log('type error!')
            return;
        }
        arr = arr.sort()
        var arrry = [arr[0]];
        for (var i = 1; i < arr.length; i++) {
            if (arr[i] !== arr[i - 1]) {
                arrry.push(arr[i]);
            }
        }
        return arrry;
    }
    
  • 利用递归去重
    function unique9(arr) {
        var array = arr;
        var len = array.length;
        array.sort(function (a, b) {   //排序后更加方便去重
            return a - b;
        })
        function loop(index) {
            if (index >= 1) {
                if (array[index] === array[index - 1]) {
                    array.splice(index, 1);
                }
                loop(index - 1);    //递归loop,然后数组去重
            }
        }
        loop(len - 1);
        return array;
    }
    
利用语法特性(键值唯一性 高阶函数)
  • 利用ES6 Set去重(ES6中最常用) 不考虑兼容性,这种去重的方法代码最少。
    function unique(arr) {
        return Array.from(new Set(arr));
    }
    
    function unique(arr) {
        return [...new Set(arr)];
    }
    
  • 利用Map数据结构去重 创建一个空Map数据结构,遍历需要去重的数组,把数组的每一个元素作为key存到Map中。由于Map中不会出现相同的key值,所以最终得到的就是去重后的结果
    function unique9(arr) {
        let map = new Map();
        let array = new Array();  // 数组用于返回结果
        for (let i = 0; i < arr.length; i++) {
            if (map.has(arr[i])) {  // 如果有该key值
                map.set(arr[i], true);
            } else {
                map.set(arr[i], false);   // 如果没有该key值
                array.push(arr[i]);
            }
        }
        return array;
    }
    
  • 利用reduce+includes
    function unique(arr) {
        return arr.reduce((prev, cur) => prev.includes(cur) 
        ? prev 
        : [...prev, cur], []);
    }
    
  • 利用数组或对象特性去重 判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则push进数组 判断放法有很多比如 数组indexOf/includes等或对象键值唯一性/hasOwnProperty等
    function unique(arr) {
        if (!Array.isArray(arr)) {
            console.log('type error!')
            return;
        }
        var array = [];
        for (var i = 0; i < arr.length; i++) {
            if (array.indexOf(arr[i]) === -1) {
                array.push(arr[i])
            }
        }
        return array;
    }
    
    function unique5(arr) {
        if (!Array.isArray(arr)) {
            console.log('type error!')
            return
        }
        var arrry = [];
        var obj = {};
        for (var i = 0; i < arr.length; i++) {
            if (!obj[arr[i]]) {
                arrry.push(arr[i])
                obj[arr[i]] = 1
            } else {
                obj[arr[i]]++
            }
        }
        return arrry;
    }