JavaScript中的this

打印 上一主题 下一主题

主题 865|帖子 865|积分 2595

1.this的几种情况

1.1全局情况中的 this

在全局情况中(不在任何函数或对象内部),this指向全局对象(在欣赏器情况中是window,在node.js中是global)。
  1. console.log(this === window); // 在浏览器控制台中输出 true
复制代码
1.2 默认绑定

当一个函数被直接调用(以函数情势调用,方法前面无小数点)时,this在非严酷模式下指向全局对象(window),在严酷模式下指向undefined。
  1. function regularFunction() {
  2.   console.log(this);
  3. }
  4. regularFunction(); // 非严格模式下指向 window,严格模式下为 undefined
  5. // 演示严格模式下的情况
  6. (function () {
  7.   "use strict";
  8.   regularFunction();
  9. })();
复制代码
1.3 隐式绑定

当函数作为对象的方法被调用时,this指向调用该方法的对象。
  1. var person = {
  2.   name: "John",
  3.   sayName: function () {
  4.     console.log(this.name);
  5.   }
  6. };
  7. person.sayName(); // 输出 "John",这里的 this 指向 person 对象
复制代码
1.4 new绑定

以构造函数情势调用,this实行新创建的对象实例。
  1. function Person(name) {
  2.   this.name = name;
  3.   this.sayHello = function () {
  4.     console.log("Hello, I'm " + this.name);
  5.   };
  6. }
  7. var john = new Person("John");
  8. john.sayHello(); // 输出 "Hello, I'm John",这里 this 指向 john 实例
复制代码
构造函数怎么实行创建对象的过程详见我写的文章https://blog.csdn.net/fageaaa/article/details/141924289。
1.5 箭头函数中的this

箭头函数没有自己的this,它的this继续自外层作用域的this,与调用方式无关。
1.6 事故处置惩罚函数中的this

在DOM事故处置惩罚函数中,this通常指向触发事故的元素。
  1. <button id="myButton">Click me</button>
  2. <script>
  3.   var button = document.getElementById("myButton");
  4.   button.onclick = function () {
  5.     console.log(this); // 点击按钮时,这里的 this 指向按钮元素
  6.     //打印 :<button id="myButton">Click me</button>
  7.   };
  8. </script>
复制代码
1.7 显式绑定

使用apply、call、bind调用时候,this表示指定的谁人对象。将在下面解说。
1.8 代码示例

  1. // 普通函数
  2. function outerFunction() {
  3.   this.name = "Outer";
  4.   var innerFunction = function () {
  5.     console.log(this.name);
  6.   };
  7.   innerFunction();
  8. }
  9. // 箭头函数
  10. function outerFunctionWithArrow() {
  11.   this.name = "Outer with Arrow";
  12.   var innerFunction = () => {
  13.     console.log(this.name);
  14.   };
  15.   innerFunction();
  16. }
  17. new outerFunction(); // 输出 undefined,因为 innerFunction 中的 this 指向全局对象,全局对象没有 name 属性
  18. new outerFunctionWithArrow(); // 输出 "Outer with Arrow",箭头函数的 this 继承自outerFunctionWithArrow 的 this
复制代码
2.call、apply、bind的区别

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

(1)格式:

func.call(要改变的this指向,要给函数通报的参数1,要给函数通报的参数2, ...)
(2)特点:

传多个参数,返回值为func函数的返回值,会立刻调用函数。
(3)示例:

  1. var obj = { name: 'Jack' }
  2. function fn(a, b) {
  3.   console.log(this)
  4.   console.log(a)
  5.   console.log(b)
  6. }
  7. fn(1, 2)//fn(1,2) 的时候,函数内部的 this 指向 window(函数被直接调用)
  8. fn.call(obj, 1, 2)//fn.call(obj, 1, 2) 的时候,函数内部的 this 就指向了 obj 这个对象
复制代码
(4)本质:

func基于Function.prototype.call,实行call方法。实行call方法时候把func的this改为obj,并且将吸收的值作为实参通报给func函数,让function立刻实行;
2.2apply方法

(1)格式:

func.apply(要改变的 this 指向,[要给函数通报的参数1, 要给函数通报的参数2, ...])
(2)特点:

传数组,返回值为func函数的返回值,会立刻调用函数。
(3)示例:

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

func基于Function.prototype.apply,实行apply方法。实行apply方法时候把func的this改为obj,并且将a吸收的数组中的值作为实参通报给func函数,让function立刻实行;
2.3bind方法

(1)格式:

var newFn = func.bind(obj,...params)
(2)特点:

传多个参数,返回值为bind函数的拷贝,不会立刻调用函数。
(3)示例:

  1. var obj = { name: 'Jack' }
  2. function fn(a, b) {
  3.   console.log(this)
  4.   console.log(a)
  5.   console.log(b)
  6. }
  7. fn(1, 2)
  8. var newFn = fn.bind(obj)//fn(1, 2) 的时候 this 指向 window
  9. newFn(1, 2)newFn(1, 2) 的时候执行的是一个和 fn 一摸一样的函数,只不过里面的 this 指向改成了obj
复制代码
(4)本质:

func基于Function.prototype.bind,实行bind方法。实行bind方法时候把obj,和别的参数用闭包存储起来,并且返回原函数的拷贝(不立刻实行)。
3.手写call、apply、bind

3.1手写call

原理:使用“点”定this机制,context.xxx=self
  1. //之前有讲过Function.prototype意味着什么
  2. Function.prototype.myCall=function (context,...params){
  3.     // myCall方法的this指向的是调用它的func(因为这里有小数点)
  4.     //context指传过来的obj
  5.     //我们需要把params中的参数传给func
  6.     let self=this;//self指向func
  7.     //我们需要变成self(...params),并且让self里面的this执行obj
  8.     //如果我们能变成context.xxx(...params),这样子就可以实现
  9.     //所以要给obj(这里也就是context)加一个属性,而且这个属性不能和obj原本有的属性冲突
  10.    
  11.     //这样子key就是唯一值了,不会与key原本的值发生冲突
  12.     let key=Symbol("KEY");
  13.     let result;
  14.     context == null ? context=window :null;
  15.     //如果context是基本数据类型,会报错
  16.     //我们需要把基本数据类型转为对象类型。
  17.     //装箱操作
  18.     !/^(object|function)$/i.test(typeof context) ? context=Object(context) : null;
  19.     //需要改变this的指向,把this的指向从func改为context
  20.     context[key]=self;
  21.     result=context[key](...params);
  22.     delete context[key];
  23.     return result;
  24. }
  25. function func(a, b) {
  26.   console.log(this);
  27.   return a + b;
  28. }
  29. //不使用myCall方法
  30. console.log(func(1, 2)); //控制台打印window,之后打印3
  31. //使用myCall方法,传的目标不是基本数据类型
  32. let obj = {
  33.   name: "jack",
  34. };
  35. console.log(func.myCall(obj, 1, 2)); //控制台先打印obj,后打印3
  36. //使用myCall方法,传的目标是基本数据类型,如10
  37. console.log(func.myCall(10, 1, 2));//控制台先打印10的对象,后打印3
复制代码
3.2手写apply

思路和call很雷同。
  1. Function.prototype.myApply = function (context, params) {
  2.   let self = this;
  3.   let key = Symbol("KEY");
  4.   context == null ? (context = window) : null;
  5.   !/^(object|function)$/i.test(typeof context)
  6.     ? (context = Object(context))
  7.     : null;
  8.   context[key] = self;
  9.   let result;
  10.   result = context[key](...params);
  11.   delete context[key];
  12.   return result;
  13. };
  14. function func(a, b) {
  15.   console.log(this);
  16.   return a + b;
  17. }
  18. console.log(func(1, 2)); //控制台打印window,之后打印3
  19. console.log(func.myApply(10, [5, 7])); //控制台先打印10的对象,后打印12
  20. let obj = {
  21.   name: "jack",
  22. };
  23. console.log(func.myApply(obj, [5, 7])); //控制台先打印obj,后打印12
复制代码
3.3手写bind

  1. //手写bind方法
  2. Function.prototype.myBind=function (context,...params){
  3.     if (typeof this !== 'function') return console.error('Error');
  4.     context == null ? (context=window) :null;
  5.     //装箱操作
  6.     !/^(object|function)$/i.test(typeof context) ? context=Object(context) : null;
  7.     // 这里this指向调用方法func
  8.     let self=this;
  9.     return function proxy(...args){
  10.       //在proxy里面需要执行func并且改变this
  11.       //proxy里面可能也会传参数(...args)
  12.       return  self.apply(context,params.concat(args));
  13.     }
  14. }
  15. function func1() {
  16.   console.log(this);
  17.   let res = 0;
  18.   for (let i = 0; i < arguments.length; i++) {
  19.     res = res + arguments[i];
  20.   }
  21.   return res;
  22. }
  23. setTimeout(func1.myBind(obj, 3, 4), 2000);
  24. console.log(func1.myBind(obj, 3, 4)(5)); //12
复制代码
4.思考

  1. function func(){
  2.     console.log(arguments)
  3.     //argument是一个类数组,结构和操作很像数组,但数组的一些方法不能直接用,如Foreach等
  4.     //类数组有下标,有length属性
  5. }
复制代码
如上,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方法,当它不传参数时候,会拷贝整个数组,并且把拷贝的数组返回。那么下面代码就很好理解了
  1. let arr1 = [2, 3, 4];
  2. let arr2 = arr1.slice();
  3. let arr3 = arr1;
  4. console.log(arr2); //[2,3,4]
  5. console.log(arr2 === arr1); //false
  6. console.log(arr3 === arr1); //true
复制代码
假如我们自己实现slice内部的方法:
  1. Array.prototype.mySlice = function () {
  2.   let result = [];
  3.   for (let i = 0; i < this.length; i++) {
  4.     result.push(this[i]);
  5.   }
  6.   return result;
  7. };
复制代码
实际上slice内部的实现思路和我们写的大同小异。再回到刚刚谁人话题,如果我们自己用for循环遍历将arguments变为数组,代码如下:
  1. function func() {
  2.   console.log(arguments);
  3.   let result = [];
  4.   for (let i = 0; i < arguments.length; i++) {
  5.     result.push(arguments[i]);
  6.   }
  7. }
复制代码
观察我们自己写的循环遍历代码,和之前雷同slice内部实现的代码尴尬刁难比,会发现差别之处就是循环遍历的对象一个是arguments,一个是this。
于是我们有了一个思路,改变数组的指向,让数组指向arguments对象,如许子就可以将arguments类数组对象转为数组了。
  1. function func() {
  2.   console.log(arguments);
  3.   let arr = [];
  4.   let result2 = arr.slice.call(arguments);
  5.   console.log(result2);
  6. }
复制代码
以是我们常常看到有很多地方常常使用[].slice.call(arguments)或者Array.prototype.slice.call(arguments)的方式把类数组转为数组。现在我们应该知道这个为什么能实现这个功能了。
由此可以得到启发,数组有很多方法,我们都可以使用这种改变this指向的方法来让类数组实行数组的一些方法来简化代码。不只是类数组,如果代码内部的结构雷同,都可以使用这种方法。如下:
  1. function func() {
  2.   console.log(arguments);
  3.   arr.forEach.call(arguments, (item) => {
  4.     console.log(item);//依次次打印1,2,3
  5.   });
  6. }
  7. func(1, 2, 3);
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

天空闲话

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表