call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

fun.call(thisArg[, arg1[, arg2[, ...]]])

apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数

fun.apply(thisArg, [argsArray])

bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数

fun.bind(thisArg[, arg1[, arg2[, ...]]])

当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。

特点:

返回一个函数

可以传入参数

1、理解

call方法原理

模拟Function中内置的call方法,写一个myCall方法,探讨call方法的执行原理

function sum(){
console.log(this);
}
function fn(){
console.log(this);
}
var obj = {name:'iceman'};
Function.prototype.myCall = function (context) {
// myCall方法中的this就是当前我要操作和改变其this关键字的那个函数名 // 1、让fn中的this关键字变为context的值->obj
// 让this这个函数中的"this关键字"变为context
// eval(this.toString().replace("this","obj")); // 2、让fn方法在执行
// this();
};
fn.myCall(obj);// myCall方法中原来的this是fn
sum.myCall(obj);// myCall方法中原来的this是sum

call方法经典例子

function fn1() {
console.log(1);
}
function fn2() {
console.log(2);
}
fn1.call(fn2); // 1

首先fn1通过原型链查找机制找到Function.prototype上的call方法,并且让call方法执行,此时call这个方法中的this就是要操作的fn1。在call方法代码执行的过程过程中,首先让fn1中的“this关键字”变为fn2,然后再让fn1这个方法执行。

fn1.call.call(fn2); // 2

2、区别

三个函数存在的区别, 用一句话来说的话就是: bind是返回对应函数, 便于稍后调用; apply, call则是立即调用,apply是call的一层封装,可以传数组。所以call比较快。 除此外, 在 ES6 的箭头函数下, call 和 apply 的失效, 对于箭头函数来说:

  • 函数体内的 this 对象, 就是定义时所在的对象, 而不是使用时所在的对象;
  • 不可以当作构造函数, 也就是说不可以使用 new 命令, 否则会抛出一个错误;
  • 不可以使用 arguments 对象, 该对象在函数体内不存在. 如果要用, 可以用 Rest 参数代替;
  • 不可以使用 yield 命令, 因此箭头函数不能用作 Generator 函数;

call 方法比 apply 快的原因是 call 方法的参数格式正是内部方法所需要的格式

3、模拟

思路
  • 将函数设为对象的属性
  • 执行该函数
  • 删除该函数

call

Function.prototype.call2 = function (context) {
var context = context || window;//null等传入指向window
context.fn = this;//将函数设为对象的属性 var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
} var result = eval('context.fn(' + args +')');//执行该函数 delete context.fn; // 删除函数
return result;
}

apply

Function.prototype.apply = function (context, arr) {
var context = Object(context) || window;
context.fn = this; var result;
if (!arr) {
result = context.fn();
}
else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context.fn(' + args + ')')
} delete context.fn
return result;
}

bind

一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

Bound.prototype = this.prototype,我们直接修改 Bound.prototype 的时候,也会直接修改绑定函数的 prototype。这个时候,我们可以通过一个空函数来进行中转

Function.prototype.bind = Function.prototype.bind || function (context) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
} var self = this;
var args = Array.prototype.slice.call(arguments, 1);//先取到bind时传参,bar.bind(foo, 'daisy'); var F = function () {};//继承用空函数来转 var Bound = function () {
var bindArgs = Array.prototype.slice.call(arguments);//在取到返回函数,bindFoo('18');
//如果是new的this指向F,this指向构造函数,如果不是指向要改变的this
return self.apply(this instanceof F ? this : context, args.concat(bindArgs)//合并参数);
} F.prototype = this.prototype;
Bound.prototype = new F();
return Bound;
};

4、案例

1、柯里化

var currying = function( fn ){
var args = [];
return function(){
if ( arguments.length === 0 ){
return fn.apply( this, args );
}else{
[].push.apply( args, arguments );
return arguments.callee;
}
}
};
var cost = (function(){
var money = 0;
return function(){
for ( var i = 0, l = arguments.length; i < l; i++ ){
money += arguments[ i ];
}
return money;
}
})();
var cost = currying( cost ); // 转化成 currying 函数
cost( 100 ); // 未真正求值
cost( 200 ); // 未真正求值
cost( 300 ); // 未真正求值
alert ( cost() ); // 求值并输出: var overtime = (function() {
var args = []; return function() {
if(arguments.length === 0) {
var time = 0;
for (var i = 0, l = args.length; i < l; i++) {
time += args[i];
}
return time;
}else {
[].push.apply(args, arguments);
}
}
})(); overtime(3.5); // 第一天
overtime(4.5); // 第二天
overtime(2.1); // 第三天
//... console.log( overtime() ); // 10.1

2、debounce 函数去抖

var debounce = function(idle, action){
var last
return function(){
var ctx = this, args = arguments
clearTimeout(last)
last = setTimeout(function(){
action.apply(ctx, args)
}, idle)
}
} var timer = null;
window.onscroll = function(){
if (timer) {
// 清除未执行的逻辑,重新执行下一次逻辑,不论上一次是否执行完毕
clearTimeout(timer);
}
timer = setTimeout(function(){
//执行逻辑
}, 300);
};

3、throttle 函数节流

var throttle = function ( fn, interval ) {
var __self = fn, // 保存需要被延迟执行的函数引用
timer, // 定时器
firstTime = true; // 是否是第一次调用
return function () {
var args = arguments,
__me = this;
if ( firstTime ) { // 如果是第一次调用,不需延迟执行
__self.apply(__me, args);
return firstTime = false;
}
if ( timer ) { // 如果定时器还在,说明前一次延迟执行还没有完成
return false;
}
timer = setTimeout(function () { // 延迟一段时间执行
clearTimeout(timer);
timer = null;
__self.apply(__me, args);
}, interval || 5000 );
};
};
window.onresize = throttle(function(){ console.log( 1 ); }, 5000 ); var can = true;
window.onscroll = function(){
if(!can){
//判断上次逻辑是否执行完毕,如果在执行中,则直接return
return;
}
can = false;
setTimeout(function(){
//执行逻辑
can = true;
}, 100);
};

4、反柯里化(uncurring)

Function.prototype.uncurring = function() {
var self = this; //self此时是Array.prototype.push return function() {
var obj = Array.prototype.shift.call(arguments);
//obj 是{
// "length": 1,
// "0": 1
//}
//arguments的第一个对象被截去(也就是调用push方法的对象),剩下[2] return self.apply(obj, arguments);
//相当于Array.prototype.push.apply(obj, 2);
};
}; //测试一下
var push = Array.prototype.push.uncurring();
var obj = {
"length": 1,
"0" : 1
}; push(obj, 2);
console.log( obj ); //{0: 1,1: 2, length: 2 }

5、取数组最大值

Math.max(1,2,3,4);

利用apply可以把传数组的方法

var max = Math.max.apply(null, ary); 

Math.max(1,2,3,4);

var max = eval("Math.max(" + ary.toString() + ")");

在非严格模式下,给apply的第一个参数为null的时候,会让max/min中的this指向window,然后将ary的参数一个个传给max/min

6、将类数组转换数组

slice在不穿参的情况下是复制数组

Array.prototype.slice = function() {
var result = [];
for(var i = 0; i < this.length; i++){
result[i] = this[i] };
return result;
} [1,2,3,4].slice()
=> [1, 2, 3, 4]
var obj = {length:'2', '0': 'aa', '1': 'bb'}
[].slice.call(obj)
=> ["aa", "bb"]
function listToArray(likeAry) {
var ary = [];
try {
ary = Array.prototype.slice.call(likeAry);
} catch (e) {
for (var i = 0; i < likeAry.length; i++) {
ary[ary.length] = likeAry[i];
}
}
return ary;
}

7、push的理解

Array.prototype.push = function(str){ return this.concat(str)  }
[1,2,3].push(1)
=>[1, 2, 3, 1]
[].push.call([1,2,3],3)
=> [1, 2, 3, 3]

5、caller与callee

caller

返回一个对函数的引用,该函数调用了当前函数。

对于函数来说,caller 属性只有在函数执行时才有定义。

如果函数是由顶层调用的,那么 caller 包含的就是 null 。

如果在字符串上下文中使用 caller 属性,那么结果和 functionName.toString 一样,也就是说,显示的是函数的反编译文本。

// caller demo {
function callerDemo() {
if (callerDemo.caller) {
var a= callerDemo.caller.toString();
alert(a);
} else {
alert("this is a top function");
}
}
function handleCaller() {
callerDemo(); //"function handleCaller() { callerDemo();}"
}

callee

返回正被执行的 Function 对象,也就是所指定的 Function 对象的正文。

ES5 提示: 在严格模式下,arguments.callee 会报错 TypeError,因为它已经被废除了

callee 属性是 arguments 对象的一个成员,它表示对函数对象本身的引用,这有利于匿名函数的递归或者保证函数的封装性

arguments.length是实参长度,arguments.callee.length是形参长度

在递归中有很好的应该

call、apply与bind在理解的更多相关文章

  1. 对于call,apply,bind 的理解

    JavaScript 中 call().apply().bind() 的用法 之前对与JavaScript中的call,apply,bind这几个方法一直理解的很模糊,今天总结一下. 例1 var n ...

  2. call,apply,bind的理解

    call,apply,bind均是用于改变this指向. 三者相似之处: 1:都是用于改变函数的this指向. 2:第一个参数都是this要指向的对象. 3:都可以通过后面的参数进行对方法的传参. l ...

  3. JS中的apply,call,bind深入理解

    在Javascript中,Function是一种对象.Function对象中的this指向决定于函数被调用的方式.使用apply,call 与 bind 均可以改变函数对象中this的指向,在说区别之 ...

  4. javascript中call()、apply()、bind()的用法终于理解

    其实是一个很简单的东西,认真看十分钟就从一脸懵B 到完全 理解! 先看明白下面: 例1 obj.objAge;  //17 obj.myFun()  //小张年龄undefined 例2 shows( ...

  5. 理解JS中的call、apply、bind方法(*****************************************************************)

    在JavaScript中,call.apply和bind是Function对象自带的三个方法,这三个方法的主要作用是改变函数中的this指向. call.apply.bind方法的共同点和区别:app ...

  6. 深入理解 call,apply 和 bind

    在JavaScript 中,call.apply 和 bind 是 Function 对象自带的三个方法,这三个方法的主要作用是改变函数中的 this 指向,从而可以达到`接花移木`的效果.本文将对这 ...

  7. 【JS】306- 深入理解 call,apply 和 bind

    作者:一像素 链接:https://www.cnblogs.com/onepixel/p/6034307.html 在JavaScript 中,call.apply 和 bind 是 Function ...

  8. call,apply,bind的理解

    2020-03-19 call,apply,bind的理解 先说区别call, apply基本上没什么不一样,唯一不一样的地方是传参方式不同 但是bind和call,apply有区别.bind是重新绑 ...

  9. bind、apply、call的理解

    一直感觉代码中有call和apply就很高大上(看不懂),但是都草草略过,今天非要弄明白!以前总是死记硬背:call.apply.bind 都是用来修改函数中的this,传参时,call是一个个传参, ...

随机推荐

  1. 运维自动化之Cobbler系统安装详解

    原文链接 参考文档 参考文档SA们现在都知道运维自动化的重要性,尤其是对于在服务器数量按几百台.几千台增加的公司而言,单单是装系统,如果不通过自动化来完成,根本是不可想象的. 运维自动化安装方面,早期 ...

  2. XGBoost算法原理小结

    在两年半之前作过梯度提升树(GBDT)原理小结,但是对GBDT的算法库XGBoost没有单独拿出来分析.虽然XGBoost是GBDT的一种高效实现,但是里面也加入了很多独有的思路和方法,值得单独讲一讲 ...

  3. IOS后台执行

    大多数应用程序进入后台状态不久后转入暂停状态.在这种状态下,应用程序不执行任何代码,并有可能在任意时候从内存中删除.应用程序提供特定的服务,用户可以请求后台执行时间,以提供这些服务. 判断是否支持多线 ...

  4. vue学习之路 - 3.基本操作(中)

    基本操作(中) 本章节主要介绍:vue的条件渲染.列表渲染,计算属性和侦听器 条件渲染和列表渲染 条件渲染主要使用到了 v-if 指令,列表渲染主要使用了 v-for 指令. 下面介绍 v-if .  ...

  5. 判断IP连接数前五,并自动加入防火墙

    #!/bin/bash #Author Template #Time -- : log_file=/tmp/tmp.log JudgeExt(){ if expr "$1" : & ...

  6. mysql零散操作

    添加对外用户 CREATE USER 'admin'@'%' IDENTIFIED BY '!QAZ2wsx'; GRANT ALL PRIVILEGES ON *.* TO 'admin'@'%'; ...

  7. Ansible学习 Inventory文件

    Ansible可同时操作属于一个组的多台主机,组与主机之间关系配置在inventory文件中,inventory默认的配置文件是/etc/ansible/hosts 1.在/etc/ansible/h ...

  8. 【JS】JS实现时间戳转换成普通时间

    var time = 1514457627; alert(getDate(time)); function getDate(tm){ var tt=new Date(parseInt(tm) * 10 ...

  9. 初学js之多组图片切换实例

    需求是以上效果展示.话不多说,直接代码显示,不涉及代码优化.已实现功能为目的. 先看html部分: <body> <div class="dream" id=&q ...

  10. 动态规划:POJ2576-Tug of War(二维费用的背包问题)

    Tug of War Time Limit: 3000MS Memory Limit: 65536K Description A tug of war is to be arranged at the ...