call方法

基础版, 只能修改指向,不能传参

  1. Function.prototype.myCall = function(context) {
  2. // 获取调用者,这里为bar
  3. context.fn = this;
  4. // 运行函数
  5. context.fn();
  6. // 删除缓存
  7. delete context.fn;
  8. }
  9. let foo = {
  10. value: 1
  11. }
  12. function bar() {
  13. console.log(this.value);
  14. }
  15. bar.myCall(foo);
  16. // 1

eval版本

  1. Function.prototype.myCall = function(context) {
  2. // 获取调用者,这里为sayHi
  3. // 将无调用者的情况转换为有调用者的情况
  4. // 有调用者那么函数内部的this就指向调用者
  5. context.fn = this;
  6. var len = arguments.length;
  7. // 保存传递的参数
  8. var args = Array(len - 1);
  9. for (var i = 1; i < len; i++) {
  10. // 这里只是把获取参数的字符串拼接了,供eval调用
  11. args[i - 1] = 'arguments[' + i + ']';
  12. }
  13. // 不直接调用而用eval是因为参数个数不一定
  14. // 在这里相当于是在执行context.fn(arguments[1],arguments[2]);
  15. eval('context.fn('+ args +')');
  16. delete context.fn;
  17. }
  18. let person = {
  19. name: 'Wango'
  20. }
  21. function sayHi(age, sex) {
  22. console.log(this.name, age, sex)
  23. }
  24. sayHi.myCall(person, 24, 'male');
  25. // Wango 24 male

ES6 版本

  1. Function.prototype.myCall = function(context) {
  2. context.fn = this;
  3. // 将arguments转换为数组并调用slice方法去除第一个参数
  4. // 再使用...将数组打散
  5. context.fn(...Array.from(arguments).slice(1));
  6. delete context.fn;
  7. }
  8. let person = {
  9. name: 'Wango'
  10. }
  11. function sayHi(age, sex) {
  12. console.log(this.name, age, sex);
  13. }
  14. sayHi.myCall(person, 24, 'male');
  15. // Wango 24 male

ES6 升级版

  1. Function.prototype.myCall = function(context, ...args) {
  2. context.fn = this;
  3. // 相比使用arguments,这里只是用了ES6的剩余参数
  4. context.fn(...args);
  5. delete context.fn;
  6. }
  7. let person = {
  8. name: 'Wango'
  9. }
  10. function sayHi(age, sex) {
  11. console.log(this.name, age, sex);
  12. }
  13. sayHi.myCall(person, 24, 'male');
  14. // Wango 24 male

最终版

之前的版本如果传入的对象原本的fn属性或方法会被覆盖,然后被删除;而且传入第一个参数如果不是对象,会报错,所以还需要一些容错处理

  1. // 保存一个全局变量作为默认值
  2. const root = this;
  3. Function.prototype.myCall = function(context, ...args) {
  4. if (typeof context === 'object') {
  5. // 如果参数是null,使用全局变量
  6. context = context || root;
  7. } else {
  8. // 参数不是对象的创建一个空对象
  9. context = Object.create(null);
  10. }
  11. // 使用Symbol创建唯一值作为函数名
  12. let fn = Symbol();
  13. context[fn] = this;
  14. context[fn](...args);
  15. delete context[fn];
  16. }
  17. let person = {
  18. name: 'Wango',
  19. fn: function() {
  20. console.log(this.name);
  21. }
  22. }
  23. function sayHi(age, sex) {
  24. console.log(this.name, age, sex);
  25. }
  26. sayHi.myCall(person, 24, 'male');
  27. // Wango 24 male
  28. sayHi.myCall(null, 24, 'male');
  29. // undefined 24 male
  30. sayHi.myCall(123, 24, 'male');
  31. // undefined 24 male
  32. // 原函数不受影响
  33. person.fn();
  34. // Wango

call的实现最核心的部分就是将没有调用者的情况转换为有调用者的情况,函数内部的this自然就指向调用者

apply方法

apply的实现思路和call方法是一样的,只是只接收两个参数,第二个参数为类数组

  1. const root = this;
  2. Function.prototype.myApply = function(context, arg) {
  3. if (typeof context === 'object') {
  4. context = context || root;
  5. } else {
  6. context = Object.create(null);
  7. }
  8. const fn = Symbol();
  9. context[fn] = this;
  10. context[fn](...arg);
  11. delete context[fn];
  12. }
  13. let person = {
  14. name: 'Wango',
  15. fn: function() {
  16. console.log(this.name);
  17. }
  18. }
  19. function sayHi(age, sex) {
  20. console.log(this.name, age, sex);
  21. }
  22. sayHi.myApply(person, [24, 'male']);
  23. // Wango 24 male

bind方法

  1. // bind 改变this指向但是不立即执行函数,而是返回一个绑定了this的函数
  2. // bind方法在绑定this指向的同时也可以传递参数
  3. Function.prototype.myBind = function(context, ...innerArgs) {
  4. // 不需要对参数类型进行判断,后边调用call方法时会进行处理
  5. const fn = this;
  6. // 不执行函数,而是返回一个函数等待调用
  7. return function(...finalArgs) {
  8. // 通过已经实现的call方法实现对this指向的改变,并传入参数执行
  9. return fn.call(context, ...innerArgs, ...finalArgs);
  10. }
  11. }
  12. const person = {
  13. name: 'Wango'
  14. }
  15. function sayHi(age, sex) {
  16. console.log(this.name, age, sex);
  17. }
  18. const personSayHi_1 = sayHi.myBind(person, 24, 'male');
  19. personSayHi_1();
  20. // Wango 24 male
  21. const personSayHi_2 = sayHi.myBind(person);
  22. personSayHi_2(24, 'male');
  23. // Wango 24 male

bind方法的核心在于在返回的函数中调用call方法

参考网址:call、apply和bind的实现

call、apply和bind的实现的更多相关文章

  1. JS核心系列:浅谈 call apply 与 bind

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

  2. Javascript中call,apply,bind方法的详解与总结

    在 javascript之 this 关键字详解 文章中,谈及了如下内容,做一个简单的回顾: 1.this对象的涵义就是指向当前对象中的属性和方法. 2.this指向的可变性.当在全局作用域时,thi ...

  3. JS中call、apply、bind使用指南,带部分原理。

    为什么需要这些?主要是因为this,来看看this干的好事. box.onclick = function(){ function fn(){ alert(this); } fn();}; 我们原本以 ...

  4. Javascript中call、apply、bind函数

    javascript在函数创建的时候除了自己定义的参数外还会自动新增this和arguments两个参数 javascript中函数也是对象,call.apply.bind函数就是函数中的三个函数,这 ...

  5. 图解call、apply、bind的异同及各种实战应用演示

    一.图解call.apply.bind的异同 JavaScript中函数可以通过3种方法改变自己的this指向,它们是call.apply.bind.它们3个非常相似,但是也有区别.下面表格可以很直观 ...

  6. js里function的apply vs. bind vs. call

    js里除了直接调用obj.func()之外,还提供了另外3种调用方式:apply.bind.call,都在function的原型里.这3种方法的异同在stackoverflow的这个答案里说的最清楚, ...

  7. JS之apply,call,bind区别

    为了加深对基础知识的理解,今天再复习下js中的apply,call,bind的区别和用法.整理笔记的过程也是一个再次学习的过程. apply和call js中的调用apply和call方法可以改变某个 ...

  8. JavaScript中call、apply、bind、slice的使用

    1.参考资料 http://www.cnblogs.com/coco1s/p/4833199.html   2.归结如下 apply . call .bind 三者都是用来改变函数的this对象的指向 ...

  9. JS中的控制函数调用:call(),apply()和bind()

    所有的函数都具有call(),apply()和bind()方法.它们可以在执行方法的时候用一个值指向this,并改变面向对象的作用域. apply方法: 以下的两种表达式是等价的: func(arg1 ...

  10. JS中的call、apply、bind方法

    JS中的call.apply.bind方法 一.call()和apply()方法 1.方法定义 call方法: 语法:call([thisObj[,arg1[, arg2[,   [,.argN]]] ...

随机推荐

  1. Spring boot AOP 实现Redis 存储

    package com.carloan.common.web.annotation; import java.lang.annotation.*; /** * 自定义redis缓存注解.只要在serv ...

  2. 将一个 JavaBean 对象转化为一个 Map

    package com.loan.modules.common.util; import java.beans.BeanInfo; import java.beans.IntrospectionExc ...

  3. java 将内容写入文件 txt

    @Test //将内容写入文件 public void xieru() throws Exception{ FileWriter fileWriter=new FileWriter("d:\ ...

  4. H3C交换机端口聚合配置

    1.接入交换机: interface Ten-GigabitEthernet1/0/21 port link-mode bridge port link-type trunk port trunk p ...

  5. 故障-因为MAC地址冲突造成的故障

    1.问题分析与解决 1.1 症状与起因 问题症状: 访问卡慢,负载并不高 起因: 笔者有一部分物理机做了虚拟化,由于体量小就直接通过命令行工具创建,在创建时并没有通过kvm的clone命令,而是手工修 ...

  6. 从零搭建一个IdentityServer——聊聊Asp.net core中的身份验证与授权

    OpenIDConnect是一个身份验证服务,而Oauth2.0是一个授权框架,在前面几篇文章里通过IdentityServer4实现了基于Oauth2.0的客户端证书(Client_Credenti ...

  7. C# 如何复制(拷贝)Label控件上的文本【新方法】

    Label控件在目前是无法直接调用成员函数来复制其文本内容.其实网络上有很多热心程序员网民解答过这个问题,百度上也可以搜索到,不过大多数人建议使用 TextBox 并把边框调整为不可见(运行时文本框看 ...

  8. Java常用类库2

    1.java.util.Date类 package LESSON9; import java.util.Date; public class demo1 { public static void ma ...

  9. G - 棋盘游戏

    小希和Gardon在玩一个游戏:对一个N*M的棋盘,在格子里放尽量多的一些国际象棋里面的"车",并且使得他们不能互相攻击,这当然很简单,但是Gardon限制了只有某些格子才可以放, ...

  10. 最短Hamilton路径(状压dp)

    最短Hamilton路径实际上就是状压dp,而且这是一道作为一个初学状压dp的我应该必做的题目 题目描述 给定一张 n(n≤20) 个点的带权无向图,点从 0~n-1 标号,求起点 0 到终点 n-1 ...