JavaScript中的this

1.this的几种情况

1.1 全局环境中的this

在全局环境中(不在任何函数或对象内部),this指向全局对象(在浏览器环境中是window,在node.js中是global)。

console.log(this === window); // 在浏览器控制台中输出 true

1.2 默认绑定

当一个函数被直接调用(以函数形式调用,方法前面无小数点)时,this在非严格模式下指向全局对象(window),在严格模式下指向undefined。

function regularFunction() {
  console.log(this);
}
regularFunction(); // 非严格模式下指向 window,严格模式下为 undefined

// 演示严格模式下的情况
(function () {
  "use strict";
  regularFunction();
})();

1.3 隐式绑定

当函数作为对象的方法被调用时,this指向调用该方法的对象。

var person = {
  name: "John",
  sayName: function () {
    console.log(this.name);
  }
};

person.sayName(); // 输出 "John",这里的 this 指向 person 对象

1.4 new绑定

以构造函数形式调用,this执行新创建的对象实例。

function Person(name) {
  this.name = name;
  this.sayHello = function () {
    console.log("Hello, I'm " + this.name);
  };
}

var john = new Person("John");
john.sayHello(); // 输出 "Hello, I'm John",这里 this 指向 john 实例

构造函数怎么执行创建对象的过程详见我写的文章https://blog.youkuaiyun.com/fageaaa/article/details/141924289

1.5 箭头函数中的this

箭头函数没有自己的this,它的this继承自外层作用域的this,与调用方式无关。

1.6 事件处理函数中的this

在DOM事件处理函数中,this通常指向触发事件的元素。

<button id="myButton">Click me</button>

<script>
  var button = document.getElementById("myButton");
  button.onclick = function () {
    console.log(this); // 点击按钮时,这里的 this 指向按钮元素
    //打印 :<button id="myButton">Click me</button>
  };
</script>

1.7 显式绑定

使用apply、call、bind调用时候,this表示指定的那个对象。将在下面讲解。

1.8 代码示例

// 普通函数
function outerFunction() {
  this.text= "Outer";
  var innerFunction = function () {
    console.log(this.text);
  };
  innerFunction();
}

// 箭头函数
function outerFunctionWithArrow() {
  this.text = "Outer with Arrow";
  var innerFunction = () => {
    console.log(this.text);
  };
  innerFunction();
}

new outerFunction(); // 输出 undefined,因为 innerFunction 中的 this 指向全局对象,全局对象没有 text属性
new outerFunctionWithArrow(); // 输出 "Outer with Arrow",箭头函数的 this 继承自outerFunctionWithArrow 的 this

outerFunction();//输出outer。innerFunction 中的 this 指向全局对象,outerFunction给window设置了text

2.call、apply、bind的区别

由于箭头函数的this来自于继承,箭头函数无法使用以下三种方法改变this指向。

2.1 call方法

2.1.1 格式:

func.call(要改变的this指向,要给函数传递的参数1,要给函数传递的参数2, ...)

2.1.2 特点:

传多个参数,返回值为func函数的返回值,会立即调用函数。

2.1.3 示例:

var obj = { name: 'Jack' }
function fn(a, b) {
  console.log(this)
  console.log(a)
  console.log(b)
}
fn(1, 2)//fn(1,2) 的时候,函数内部的 this 指向 window(函数被直接调用)
fn.call(obj, 1, 2)//fn.call(obj, 1, 2) 的时候,函数内部的 this 就指向了 obj 这个对象

2.1.4 本质:

func基于Function.prototype.call,执行call方法。执行call方法时候把func的this改为obj,并且将接收的值作为实参传递给func函数,让function立即执行;

2.2 apply方法

2.2.1 格式:

func.apply(要改变的 this 指向,[要给函数传递的参数1, 要给函数传递的参数2, ...])

2.2.2 特点:

传数组,返回值为func函数的返回值,会立即调用函数。

2.2.3 示例:

var obj = { name: 'Jack' }
function fn(a, b) {
  console.log(this)
  console.log(a)
  console.log(b)
}
fn(1, 2)//fn(1,2) 的时候,函数内部的 this 指向 window(函数被直接调用)
fn.apply(obj, [1, 2])//fn.call(obj, 1, 2) 的时候,函数内部的 this 就指向了 obj 这个对象

2.2.4 本质:

func基于Function.prototype.apply,执行apply方法。执行apply方法时候把func的this改为obj,并且将a接收的数组中的值作为实参传递给func函数,让function立即执行;

2.3 bind方法

2.3.1 格式:

var newFn = func.bind(obj,...params)

2.3.2 特点:

传多个参数,返回值为bind函数的拷贝,不会立即调用函数。

2.3.3 示例:

var obj = { name: 'Jack' }
function fn(a, b) {
  console.log(this)
  console.log(a)
  console.log(b)
}
fn(1, 2)//fn(1, 2) 的时候 this 指向 window
var newFn = fn.bind(obj)
newFn(1, 2)////newFn(1, 2) 的时候执行的是一个和 fn 一摸一样的函数,只不过里面的 this 指向改成了obj

2.3.4 本质:

func基于Function.prototype.bind,执行bind方法。执行bind方法时候把obj,和其它参数用闭包存储起来,并且返回原函数的拷贝(不立即执行)。

3.手写call、apply、bind

3.1 手写call

原理:利用“点”定this机制,context.xxx=self

//之前有讲过Function.prototype意味着什么
Function.prototype.myCall=function (context,...params){
    // myCall方法的this指向的是调用它的func(因为这里有小数点)
    //context指传过来的obj
    //我们需要把params中的参数传给func
    let self=this;//self指向func
    //我们需要变成self(...params),并且让self里面的this执行obj
    //如果我们能变成context.xxx(...params),这样子就可以实现
    //所以要给obj(这里也就是context)加一个属性,而且这个属性不能和obj原本有的属性冲突
    
    //这样子key就是唯一值了,不会与key原本的值发生冲突
    let key=Symbol("KEY");
    let result;

    context == null ? context=window :null;
    //如果context是基本数据类型,会报错
    //我们需要把基本数据类型转为对象类型。
    //装箱操作
    !/^(object|function)$/i.test(typeof context) ? context=Object(context) : null;

    //需要改变this的指向,把this的指向从func改为context
    context[key]=self;
    result=context[key](...params);
    delete context[key];
    return result;
}
function func(a, b) {
  console.log(this);
  return a + b;
}
//不使用myCall方法
console.log(func(1, 2)); //控制台打印window,之后打印3

//使用myCall方法,传的目标不是基本数据类型
let obj = {
  name: "jack",
};
console.log(func.myCall(obj, 1, 2)); //控制台先打印obj,后打印3
//使用myCall方法,传的目标是基本数据类型,如10
console.log(func.myCall(10, 1, 2));//控制台先打印10的对象,后打印3

3.2 手写apply

思路和call很类似。

Function.prototype.myApply = function (context, params) {
  let self = this;

  let key = Symbol("KEY");
  context == null ? (context = window) : null;

  !/^(object|function)$/i.test(typeof context)
    ? (context = Object(context))
    : null;

  context[key] = self;
  let result;
  result = context[key](...params);
  delete context[key];
  return result;
};
function func(a, b) {
  console.log(this);
  return a + b;
}
console.log(func(1, 2)); //控制台打印window,之后打印3
console.log(func.myApply(10, [5, 7])); //控制台先打印10的对象,后打印12

let obj = {
  name: "jack",
};
console.log(func.myApply(obj, [5, 7])); //控制台先打印obj,后打印12

3.3 手写bind

//手写bind方法
Function.prototype.myBind=function (context,...params){
    if (typeof this !== 'function') return console.error('Error');
    context == null ? (context=window) :null;
    //装箱操作
    !/^(object|function)$/i.test(typeof context) ? context=Object(context) : null;
    // 这里this指向调用方法func
    let self=this;
    return function proxy(...args){
      //在proxy里面需要执行func并且改变this
      //proxy里面可能也会传参数(...args)
      return  self.apply(context,params.concat(args));
    }
}
function func1() {
  console.log(this);
  let res = 0;
  for (let i = 0; i < arguments.length; i++) {
    res = res + arguments[i];
  }
  return res;
}
setTimeout(func1.myBind(obj, 3, 4), 2000);
console.log(func1.myBind(obj, 3, 4)(5)); //12

4.思考

function func(){
    console.log(arguments)
    //argument是一个类数组,结构和操作很像数组,但数组的一些方法不能直接用,如Foreach等
    //类数组有下标,有length属性
}

如上,arguments是一个类数组。什么是类数组?

  • 拥有length属性,其它属性(索引)为非负整数
  • 不具有数组所具有的的方法;
  • 类数组是一个普通对象,而真实的数组是Array类型

常见的类数组:

  • 函数的参数arguments;
  • DOM对象列表(通过document.querySelectorAll)NodeList 集合;

如上,我们怎么把类数组arguments变成数组呢?

  • Array.from(arrayLike);
    Array.from()方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。 后续在es6中我将详细的讲述什么是类数组、什么是可遍历的对象以及数组的一些扩展方法(如 Array.from())
  • 展开运算符[…arrayLike];
  • 自己手写遍历

能想到的方法有很多。我们先看一下Array.prototype中的slice方法,当它不传参数时候,会拷贝整个数组,并且把拷贝的数组返回。那么下面代码就很好理解了

let arr1 = [2, 3, 4];
let arr2 = arr1.slice();
let arr3 = arr1;

console.log(arr2); //[2,3,4]
console.log(arr2 === arr1); //false
console.log(arr3 === arr1); //true

假如我们自己实现slice内部的方法:

Array.prototype.mySlice = function () {
  let result = [];
  for (let i = 0; i < this.length; i++) {
    result.push(this[i]);
  }
  return result;
};

实际上slice内部的实现思路和我们写的大同小异。再回到刚刚那个话题,如果我们自己用for循环遍历将arguments变为数组,代码如下:

function func() {
  console.log(arguments); 
  let result = [];
  for (let i = 0; i < arguments.length; i++) {
    result.push(arguments[i]);
  }
}

观察我们自己写的循环遍历代码,和之前类似slice内部实现的代码作对比,会发现不同之处就是循环遍历的对象一个是arguments,一个是this。

于是我们有了一个思路,改变数组的指向,让数组指向arguments对象,这样子就可以将arguments类数组对象转为数组了。

function func() {
  console.log(arguments); 
  let arr = [];
  let result2 = arr.slice.call(arguments);
  console.log(result2);
}

所以我们经常看到有很多地方经常使用[].slice.call(arguments)或者Array.prototype.slice.call(arguments)的方式把类数组转为数组。现在我们应该知道这个为什么能实现这个功能了。

由此可以得到启发,数组有很多方法,我们都可以利用这种改变this指向的方法来让类数组执行数组的一些方法来简化代码。不只是类数组,如果代码内部的结构类似,都可以使用这种方法。如下:

function func() {
  console.log(arguments);
  let arr=[];
  arr.forEach.call(arguments, (item) => {
    console.log(item);//依次次打印1,2,3
  });
}
func(1, 2, 3);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

太阳与星辰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值