阅读 314

new,call,apply,bind知多少

new

作用以及执行步骤

new 关键词的主要作用就是执行一个构造函数、返回一个实例对象,在 new 的过程中,根据构造函数的情况,来确定是否可以接受参数的传递。

function Person(name){
   this.name = name;
}
var p = new Person("jimmy"); 
console.log(p.name)  // jimmy
复制代码

使用new关键字时,大概执行以下步骤

  1. 创建一个新对象
  2. 将构造函数的作用域赋给新对象(this 指向新对象)
  3. 执行构造函数中的代码(为这个新对象添加属性或方法)
  4. 返回新对象

如果不用new 关键字

function Person(){
  this.name = 'jimmy';
}
var p = Person(); 
console.log(p) // undefined
console.log(name) // jimmy
console.log(p.name) // 'name' of undefined
复制代码

没有使用 new 这个关键词,因为Persion函数没有返回值,所以返回的结果是undefined。Persion中thisthis 的指向是 window(如果不清楚这里的this为什么指向window,可以参考我的这篇文章 this指向知多少),所以name 的输出结果就为jimmy。

使用new,但是有返回值

function Person(){
   this.name = 'jimmy'; 
   return {age: 18}
}
var p = new Person(); 
console.log(p)  // {age: 18}
console.log(p.name) // undefined
console.log(p.age) // 18
复制代码

构造函数最后 return 出来的是一个和 this 无关的对象时,new 命令会直接返回这个新对象,而不是通过 new 执行步骤生成新的实例对象。 但是这里要求构造函数必须是返回一个对象,如果返回的不是对象,那么还是会按照 new 的实现步骤,返回新生成的实例对象。

接下来在上面这段代码的基础之上稍微改动一下。

function Person(){
   this.name = 'jimmy'; 
   return 'chimmy';
}
var p = new Person(); 
console.log(p)  // {name: 'jimmy'}
console.log(p.name) // jimmy
复制代码

可以看出,当构造函数中 return 的不是一个对象时,那么它还是会根据 new 关键词的执行逻辑,生成一个新的对象,最后返回出来。

因此我们总结一下:new 关键词执行之后总是会返回一个对象,要么是实例对象,要么是 return 语句指定的对象。

apply & call & bind

all、apply 和 bind 是挂在 Function 对象上的三个方法,调用这三个方法的必须是一个函数。

基本语法以及介绍

func.call(thisArg, param1, param2, ...)
func.apply(thisArg, [param1,param2,...])
func.bind(thisArg, param1, param2, ...)
复制代码

func 是要调用的函数,thisArg 一般为 this 所指向的对象,后面的 param1、2 为函数 func 的多个参数,如果 func 不需要参数,则后面的 param1、2 可以不写。

这三个方法共有的、比较明显的作用就是,都可以改变函数 func 的 this 指向。call 和 apply 的区别在于,传参的写法不同:apply 的第 2 个参数为数组; call 则是从第 2 个至第 N 个都是给 func 的传参;而 bind 和这两个(call、apply)又不同,bind 虽然改变了 func 的 this 指向,但不是马上执行,而这两个(call、apply)是在改变了函数的 this 指向之后立马执行。

举个实际生活中的例子:
生活中我不经常做饭,家里没有锅,周末突然想给自己做个饭尝尝。但是家里没有锅,而我又不想出去买,所以就问隔壁邻居借了一个锅来用,这样做了饭,又节省了开销,一举两得。

对应在程序中:A 对象有个 getName 的方法,B 对象也需要临时使用同样的方法,那么这时候我们是单独为 B 对象扩展一个方法,还是借用一下 A 对象的方法呢?当然是可以借用 A 对象的 getName 方法,既达到了目的,又节省重复定义,节约内存空间。

let a = {
  name: 'jimmy',
  getName: function(msg) {
    return msg + this.name;
  } 
}
let b = {
  name: 'chimmy'
}
console.log(a.getName('hello~'));  // hello~jimmy
console.log(a.getName.call(b, 'hi~'));  // hi~chimmy
console.log(a.getName.apply(b, ['hi~']))  // hi~chimmy
let name = a.getName.bind(b, 'hello~');
console.log(name());  // hello~chimmy
复制代码

上面的代码执行的结果中可以发现,使用这三种方式都可以达成我们想要的目标,即通过改变 this 的指向,让 b 对象可以直接使用 a 对象中的 getName 方法。

call,apply,bind应用场景

判断数据类型

用 Object.prototype.toString 来判断类型是最合适的,借用它我们几乎可以判断所有类型的数据,我在js数据类型知多少中有介绍过,我将当时总结的用来判断数据类型的那部分代码粘贴在下面了,可以回忆一下。

function getType(type){
  if(typeof type !== "object"){
    return typeof type;
  }
  return Object.prototype.toString.call(type).slice(8, -1).toLowerCase();
}
复制代码

类数组借用方法

类数组因为不是真正的数组,所有没有数组类型上自带的种种方法,所以我们就可以利用一些方法去借用数组的方法,比如借用数组的 push 方法

var arrayLike = { 
  0: 'java',
  1: 'script',
  length: 2
} 
Array.prototype.push.call(arrayLike, 'jack', 'lily'); 
console.log(typeof arrayLike); // 'object'
console.log(arrayLike);
// {0: "java", 1: "script", 2: "jack", 3: "lily", length: 4}
复制代码

从上面的代码可以看到,arrayLike 是一个对象,模拟数组的一个类数组。从数据类型上看,它是一个对象。从上面的代码中可以看出,用 typeof 来判断输出的是 'object',它自身是不会有数组的 push 方法的,这里我们就用 call 的方法来借用 Array 原型链上的 push 方法,可以实现一个类数组的 push 方法,给 arrayLike 添加新的元素。

获取数组的最大 / 最小值

我们可以用 apply 来实现数组中判断最大 / 最小值,apply 直接传递数组作为调用方法的参数,也可以减少一步展开数组,可以直接使用 Math.max、Math.min 来获取数组的最大值 / 最小值,请看下面这段代码。

let arr = [13, 6, 10, 11, 16];
const max = Math.max.apply(Math, arr); 
const min = Math.min.apply(Math, arr);
 
console.log(max);  // 16
console.log(min);  // 6
复制代码

还有其他应用比如继承等,会在后面专门的章节讲解。

文章分类
前端
文章标签