【javascript】 内功修炼--this、闭包与作⽤域

1.什么是this?

        this 是 JavaScript 中的一个关键字。函数的this指向谁,其实可以分为两种情况讨论:

  1. javascript普通函数的this特点:
    • 普通函数的this指向函数调用者,
    • 找不到调用者,默认指向window。
    • 普通函数的this指向可以通过call\apply\bind去改变
      2.箭头函数的this特点:

               1.箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定。

               2.箭头函数的this只和定义时的作用域this有关,和调用者无关,也永远不会改变。

2.this在不同应用场景中的取值

1.普通函数(非箭头函数)被调用时的this指向的是window

2.普通函数(非箭头函数)作为对象方法被调用时,this的指向的是对象本身

3.普通函数(非箭头函数)作为class方法被调用时,this的指向是class实例本身

4.call\apply\bind可以改变普通函数(非箭头函数)的this指向,因此普通函数(非箭头函数)被call\apply\bind转化之后,this指向变成指定的对象

5普通函数fn作为对象obj的方法,并return一个普通函数

6.普通函数fn作为对象obj的方法,并return一个箭头函数

7.对象obj的对象字面量fn赋值一个箭头函数,并return一个箭头函数

8.对象obj的对象字面量fn赋值一个普通函数,并return一个普通函数,再return一个箭头函数

9.普通函数fn,return一个箭头函数

10.普通函数fn,return一个箭头函数,并且fn通过call修改this指向为obj

11.普通函数fn,return一个普通函数

12.普通函数fn,return一个普通函数,并且fn通过call修改this指向为obj

13.计时器setTimeout\setInterval的回调函数,包括各种方法的回调函数,实际上都比较特殊,关于这类回调函数的this指向,首先我们要关注两个点,第一是回调函数的类型是属于普通函数还是箭头函数;第二是回调函数的调用者是谁,是window还是对象本身。

3.call\apply\bind区别

        call,apply,bind都是可以改变普通函数的this指向(不能改变箭头函数的this指向)的方法,这三个函数实际上都是绑定在Function构造函数的prototype上,而每一个函数都是Function的实例,因此每一个函数都可以直接调用call\apply\bind。

名称this指向参数返回值示例
call改变this要指向的对象多个参数使用调用者提供的 this 值和参数调用该函数的返回值
const res = fn.call(obj, '参数1', '参数2');
console.log(result);
apply改变this要指向的对象数组使用调用者提供的 this 值和参数调用该函数的返回值
const res = fn.apply(obj, ['参数1', '参数2']);
console.log(result);
bind改变this要指向的对象多个参数不会立即执行,而是作为bind方法的返回值return出来
const res = fn.bind(obj, '参数1', '参数2');
resultFun();
断数据类型:Object.prototype.toString.call()

4.手写apply

// 实现apply

// apply挂载在Function构造函数的原型链上,因此myApply也要挂载在Function的prototype上
 // 第一个参数是指定的this指向
 // 第二个参数是一个数组,数组中是要传给改造后的函数作为参数
 // 先获取调用myApply的函数

Function.prototype.myApply = function (context, args = []) { 
  const fn = this;
  
  // myApply的调用者数据类型必须是函数,否则抛出错误
  if(typeof fn !== 'function') {
    throw TypeError('Not a Function');
  }

  /*
    容错机制:要保证指定的this指向(即context)必须是一个复杂类型的数据结构(如object\function),不能是简单类型的数据(如boolean\number\string\symbol)。
    如果context是简单类型的数据,需要转成对应类型的对象;
    如果context是复杂类型的数据,则保持不变;
    如果是undefined、null,则默认指向window;
    备注:其实判断数据类型最推荐的办法是通过Object.prototype.toString.call()来判断,但是我们需要手动实现call\apply\bind,那么这里就不通过这种方式来判断数据类型,而是通过其他的方法来判断数据类型
  */
  if (typeof context === 'undefined') {
    context = window;
  } else if (typeof context === 'boolean') {
    context = new Boolean(context);
  } else if (typeof context === 'number') {
    context = new Number(context);
  } else if (typeof context === 'string') {
    context = new String(context);
  } else if (typeof context === 'symbol') {
    context = new Symbol(context);
  } else if (typeof context === 'function') {
    context = context;
  } else if (typeof context === 'object') {
    context = context ? context : window;
  }

    // 创建一个随机属性名,Symbol()始终返回唯一值
  let randomAttributeName = Symbol(); 

    // 将fn临时作为context这个对象的属性存储起来,去调用fn的时候this也就指向context
  context[randomAttributeName] = fn; 

    // 用context这个对象调用函数fn
  let result = context[randomAttributeName](...args); 
    // 删除临时绑定在context上的属性,让context回归原本的样子
  delete context[randomAttributeName]; 

    // myApply返回的是改造后的函数调用后所返回的值(原函数返回啥,myApply就返回啥)
  return result; 
}


// 测试
function fn(params1, params2) {
  console.log(this, params1, params2)
}

const obj = {
  name: '老王卖豆腐',
  age: 18,
}

fn.myApply(obj, ['老王', '豆腐']); // 打印obj、参数1、参数2

5.手写call

// 实现apply

// apply挂载在Function构造函数的原型链上,因此myApply也要挂载在Function的prototype上
 // 第一个参数是指定的this指向
 // 第二个参数是一个数组,数组中是要传给改造后的函数作为参数
 // 先获取调用myApply的函数

Function.prototype.myApply = function (context, args = []) { 
  const fn = this;
  
  // myApply的调用者数据类型必须是函数,否则抛出错误
  if(typeof fn !== 'function') {
    throw TypeError('Not a Function');
  }

    // 创建一个数组,用于接收myCall的实参
  const args = Array.from(arguemtns)

  /*
    容错机制:要保证指定的this指向(即context)必须是一个复杂类型的数据结构(如object\function),不能是简单类型的数据(如boolean\number\string\symbol)。
    如果context是简单类型的数据,需要转成对应类型的对象;
    如果context是复杂类型的数据,则保持不变;
    如果是undefined、null,则默认指向window;
    备注:其实判断数据类型最推荐的办法是通过Object.prototype.toString.call()来判断,但是我们需要手动实现call\apply\bind,那么这里就不通过这种方式来判断数据类型,而是通过其他的方法来判断数据类型
  */
  if (typeof context === 'undefined') {
    context = window;
  } else if (typeof context === 'boolean') {
    context = new Boolean(context);
  } else if (typeof context === 'number') {
    context = new Number(context);
  } else if (typeof context === 'string') {
    context = new String(context);
  } else if (typeof context === 'symbol') {
    context = new Symbol(context);
  } else if (typeof context === 'function') {
    context = context;
  } else if (typeof context === 'object') {
    context = context ? context : window;
  }

    // 创建一个随机属性名,Symbol()始终返回唯一值
  let randomAttributeName = Symbol(); 

    // 将fn临时作为context这个对象的属性存储起来,去调用fn的时候this也就指向context
  context[randomAttributeName] = fn; 

    // 用context这个对象调用函数fn
  let result = context[randomAttributeName](...args); 
    // 删除临时绑定在context上的属性,让context回归原本的样子
  delete context[randomAttributeName]; 

    // myApply返回的是改造后的函数调用后所返回的值(原函数返回啥,myApply就返回啥)
  return result; 
}


// 测试
function fn(params1, params2) {
  console.log(this, params1, params2)
}

const obj = {
  name: '老王卖豆腐',
  age: 18,
}

fn.myApply(obj, ['老王', '豆腐']); // 打印obj、参数1、参数2

6.手写bind

// 实现bind

// apply挂载在Function构造函数的原型链上,因此myApply也要挂载在Function的prototype上
 // 第一个参数是指定的this指向
 // 第二个参数是一个数组,数组中是要传给改造后的函数作为参数
 // 先获取调用myApply的函数

Function.prototype.myBind = function (context, args = []) { 
  const fn = this;
  
  // myApply的调用者数据类型必须是函数,否则抛出错误
  if(typeof fn !== 'function') {
    throw TypeError('Not a Function');
  }

// 创建一个数组,用于接收myCall的实参
  const args = Array.from(arguemtns)

  /*
    容错机制:要保证指定的this指向(即context)必须是一个复杂类型的数据结构(如object\function),不能是简单类型的数据(如boolean\number\string\symbol)。
    如果context是简单类型的数据,需要转成对应类型的对象;
    如果context是复杂类型的数据,则保持不变;
    如果是undefined、null,则默认指向window;
    备注:其实判断数据类型最推荐的办法是通过Object.prototype.toString.call()来判断,但是我们需要手动实现call\apply\bind,那么这里就不通过这种方式来判断数据类型,而是通过其他的方法来判断数据类型
  */
  if (typeof context === 'undefined') {
    context = window;
  } else if (typeof context === 'boolean') {
    context = new Boolean(context);
  } else if (typeof context === 'number') {
    context = new Number(context);
  } else if (typeof context === 'string') {
    context = new String(context);
  } else if (typeof context === 'symbol') {
    context = new Symbol(context);
  } else if (typeof context === 'function') {
    context = context;
  } else if (typeof context === 'object') {
    context = context ? context : window;
  }

  return function F(...rest) {
        
    // 处理myBind return出去的函数被作为构造函数使用的情况,即通过new运算符来调用return出去的函数实例( 如 new func.bind() ) (很少有这样的用法,一般都是return出去之后直接调用的)
    if (this instanceof F) { 
      return new fn(...args, ...rest);
    }
        // 创建一个随机属性名,Symbol()始终返回唯一值
      let randomAttributeName = Symbol(); 
    
        // 将fn临时作为context这个对象的属性存储起来,去调用fn的时候this也就指向context
      context[randomAttributeName] = fn; 

         // args.concat(...rest)是为了支持函数柯里化
      let result = args.concat(...rest);

        // 用context这个对象调用函数fn
      let result = context[randomAttributeName](...paramsArr); 

        // 删除临时绑定在context上的属性,让context回归原本的样子
      delete context[randomAttributeName]; 

        // myApply返回的是改造后的函数调用后所返回的值(原函数返回啥,myApply就返回啥)
      return result; 
    }
}


// 测试
function fn(params1, params2) {
  console.log(this, params1, params2)
}

const obj = {
  name: '老王卖豆腐',
  age: 18,
}

fn.myBind(obj, ['老王', '豆腐'])(); // 打印obj、参数1、参数2

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值