使用另一种方式实现js中Function的调用(call/apply/bind)

时间:2022-09-21 20:08:39

在JavaScript中函数的调用可以有多种方式,但更经典的莫过于call和apply。call跟apply都绑定在函数上,他们两个的第一个参数意义相同,传入一个对象,他作为函数的执行环境(实质上是为了改变函数的Execution Context执行上下文),也就是this的指向;而第二个参数两者只是类型的不同,call传的是arguments,而apply传的是array。废话不多说,先上一个最基础的例子:

function add(c,d){
return this.a + this.b + c + d;
}
var o = {
a: 1,
b: 2
}
add.call(o,3,4) //
add.apply(o,[3,4]) //

再比如我之前看过的Twitter上的关于call和apply的一个面试题:定义一个函数log,传入任意数量参数,让它模拟console.log的方法,形如log('hello','world');

function log(){
console.log.apply(console,arguments);
}

  使用另一种方式实现js中Function的调用(call/apply/bind)

当执行这个log函数时,该函数的执行上下文为console对象,arguments为传入的实参。

然后此题又有需求,如果要给每个log信息加一个(app)前缀,比如 '(app) hello world';

这时我们应该想到一点,那就是我们传入的实参,也就是arguments并不是一个数组,它实质上只是一个类数组的对象,我们在这里可以用 instanceof 来判断自定义对象。

使用另一种方式实现js中Function的调用(call/apply/bind)使用另一种方式实现js中Function的调用(call/apply/bind)

instanceof的意思就是看左边对象的原型链上是否有右边构造器的prototype属性。由上图可以看出arguments是一个类数组的对象,并且可以看出arguments没有array的方法。

所以回到刚才那个题,我们必须让传入的实参变为Array类型才可以调用数组的unshift方法(在它的前面加上app)。

function log(){
var args = Array.prototype.slice.call(arguments);
args.unshift('(app)');
console.log.apply(console,args);
} log('hello','world'); // (app) hello world

在OOP面向对象中此方法用的更加多一些,当我们不想为另一个对象创建方法的时候,可以用call调用foo的方法。

function foo () {}
foo.prototype.name = 'james';
foo.prototype.sayHello = function(){
console.log(this.name);
}
var obj1 = new foo();
obj1.sayHello(); //james
var obj2 = {
name: 'bond'
}
obj1.sayHello.call(obj2); //bond

再比如如果要想调用一些不能直接调用的方法,比如Object.prototype.toString(),我们也可以用call,它的本质在于将内部的变量改为包装对象。

这个对象原型的方法指向window,也就是this,但是如果把log函数里call指向的对象改为window的话,就会输出global。因为在执行上下文中的全局对象为Global Context,

比方说全局的Math,String,window都存在于[[global]]变量对象中。

window对象依附于Global全局变量对象,虽然权威上来说在NodeJS里全局是Global对象,但是在全局执行上下文中window指向global,请看下图。

使用另一种方式实现js中Function的调用(call/apply/bind)

function log(){
console.log(this===window); // true
return Object.prototype.toString.call(this);
}
log.call(5) // [Object Number]
log.call(true) // [Object Boolean]

使用另一种方式实现js中Function的调用(call/apply/bind)

对于js中的bind方法,他跟apply和call基本一样,里面传递的参数也是改变this指向的,比方下面一个关于执行上下文的代码,通过bind改变this的指向。

var User = {
count: 1,
getCount: function(){
return this.count;
}
}
var func = User.getCount;
console.log(func()); //undefined
var func1 = User.getCount.bind(User) 
console.log(func1());  // 1
 

上面的答案是undefined,因为func是在全局作用域window中,window里面没有count属性,所以我们为了让this指向User对象,我们便想到了使用bind。但是bind是es5才有的方法,不兼容老版本浏览器,那如何解决这个问题呢?下面是我从火狐的MDN上拿下来的bind模拟。

if(!Function.prototype.bind){
Function.prototype.bind = function(oThis){
if(typeof this !== 'function'){
throw new TypeError('What is trying to be bound is not callable');
}
var args = Array.prototype.slice.call(arguments,1),
fTobind = this, //指向调用bind的函数
fNOP = function(){}, //创建一个空函数,为了下面的继承
fBound = function(){
return fTobind.apply(this instanceof fNOP ? this : oThis, //改变this的指向
args.concat(Array.prototype.slice.call(arguments))); //将通过bind传递的参数与调用时传的参数合并
};
fNOP.prototype = this.prototype; //将目标函数的原型传递到新函数中
fBound.prototype = new fNOP; //这两条相当于Object.create的作用
return fBound;
}
}

我们通过下面这个例子来分析它吧:

function foo(){
this.b = 100;
return this.a;
};
var func = foo.bind({a:1});
func() //
new func() // {b:100}

fBound指向新函数func,因为foo调用的bind方法,所以fToBind指向目标函数foo,在func()里面this指向传进去的对象,也就是bind模拟里面的oThis,通过this instanceof fNOP来判断this的指向,因为func()是window调用下的,所以this指向window,所以this instanceof fNOP 返回false,所以oThis也就是传入的{a:1}为当前的执行上下文,所以弹出1。但如果是对象调用(  new Func()  )的话,this instanceof fNOP中的this会指向一个空对象,空对象的原型会指向构造器的prototype属性,即func的prototype属性,这里注意一点,因为foo 中 return的不是对象,所以忽略return。

使用另一种方式实现js中Function的调用(call/apply/bind)的更多相关文章

  1. JavaScript 基础——使用js的三种方式,js中的变量,js中的输出语句,js中的运算符;js中的分支结构

    JavaScript 1.是什么:基于浏览器 基于(面向)对象 事件驱动 脚本语言 2.作用:表单验证,减轻服务器压力 添加野面动画效果 动态更改页面内容 Ajax网络请求 () 3.组成部分:ECM ...

  2. 在单文件组件中,引入安装模块里的css的2种方式:script中引入、style中引入

    在单文件组件中,引入安装模块里的css的2种方式:script中引入.style中引入 1.script中引入 <script> import 'bulma/css/bulma.css' ...

  3. 关于js中函数的调用问题

    js中函数的调用方法 1.直接调用 函数名(参数): 2.通过指向函数的变量去调用 例如: var myval = 函数名: 此刻 myval是指向函数的一个指针: myval(实际参数):此刻调用的 ...

  4. js中function的与众不同

    js中function的与众不同在于可以被调用

  5. 前端面试 js 你有多了解call&comma;apply&comma;bind?

    函数原型链中的 apply,call 和 bind 方法是 JavaScript 中相当重要的概念,与 this 关键字密切相关,相当一部分人对它们的理解还是比较浅显,所谓js基础扎实,绕不开这些基础 ...

  6. js创建对象的三种方式和js工厂模式创建对象

    文章地址: https://www.cnblogs.com/sandraryan/ 创建对象 创建对象的三种方式 构造函数 ,是一种特殊的方法.主要用来在创建对象时初始化对象 1. 调用系统的构造函数 ...

  7. 【转】三种方式在C&plus;&plus;中调用matlab

      C/C++调用Matlab 在工程实践中,C/C++调用Matlab 的方法主要有调用Matlab 计算引擎.包含m 文件转 换的C/C++文件,以及调用m文件生成的DLL 文件. 1 利用Mat ...

  8. 总结javascript继承的两种方式的N中写法

    最近翻看博客园,总结了一下javascript的继承方式:prototype和copy继承方式. 一.prototype方式 当一个函数被创建时,Function构造函数产生的函数会隐式的被赋予一个p ...

  9. promise核心技术 2&period;两种回调函数 js中error的处理

    抽空详细学习一下什么是回调函数(一个回调函数,也被称为高阶函数) 1.什么样的函数是回调函数 自己定义的(sittimeout不是自己定义的) 没有调用 自己执行 1.同步回调与异步回调函数 同步回调 ...

随机推荐

  1. Swift基础语法 、 元组(Tuple)

    字符串的使用 1.1 问题 Swift的String和Character类型提供了一个快速的,兼容Unicode的方式来处理代码中的文本信息.创建和操作字符串的语法与C语言中字符串类似.本案例将学习如 ...

  2. POJ1229 域名匹配

    给你两个域名,域名中包含一些通配符. * :匹配一个或任意多个部分 ?:匹配一个或三个部分 !:匹配三个以上部分. 求这两个域名是否能够表示同一个域名? 域名的长度不超过255. 分析:设给出的域名为 ...

  3. MSSQL常用操作及方法总结

    1.在安装Sql或sp补丁的时候系统提示之前有挂起的安装操作,要求重启的解决办法: 到注册表中找到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control ...

  4. asp&period;net mvc3 数据验证(二)——错误信息的自定义及其本地化

    原文:asp.net mvc3 数据验证(二)--错误信息的自定义及其本地化 一.自定义错误信息         在上一篇文章中所做的验证,在界面上提示的信息都是系统自带的,有些读起来比较生硬.比如: ...

  5. windows API实现用户选择文件路径的对话框

    在编写应用程序时,有时需要用户选择某个文件,以供应用程序使用,比如在某些管理程序中需要打开某一个进程,这个时候需要弹出一个对话框来将文件路径以树形图的形式表示出来,以图形化的方式供用户选择文件路径,而 ...

  6. PHP安装后php-config命令干嘛的

    php-config 是一个简单的命令行脚本用于查看所安装的 PHP 配置的信息. 我们在命令行执行 php-config 会输出所有的配置信息 Usage: /usr/local/php/bin/p ...

  7. selenium&plus;chromedriver刷点击量

    #coding=utf-8 import re import time import json import requests from selenium import webdriver from ...

  8. 如何让Excel单元格中的名字分散对齐

    1 操作方式 开始->对齐方式->对齐->水平对齐->分散对齐(缩进) 2 优势 不会破坏数据的有效性

  9. springMVC之mvc&colon;interceptors拦截器的用法

    1.配置拦截器 在springMVC.xml配置文件增加: <mvc:interceptors> <!-- 日志拦截器 --> <mvc:interceptor> ...

  10. Python学习笔记(五)OOP

    模块 使用模块import 模块名.有的仅仅导入了某个模块的一个类或者函数,使用from 模块名 import 函数或类名实现.为了避免模块名冲突.Python引入了按文件夹来组织模块的方法,称为包( ...