资讯专栏INFORMATION COLUMN

JavaScript基础心法——call apply bind

techstay / 476人阅读

摘要:原文地址基础心法欢迎。也就是说,这三个方法可以改变函数体内部的指向。令为一个空列表。提供作为值并以作为参数列表,调用的内部方法,返回结果。在外面传入的值会修改并成为值。语法其中,就是指向,是指定的参数。

原文地址:JavaScript基础心法——call apply bind

欢迎star。

如果有错误的地方欢迎指正。

整理callapplybind这三个方法的的知识点。

之前这篇文章提到过this的各种情况,其中有一种情况就是通过callapplybind来将this绑定到指定的对象上。

也就是说,这三个方法可以改变函数体内部this的指向。

这三个方法有什么区别呢?分别适合应用在哪些场景中呢?

先举个简单的栗子 ~

var person = {
  name: "axuebin",
  age: 25
};
function say(job){
  console.log(this.name+":"+this.age+" "+job);
}
say.call(person,"FE"); // axuebin:25 FE
say.apply(person,["FE"]); // axuebin:25 FE
var sayPerson = say.bind(person,"FE");
sayPerson(); // axuebin:25 FE

对于对象person而言,并没有say这样一个方法,通过call/apply/bind就可以将外部的say方法用于这个对象中,其实就是将say内部的this指向person这个对象。

call

call是属于所有Function的方法,也就是Function.prototype.call

The call() method calls a function with a given this value and arguments provided individually.

call() 方法调用一个函数, 其具有一个指定的this值和分别地提供的参数(参数的列表)。

它的语法是这样的:

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

其中,thisArg就是this指向,arg是指定的参数。

call的用处简而言之就是可以让call()中的对象调用当前对象所拥有的function。

ECMAScript规范

ECMAScript规范中是这样定义call的:

当以thisArg和可选的arg1,arg2等等作为参数在一个func对象上调用call方法,采用如下步骤:

如果IsCallable(func)false, 则抛出一个TypeError异常。

argList为一个空列表。

如果调用这个方法的参数多余一个,则从arg1开始以从左到右的顺序将每个参数插入为argList的最后一个元素。

提供thisArg作为this值并以argList作为参数列表,调用func[[Call]]内部方法,返回结果。

call方法的length属性是1。

在外面传入的thisArg值会修改并成为this值。thisArgundefinednull时它会被替换成全局对象,所有其他值会被应用ToObject并将结果作为this值,这是第三版引入的更改。

使用call调用函数并且指定this
var obj = {
  a: 1
}
function foo(b, c){
  this.b = b;
  this.c = c;
  console.log(this.a + this.b + this.c);
}
foo.call(obj,2,3); // 6
call实现继承

在需要实现继承的子类构造函数中,可以通过call调用父类构造函数实现继承。

function Person(name, age){
  this.name = name;
  this.age = age;
  this.say = function(){
    console.log(this.name + ":" + this.age);
  }
}
function Student(name, age, job){
  Person.call(this, name ,age);
  this.job = job;
  this.say = function(){
    console.log(this.name + ":" + this.age + " " + this.job);
  }
}
var me = new Student("axuebin",25,"FE");
console.log(me.say()); // axuebin:25 FE
apply

apply也是属于所有Function的方法,也就是Function.prototype.apply

The apply() method calls a function with a given this value, and arguments provided as an array (or an array-like object).

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

它的语法是这样的:

fun.apply(thisArg, [argsArray]);

其中,thisArg就是this指向,argsArray是指定的参数数组。

通过语法就可以看出callapply的在参数上的一个区别:

call的参数是一个列表,将每个参数一个个列出来

apply的参数是一个数组,将每个参数放到一个数组中

ECMAScript规范

当以thisArgargArray为参数在一个func对象上调用apply方法,采用如下步骤:

如果IsCallable(func)false, 则抛出一个TypeError异常 .

如果argArraynullundefined, 则

返回提供thisArg作为this值并以空参数列表调用func[[Call]]内部方法的结果。

如果Type(argArray)不是Object, 则抛出一个TypeError异常 .

len为以"length"作为参数调用argArray[[Get]]内部方法的结果。

nToUint32(len).

argList为一个空列表 .

index为0.

只要index<n就重复

indexNameToString(index).

nextArg为以indexName作为参数调用argArray[[Get]]内部方法的结果。

nextArg作为最后一个元素插入到argList里。

设定indexindex + 1.

提供thisArg作为this值并以argList作为参数列表,调用func[[Call]]内部方法,返回结果。

apply方法的length属性是 2。

在外面传入的thisArg值会修改并成为this值。thisArgundefinednull时它会被替换成全局对象,所有其他值会被应用ToObject并将结果作为this值,这是第三版引入的更改。

用法

在用法上applycall一样,就不说了。

实现一个apply

参考链接:https://github.com/jawil/blog/issues/16

第一步,绑定上下文
Function.prototype.myApply=function(context){
  // 获取调用`myApply`的函数本身,用this获取
  context.fn = this;
  // 执行这个函数
  context.fn();
  // 从上下文中删除函数引用
  delete context.fn;
}

var obj ={
  name: "xb",
  getName: function(){
    console.log(this.name);
  }
}

var me = {
  name: "axuebin"
}

obj.getName(); // xb 
obj.getName.myApply(me); // axuebin

确实成功地将this指向了me对象,而不是本身的obj对象。

第二步,给定参数

上文已经提到apply需要接受一个参数数组,可以是一个类数组对象,还记得获取函数参数可以用arguments吗?

Function.prototype.myApply=function(context){
  // 获取调用`myApply`的函数本身,用this获取
  context.fn = this;
  // 通过arguments获取参数
  var args = arguments[1];
  // 执行这个函数,用ES6的...运算符将arg展开
  context.fn(...args);
  // 从上下文中删除函数引用
  delete context.fn;
}

var obj ={
  name: "xb",
  getName: function(age){
    console.log(this.name + ":" + age);
  }
}

var me = {
  name: "axuebin"
}

obj.getName(); // xb:undefined
obj.getName.myApply(me,[25]); // axuebin:25

context.fn(...arg)是用了ES6的方法来将参数展开,如果看过上面那个链接,就知道这里不通过...运算符也是可以的。

原博主通过拼接字符串,然后用eval执行的方式将参数传进context.fn中:

for (var i = 0; i < args.length; i++) {
  fnStr += i == args.length - 1 ? args[i] : args[i] + ",";
}
fnStr += ")";//得到"context.fn(arg1,arg2,arg3...)"这个字符串在,最后用eval执行
eval(fnStr); //还是eval强大
第三步,当传入apply的this为null或者为空时

我们知道,当apply的第一个参数,也就是this的指向为null时,this会指向window。知道了这个,就简单了~

Function.prototype.myApply=function(context){
  // 获取调用`myApply`的函数本身,用this获取,如果context不存在,则为window
  var context = context || window;
  context.fn = this;
  //获取传入的数组参数
  var args = arguments[1];
  if (args == undefined) { //没有传入参数直接执行
    // 执行这个函数
    context.fn()
  } else {
    // 执行这个函数
    context.fn(...args);
  }
  // 从上下文中删除函数引用
  delete context.fn;
}

var obj ={
  name: "xb",
  getName: function(age){
    console.log(this.name + ":" + age);
  }
}

var name = "window.name";

var me = {
  name: "axuebin"
}

obj.getName(); // xb:25
obj.getName.myApply(); // window.name:undefined
obj.getName.myApply(null, [25]); // window.name:25
obj.getName.myApply(me, [25]); // axuebin:25
第四步 保证fn函数的唯一性

ES6中新增了一种基础数据类型Symbol

const name = Symbol();
const age = Symbol();
console.log(name === age); // false

const obj = {
  [name]: "axuebin",
  [age]: 25
}

console.log(obj); // {Symbol(): "axuebin", Symbol(): 25}
console.log(obj[name]); // axuebin

所以我们可以通过Symbol来创建一个属性名。

var fn = Symbol();
context[fn] = this;
完整的apply
Function.prototype.myApply=function(context){
  // 获取调用`myApply`的函数本身,用this获取,如果context不存在,则为window
  var context = context || window;
  var fn = Symbol();
  context[fn] = this;
  //获取传入的数组参数
  var args = arguments[1];
  if (args == undefined) { //没有传入参数直接执行
    // 执行这个函数
    context[fn]()
  } else {
    // 执行这个函数
    context[fn](...args);
  }
  // 从上下文中删除函数引用
  delete context.fn;
}

这样就是一个完整的apply了,我们来测试一下:

var obj ={
  name: "xb",
  getName: function(age){
    console.log(this.name + ":" + age);
  }
}

var name = "window.name";

var me = {
  name: "axuebin"
}

obj.getName(); // xb:25
obj.getName.myApply(); // window.name:undefined
obj.getName.myApply(null, [25]); // window.name:25
obj.getName.myApply(me, [25]); // axuebin:25

ok 没啥毛病 ~

再次感谢1024大佬 ~

bind

The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

语法:

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

其中,thisArg就是this指向,arg是指定的参数。

可以看出,bind会创建一个新函数(称之为绑定函数),原函数的一个拷贝,也就是说不会像callapply那样立即执行。

当这个绑定函数被调用时,它的this值传递给bind的一个参数,执行的参数是传入bind的其它参数和执行绑定函数时传入的参数。

用法

当我们执行下面的代码时,我们希望可以正确地输出name,然后现实是残酷的

function Person(name){
  this.name = name;
  this.say = function(){
    setTimeout(function(){
      console.log("hello " + this.name);
    },1000)
  }
}
var person = new Person("axuebin");
person.say(); //hello undefined

这里this运行时是指向window的,所以this.nameundefined,为什么会这样呢?看看MDN的解释:

由setTimeout()调用的代码运行在与所在函数完全分离的执行环境上。这会导致,这些代码中包含的 this 关键字在非严格模式会指向 window。

有一个常见的方法可以使得正确的输出:

function Person(name){
  this.name = name;
  this.say = function(){
    var self = this;
    setTimeout(function(){
      console.log("hello " + self.name);
    },1000)
  }
}
var person = new Person("axuebin");
person.say(); //hello axuebin

没错,这里我们就可以用到bind了:

function Person(name){
  this.name = name;
  this.say = function(){
    setTimeout(function(){
      console.log("hello " + this.name);
    }.bind(this),1000)
  }
}
var person = new Person("axuebin");
person.say(); //hello axuebin
MDN的Polyfill
Function.prototype.bind = function (oThis) {
  var aArgs = Array.prototype.slice.call(arguments, 1);
  var fToBind = this;
  var fNOP = function () {};
  var fBound = function () {
    fBound.prototype = this instanceof fNOP ? new fNOP() : fBound.prototype;
    return fToBind.apply(this instanceof fNOP ? this : oThis || this, aArgs )
  }   
  if( this.prototype ) {
    fNOP.prototype = this.prototype;
  }
  return fBound;
}
总结

三者都是用来改变函数的this指向

三者的第一个参数都是this指向的对象

bind是返回一个绑定函数可稍后执行,callapply是立即调用

三者都可以给定参数传递

call给定参数需要将参数全部列出,apply给定参数数组

感谢

不用call和apply方法模拟实现ES5的bind方法

深入浅出妙用 Javascript 中 apply、call、bind

回味JS基础:call apply 与 bind

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/91926.html

相关文章

  • JavaScript基础心法——this

    摘要:原文地址基础心法欢迎。作为一个构造函数被绑定到正在构造的新对象。通过构造函数创建一个对象其实执行这样几个步骤创建新对象将指向这个对象给对象赋值属性方法返回所以就是指向创建的这个对象上。 原文地址:JavaScript基础心法——this 欢迎star。 如果有错误的地方欢迎指正。 看看这个有着深不可测的魔力的this到底是个什么玩意儿 ~ 什么是this 在传统面向对象的语言中,比如...

    hover_lew 评论0 收藏0
  • 细数 JavaScript 实用黑科技(二)

    摘要:前言书接上文细数实用黑科技一本文介绍独孤九剑和两篇最高内功心法。可以将变量转换为布尔值。可以把任何类型的值转换为布尔值,并且只有当这个变量的值为的时候才会返回,其他情况都返回。同样的,函数体内部声明的函数,作用域绑定函数体内部。 showImg(https://segmentfault.com/img/remote/1460000016507838); 前言 书接上文:细数 JavaS...

    马忠志 评论0 收藏0
  • JS基础篇--callapplybind方法详解

    摘要:首先我们可以通过给目标函数指定作用域来简单实现方法保存,即调用方法的目标函数考虑到函数柯里化的情况,我们可以构建一个更加健壮的这次的方法可以绑定对象,也支持在绑定的时候传参。原因是,在中,多次是无效的。而则会立即执行函数。 bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。 apply、call 在 javascript 中,call 和 apply 都是...

    lastSeries 评论0 收藏0
  • javascript 基础call, apply, bind

    摘要:系统,扎实的语言基础是一个优秀的前端工程师必须具备的。第一个参数为调用函数时的指向,随后的参数则作为函数的参数并调用,也就是。和的区别只有一个,就是它只有两个参数,而且第二个参数为调用函数时的参数构成的数组。 系统,扎实的 javascript 语言基础是一个优秀的前端工程师必须具备的。在看了一些关于 call,apply,bind 的文章后,我还是打算写下这篇总结,原因其实有好几个。...

    xeblog 评论0 收藏0
  • javascript基础:this关键字

    摘要:它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用类似的还有。总结关键字就是,谁调用我,我就指向谁。注意由于已经被定义为函数内的一个变量。因此通过关键字定义或者将声明为一个形式参数,都将导致原生的不会被创建。 题目 封装函数 f,使 f 的 this 指向指定的对象 。 输入例子 bindThis(function(a, b) { return this.test +...

    LeoHsiun 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<