call、apply、bind三者有何异同

call bind apply的异同?

相同:用来指定一个函数内部的this的值,都可以手动改变原本this的指向;

不同:call和apply传参不同,call是依次传参,apply是数组传入,

bind是需要执行的,返回一个函数。

用法:

1)函数调用
var year = 2024
function getDate(month, day) {
  return this.year + '-' + month + '-' + day
}

let obj = { year: 2023 }
console.log(getDate.call(null, 4, 2))    // 2024-4-2
console.log(getDate.call(obj, 4, 2))     // 2023-4-2
console.log(getDate.apply(obj, [6, 1]))  // 2023-6-1
console.log(getDate.bind(obj)(4, 2))     // 2023-4-2
2)构造函数之间的互相调用
function Person() {
     this.age = 50;
     this.showAge= function() {
        console.log(this.age);
    }
}
function Son(){
    this.age = 20;
    Person.call(this); // 让Son也具有Person的方法
    //Person.apply(this)
}

var father = new Person();
var Joy = new Son();

// father.showAge.apply(Joy)  // 20
// father.showAge.call(Joy)  // 20
Joy.showAge();  // 不写 Person.call(this)会报错,showAge() is not a function
// 继承了Person的属性之后会覆盖Son定义的age,输出:50
3)多重继承

a:找出数组最大值和最小值,数组长度不固定

var arr  = [1,2,3,.......n]
Math.min.apply(this,arr) // this可随便换,但需是一个对象

b:合并数组

var arr1 = new Array(1, 2, 3);  
var arr2 = new Array(4, 5, 6);  
  
Array.prototype.push.apply(arr1, arr2); 

c:类数组共用数组方法


function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = Array.prototype.slice.call(arguments);
 
    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    var _adder = function() {
        _args.push(...arguments);
        return _adder;
}

function add() {
        // 第一次执行时,定义一个数组专门用来存储所有的参数
        var _args = [].slice.call(arguments);
        // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值,执行时已经收集所有参数为数组
        var adder = function () {
            var _adder = function () {
                // 执行收集动作,每次传入的参数都累加到原参数
                [].push.apply(_args, [].slice.call(arguments));
                return _adder;
            };
            // 利用隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
            _adder.toString = function () {
                return _args.reduce(function (a, b) {
                    return a + b;
                });
            }
            return _adder;
        }
        return adder(_args);
    }
}

1. call():

call 和 apply 的异同:   

call()方法接受的语法和作用与apply()方法类似,只有一个区别就是call()接受的是一个参数列表,而apply()方法接受的是一个包含多个参数的数组。

二者都是函数对象Function的方法,且第一个参数都是要绑定对象的上下文。

第一个参数就是需要绑定的this,后面都是参数。

apply 与 call() 非常相似,不同之处在于提供参数的方式。

let obj = {
    a: 1,
    get: function(){
        return 2
    }
}
let g = obj.get
g.call({},1,2,3)
g.apply({},[1,2,3])
call() 的使用场景:
1)调用父级构造函数
function Product(name, price){
    this.name = name;
    this.price = price;
    console.log('this.name', this.name)
    console.log('this.price', this.price)
    console.log('this.category', this.category)
}

// 调用父构造函数的call方法来实现继承
function Food(name, price){
    this.category = 'food';
    Product.call(this, name, price);
}

function Toy(name, price){
    this.category = 'toy';
    Product.call(this, name, price);
}

var cheese = new Food('hamburger', 15); // 会打印出 hamburger 15 food
var fun = new Toy('robot', 40); // 会打印出 robot 40 toy
// 将this指定为Food或Toy,新category 和旧name/price 都会存在
2)调用匿名函数
var animals = [
    {species: 'Lion', name: 'King'},
    {species: 'Whale', name: 'Fail'}
];

for(var i = 0; i < animals.length; i++) {
    (function(i) {
        this.print = function() {
            console.log('#' + i + ' ' + this.species + ': ' + this.name);
        }
        this.print();
    }).call(animals[i], i); // call调用匿名函数
}
3)调用函数并指定this

传第一个参数指定this,直接调用的话this指向window全局对象,
call不传参也一样指向全局 fn.call(), 严格模式下 this是 undefined

var name = 'Bella'

var obj = {
    name: 'Joy',
    age: 18,
};

function fn(){
    console.log(this.name)
    console.log(this.age)
}

fn.call(obj); // 会打印 Joy 18,将this指向了obj
fn() // 会打印 name为 Bella,age为 undefined

2. apply():

使用 apply, 我们可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。

apply 使用参数数组而不是一组参数列表。apply 可以使用数组字面量(array literal),如 fun.apply(this, [‘eat’, ‘bananas’]),或数组对象, 如 fun.apply(this, new Array(‘eat’, ‘bananas’))。

apply() 的使用场景:

1)调用一个具有给定this值的函数,以及以一个数组的形式提供参数

var array = ['apple','banana'];
var elements = [0,1,2];
array.push.apply(array, elements);
console.log(array);  // ['apple','banana', 0, 1, 2]

2)使用apply和内置函数

对于一些需要写循环以遍历数组各项的需求,我们可以用apply完成以避免循环。

// 找出数组中最大值和最小值
var numbers = [5, 6, 2, 3, 7];
// 使用Math.min和Math.max以及apply函数时的代码
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
console.log('最大值', max)
console.log('最小值', min)

上边这种调用apply的方法,有超出JavaScript引擎参数长度上限的风险。
如果我们的参数数组非常大,推荐使用下边这种混合策略:将数组切块后循环传入目标方法:

function minOfArray(arr) {
    var min = Infinity;
    var QUANTUM = 32768;
  
    for (var i = 0, len = arr.length; i < len; i += QUANTUM) {
      var submin = Math.min.apply(null, arr.slice(i, Math.min(i + QUANTUM, len)));
      min = Math.min(submin, min);
    }
  
    return min;
  }
  
  var min = minOfArray([5, 6, 2, 3, 7]);

思考:什么情况下用apply,什么情况下用call?

在给对象参数的情况下:

如果参数的形式是数组的时候,比如apply示例里面传递了参数arguments,这个参数是数组类型,并且在调用Person的时候参数的列表是对应一致的(也就是Person和Student的参数列表前两位是一致的) 就可以采用 apply。

如果我的Person的参数列表是这样的(age,name),而Student的参数列表是(name,age,grade),这样就可以用call来实现了,也就是直接指定参数列表对应值的位置(Person.call(this,age,name,grade));

call方法:call(obj,x,y,z,…)
apply方法:apply(obj,[x,y,z])

function Person(name, age) {  
    this.name = name;  
    this.age = age;  
}  
// 定义一个学生类
function Student(name, age, grade) {
    Person.apply(this, arguments);  // Person.call(this, name, age);
    this.grade = grade;  
}
// 创建一个学生实例 
var student = new Student("Joy", 21, "一年级"); 
// student继承了Person的name和age属性
console.log(student.name, student.age, student.grade) // Joy 21 一年级 

3: bind():

bind()函数会创建一个新的绑定函数,这个绑定函数包装了原函数的对象。调用绑定函数通常会执行包装函数。
绑定函数内部属性:

包装的函数对象
在调用包装函数时始终作为this传递的值
在对包装函数做任何调用时都会优先用列表元素填充参数列表。
而原函数 retrieveAge 中的 this 并没有被改变,依旧指向全局对象 window。

this.age = 9; // this指向全局的window对象
var person = {
    age: 18,
    getAge: function() {
        return this.age;
    }
};

console.log(person.getAge()); // 18, this指向person

var retrieveAge = person.getAge;
console.log(retrieveAge()); // 9, 调用是全局的,this是window,只是拿了getAge这个方法出来

// 创建一个新函数,把this绑定到person对象
var boundGetAge = retrieveAge.bind(person);
console.log(boundGetAge()); // 18

bind传递参数问题:
在通过bind改变this指向的时候所传入的参数会拼接在调用返回函数所传参数之前,多余参数不起作用。

var name = 'window';
var newThis = { name: 'newThis' };
function showName(info1, info2) {
    console.log(this.name, info1, info2);
}
showName('a', 'b'); //输出:window a b

// 通过bind改变this指向
var newShowName = showName.bind(newThis, 'hello', 'world');
newShowName('a', 'b', 'c'); // 如果上一行的参数足够,输出:newThis hello world,如果不够则会按序取本行参数

// 也就是说,bind会拼接参数,直到满足传参,多余的参数不起作用 不够的话就会是 undefined
// var newShowName = showName.bind(newThis, 'hello');
// newShowName('a', 'b', 'c'); // newThis hello a

// var newShowName = showName.bind(newThis);
// newShowName('a', 'b', 'c'); // newThis a b

console.log(new newShowName().constructor); //输出:showName函数体

bind无法改变构造函数的this指向,通过bind改变this指向返回函数的构造器还是最开始的showName函数。

new newShowName()实例化了一个新的方法,这个方法的this也不再指向newThis。

手写bind:

// 1. 需求:手写bind => bind位置(挂载哪里)=> Function.prototype
Function.prototype.newBind = function() {
  // 2. bind是什么?
  const _this = this
  const args = Array.prototype.slice.call(arguments) // Array的slice方法,arguments是一个伪数组,转换为数组
  // 输入: args特点,第一项是this,第二项~最后一项函数传参
  const newThis = args.shift()
  // 返回: 返回一个函数 => 构造一个函数 => 这个函数返回原函数的结果且传参继承
  return function(){
    return _this.newApply(newThis, args)
  }
}

Function.prototype.newApply = function(context) {
  // 边缘检测
  if(typeof this !=='function') {
    throw new TypeError('type error')
  }
  // 兜底
  context = context || window

  // 执行函数替换
  context.fn = this
  // 临时挂载执行fn => 销毁临时挂载
  let result = arguments[1]
      ? context.fn(...arguments[1])
      :context.fn()

  delete context.fn
  // 返回结果
  return result
}

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值