1. 知识点补充:

首先在模拟实现前,先Mark一些我之前不知道的知识:

a. eval(string)函数:可计算某个字符串,并执行其中的JavaScript代码

其中,string是必需传入的待计算或待执行的语句,并且必须是原始字符串的形式!

eval(string)相当于<script> string </script>

b. 类数组对象(Array-like Object)

类数组对象是一个对象,比如:arguments、DOM API返回的NodeList对象都属于类数组对象,具有指向对象元素的数组index下标和length属性,但是它们不能使用push/pop/shift/unshift等数组方法!

但是如何能将类数组转换为真正的数组呢?有如下方法:

  1. Array.prototype.slice.call( arguments )  // 在低版本IE下不支持
  2. [].slice.call( arguments )      // 等同于1
  3. let arr = Array.from( arguments )  // ES6,可将类数组对象和可遍历对象转为真正的数组
  4. let arr = [ ...arguments ]

以下例为例演示:

var foo = {
value: 1
}; function bar(name, age) {
console.log(this.value);
   console.log(name)
   console.log(age)
} bar.call(foo, 'ning', 20);

这里可以考虑将bar这个函数作为foo的一个方法,然后在外层执行这个函数,然后再删掉该函数即可!

2. call的模拟实现

    Function.prototype.call2 = function (context) {
context.fn = this; // context是foo,this是bar也就是调用call的那个函数
context.fn();
delete context.fn;
} // 使用下例来验证call2是否可行
var foo = {
value: 1
}
function bar() {
console.log(this.value);
} bar.call2(foo);

content.fn = this;这句首先获取到了调用call的函数,本例这里也就是bar;

context.fn();即执行bar这个函数;

delete删掉该函数。

但是现在的模拟中有几个问题:

  1. 不能传入参数,因此我们将利用arguments,从Arguments对象中从第二个参数(因为第一个参数是this)开始取值,放到一个数组里,再把这个数组放到要执行的函数的参数里
  2. this参数传入null或者undefined时,我们需要将this指向window
  3. 当call2()内传的不是一个对象,而是一个基本数据类型时,如何处理?(在call实现时会自动调用Object()转换)
  4. 函数可以有返回值

所以我们得到以下call2()代码:

    Function.prototype.call2 = function (context) {
context = context ? Object(context) : window;
context.fn = this; var arr = [];
for (var i = 1, len = arguments.length; i < len; i++) {
arr.push('arguments[' + i + ']');
} var result = eval('context.fn(' + arr + ')');
delete context.fn;
return result;
}

下面我们测试一下:

    var value = 'global';
var foo = {
value: 1
}
function bar(name, age) {
console.log(this.value)
return {
value: this.value,
name: name,
age: age
}
} bar.call2(null) // global console.log(bar.call2(foo, 'ning', 20))
//
// {value: 1, name: "ning", age: 20}

说明两点:

  1. arr.push('arguments['+ i +']');这句得到的是(2) ["arguments[1]", "arguments[2]"]一个新数组,是我们想要的
  2. eval('context.fn('+ arr +')');这句中arr会自动调用arr.toString()得到一个字符串:arguments[1],arguments[2],然后进行字符串拼接

下面给出ES6版本的:

    Function.prototype.call2 = function (context) {
context = context ? Object(context) : window;
context.fn = this; let arr = [...arguments].slice(1);
let result = context.fn(' + arr + '); delete context.fn;
return result;
}

3. apply的模拟实现:

    Function.prototype.apply2 = function (context, arr) {
context = context ? Object(context) : window;
context.fn = this; var result = [];
// 没有arr参数直接执行
if (!arr) {
result = context.fn();
// 有arr参数则将参数拼接后执行
} else {
var args = [];
for (var i = 0; i < arr.length; i++) {
args.push('arr[' + i + ']')
}
result = eval('context.fn(' + args + ')')
} delete context.fn;
return result;
}

下面给出ES6版本的:

    Function.prototype.apply2 = function (context, arr) {
context = context ? Object(context) : window;
context.fn = this; let result = [];
if (!arr) {
result = context.fn();
} else {
// ...arr的使用
result = context.fn(...arr)
} delete context.fn;
return result;
}

模拟实现call、apply的更多相关文章

  1. js 模拟call、apply、bind实现

    1.模拟call实现 Function.prototype.myCall = function (context) { var context = context || window // 给 con ...

  2. JavaScript模拟call和apply的实现

    参考: call和apply的模拟实现 1. call:调用一个对象的一个方法,用另一个对象替换当前对象.例如:B.call(A, args1,args2);即A对象调用B对象的方法. /*call( ...

  3. call、apply、bind的区别,模拟call、apply和bind的实现

    bind:bind绑定完this的指向后会返回一个新的函数体,不会被立即调用   call&apply:绑定完this的指向后会立即调用   call与apply的区别:     call:第 ...

  4. call, apply, bind 区别

    #call, apply, bind 区别及模拟实现call apply bind 三者都可以用来改变this的指向,但是在用法上略有不同  首先说一下call和apply的区别 call和apply ...

  5. Vue.js组件之间的通信

    导语:组件之间的关系不外乎两种, 父子组件和非父子组件,本文将对两类组件之间的通信方式进行详细阐述. 父子组件间的通信 通信方式1(单向绑定): Props down, Events up (建议使用 ...

  6. vue以及js的一些坑或常用技巧

    判断空object Object.getOwnPropertyNames(obj).length === 0 模拟range Array.apply(null, Array(5)).map(funct ...

  7. 22 道高频 JavaScript 手写面试题及答案

    实现防抖函数(debounce) 防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时. 那么与节流函数的区别直接看这个动画实现即可. 手写简化版: // 防抖函数 cons ...

  8. JS中this的指向问题&使用call或apply模拟new

    this的指向由调用时决定而不是定义时决定,定义的方式: //直接定义在函数里 var a="window中的a"; var name="window"; fu ...

  9. JavaScript深入之call和apply的模拟实现

    call 一句话介绍 call: call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法. 举个例子: var foo = { value: 1 }; func ...

  10. call和apply的模拟实现

    call 一句话介绍 call: call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法. 举个例子: var foo = { value: 1 }; func ...

随机推荐

  1. 2018-06-05(thinking in java)

    1:类只能有2种访问权限,一种是不写访问权限:默认包访问权限,另一种是public.且类只能有一个pubic 2:方法有4种访问权限,pubic(都可以访问),protexted(继承访问权限),pr ...

  2. var 更明确地表示一个变量被设置为零值

    创建一个变量并被初始化其为零值,习惯使用关键字var.这种做法是为了更明确地表示一个变量被设置为零值. 如果变量被初始化为某个非零值,就配合结构字面量和短变量操作符来创建变量. 零值 数值类型:0 字 ...

  3. 把Debian 设置中文环境

    要支持区域设置,首先要安装locales软件包: apt-get install locales 然后配置locales软件包: dpkg-reconfigure locales 在界面中钩选上“zh ...

  4. [17]APUE:线程

    通常情况下,线程模型的并发性能优于进程模型,但不总是这样 线程的优势: 线程的创建.销毁及上下文切换代价比进程低 某些情况下,使用线程可以简化逻辑,避免异步编程的复杂性 同一进程内所有线程共享全局内存 ...

  5. JS与Jquery的事件委托机制

    传送:http://www.ituring.com.cn/article/467 概念: 什么是事件委托:通俗的讲,事件就是onclick,onmouseover,onmouseout,等就是事件,委 ...

  6. Hadoop搭建,上传文件时出现错误,没有到主机的路由

    解决方案:(1)从namenode主机ping其它slaves节点的主机名(注意是slaves节点的主机名),如果ping不通,原因可能是namenode节点的/etc/hosts 未配置主机名与IP ...

  7. unity3D笔记の四种调用其他脚本方法

    第一种,被调用脚本函数为static类型,调用时直接用  脚本名.函数名() 第二种,GameObject.Find("脚本所在的物体的名字").SendMessage(" ...

  8. Delphi 第2课

    项目文件.dpr单元文件.pas--目标文件.dcu窗体文件.dfu begin 对象名.属性 对象名.方法end; 在delphi 自动提示控制语句结构 键盘输入Ctrl 就可以看到了 代码错误:( ...

  9. Java 几种队列区别的简单说明

    前言 队列,字面意思就可以明白. 是一种线性的数据暂存与管理工具. 也可以让各种业务功能进行逐个的队列运行. 此篇博客只说明一下Java有几种队列 未阻塞和阻塞队列的区别 未阻塞: 1.未阻塞的队列在 ...

  10. drupal7权限控制之-如何访问未发表的node

    在某些特殊需求的情况下,会涉及到匿名用户或非node节点所有者,访问未发表的node节点的需求:或者需要根据不同的用户角色,访问不同的内容类型等,如果不想安装node_access等模块的时候,可以在 ...