JS:this关键字

this总是一个对象,是属性或方法“当前”所在的对象。this的设计目的是在函数体内部,指代函数当前的运行环境。

1、this的使用场合

1.1 全局环境

正常模式中,全局环境使用this,它指的就是顶层对象window(函数内/外部均指向window)。

注:本文在浏览器中测试,默认顶层对象是window

// 正常模式
this === window // true

function f () {
  return this;
}

f() === window // true

严格模式中,全局环境使用this,函数外部指向window;函数内部,this 是 undefined(严格模式中,禁止 this 关键字指向全局对象)。

// 严格模式
'use strict'
this === window // true

function f () {
  return this;
}

f() === undefined // true

严格模式相关,本文不展开说明。

1.2 构造函数

构造函数中的this,指的是实例对象。

function Person (name) {
  this.name = name
}

let p = new Person('张三');
console.log(p.name) // "张三"

1.3 对象的方法

如果对象的方法里面包含this,this指向方法运行时所在的对象。该方法赋值给另一个对象,会改变this的指向。

1)基本用法,this指向调用它的对象

let obj ={
  name: '张三',
  print: function () {
    console.log(this.name);
  }
};

obj.print() // "张三"    --> this 指向 obj

2)函数单独定义,并赋值给对象的方法(this指向可变,指向调用它的对象)

function print () {
  console.log(this.fullName);
}
let A = {
  fullName: '张三',
  print
};
let B = {
  fullName: '李四',
  print
};

A.print(); // "张三"      --> this 指向 A
B.print(); // "李四"      --> this 指向 B
print(); // undefined     --> this 指向 window

3)对象的方法赋给另一个对象(this指向可变,指向调用它的对象)

let A = {
  fullName: '张三',
  print: function () {
    console.log(this.fullName);
  }
};
let B = {
  fullName: '李四'
};
B.print = A.print;

A.print(); // "张三"     --> this 指向 A
B.print(); // "李四"     --> this 指向 B

let f = A.print;
f(); // undefined        --> this 指向 window

4)其他改变this指向的用法(this指向window的特殊用法)

let obj ={
  foo: function () {
    return this;
  }
};
(obj.foo = obj.foo)()  // Window
(false || obj.foo)()   // Window
(true && obj.foo)()    // Window
(1, obj.foo)()         // Window

5)this 在多层对象内部的一个方法里(this 指向当前一层的对象,且不会继承更上面的层)--重点关注

let a = {
  p: 'Hello',
  b: {
    d: 'world',
    m: function() {
      console.log(this.p);
      console.log(this.d);
    }
  }
};
a.b.m();         // --> this 指向 a.b
// undefined     // a.b.p
// "world"       // a.b.d

上面的例子 实际执行的是下面的代码:

let b = {
  d: 'world',
  m: function() {
    console.log(this.p);
    console.log(this.d);
  }
}
let a = {
  p: 'Hello',
  b
};
(a.b).m(); // 等同于 b.m()

6)方法中包含多层this(第一层this 指向当前一层的对象,第二层嵌套方法中的this指向window)

let obj ={
  foo: function () {
    console.log(this);        // --> this 指向 obj
    let bar = function () {
      console.log(this);      // --> this 指向 window
    };
    bar();
  }
};
obj.foo();
// obj
// Window

上述实际执行的是下面的代码

let bar = function () {
  console.log(this);
}
let obj ={
  foo: function () {
    console.log(this);
    bar();
  }
};
obj.foo();
// obj
// Window

让内层this指向外层对象的方法:

  1. 用一个变量固定this的值
    let obj ={
      foo: function () {
        console.log(this);        // --> this 指向 obj
        const that = this;
        let bar = function () {
          console.log(that);      // --> this 指向 obj
        };
        bar();
      }
    };
    obj.foo();
    // obj
    // obj
  2. Function.prototype.bind()
    let obj ={
      foo: function () {
        console.log(this);        // --> this 指向 obj
        let bar = function () {
          console.log(this);      // --> this 指向 obj
        }.bind(this);
        bar();
      }
    };
    obj.foo();
    // obj
    // obj
  3. 箭头函数。箭头函数没有自己的this对象,内部的this就是定义时上层作用域中的this。箭头函数内部的this指向是固定的。--本节(对象的方法)中所有的例子都可以改成箭头函数的形式,看this的指向,请自行尝试
    let obj ={
      foo: function () {
        console.log(this);        // --> this 指向 obj
        let bar = () => {
          console.log(this);      // --> this 指向 obj
        };
        bar();
      }
    };
    obj.foo();
    // obj
    // obj

 7)数组处理方法中的 this(this指向window

let obj ={
  v: 'hello',
  p: [ 'a1', 'a2' ],
  foo: function () {
    this.p.forEach(function (item) {
      console.log(this.v + ' ' + item);      // --> this 指向 window
    })
  }
};
obj.foo();
// undefined a1
// undefined a2

让内层this指向外层对象的方法:

  1. 用一个变量固定this的值
  2. Function.prototype.bind()
  3. 箭头函数
  4. forEach接受第二个参数,绑定参数函数的this变量。
     
    let obj ={
      v: 'hello',
      p: [ 'a1', 'a2' ],
      foo: function () {
        this.p.forEach(function (item) {
          console.log(this.v + ' ' + item);      // --> this 指向 window
        }, this);
      }
    };
    obj.foo();
    // hello a1
    // hello a2

1.4 DOM 事件操作

1)HTML 的 on- 属性

<div id="btn" onclick="console.log('HTML的onclick属性', this);">点击</div>
// --> this 指向 当前DOM元素

<div id="btn" onclick="(function (){console.log(this);})()">点击</div>
// --> this 指向 window
<div id="btn" onclick="clickFun()">点击</div>

<script>
  function clickFun() {
    console.log('HTML的onclick属性', this); // --> this 指向 window
  }
</script>

2)元素节点的事件属性

<div id="btn">点击</div>
<script>
    let oDiv = document.getElementById('btn');
    oDiv.onclick = function () {
        console.log('元素节点的事件属性', this); // --> this 指向 当前DOM元素
    }
</script>

3)EventTarget.addEventListener()

<div id="btn">点击</div>
<script>
  let oDiv = document.getElementById('btn');
  oDiv.addEventListener('click', function () {
    // --> this 指向 当前DOM元素
    console.log('DOM节点的addEventListener方法', this);
  })
</script>

1.5 使用 window.setTimeout()

var id = 21;
function f1() {
    setTimeout(function () {
        console.log('id:', this.id);
    }, 100)
}

function f2() {
    setTimeout(() => {
        console.log('id:', this.id);
    }, 100)
}

// 普通函数,执行时this指向全局对象window
f1();                // id: 21
f1.call({id: 42});   // id: 21

// 箭头函数,this总是指向函数定义生效时所在的对象
f2();                // id: 21
f2.call({id: 42});   // id: 42

2、绑定 this 的方法

JS提供了call、apply、bind三个方法,下文详述。

2.1 Function.prototype.call()

1)定义

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

语法:function.call(thisArg, arg1, arg2, ...)

其中:

thisArg:可选。在 function 函数运行时使用的 this 值。

thisArg应该是一个对象,如果是空、null和undefined,则默认传入全局对象window;

原始值会被包装。

var n = 123;
var obj = { n:456 };
function f () {
    console.log(this.n);
}
f.call();              // 123
f.call(1);             // undefined
f.call(1n);            // undefined
f.call('1');           // undefined
f.call(null);          // 123
f.call(undefined);     // 123
f.call(Symbol());      // undefined
f.call([]);            // undefined
f.call(obj);           // 456

 2)手写call,参考答案(仅支持浏览器环境)

Function.prototype.myCall = function (context, ...args) {
    if (context === null || context === undefined) {
        context = window
    } else {
        context = Object(context)
    }
    const fn = Symbol('fn')
    context[fn] = this;
    const result = context[fn](...args);
    delete context[fn];
    return result;
}

// 验证
var n = 123;
var obj = { n: 456 };
function f() {
    console.log(this.n);
}
f.myCall();        // 123
f.myCall(null);    // 123
f.myCall(obj);     // 456
f.myCall(1);       // undefined

2、Function.prototype.apply()

apply方法的作用与call方法类似。唯一的区别是,它接收一个数组(或类数组对象)作为函数执行时的参数。

语法:function.apply(thisArg, argsArray)

apply的一些应用

1)将数组各项添加到另一个数组

const arr = ['a', 'b'];
const elements = [0, 1, 2];
arr.push.apply(arr, elements);
console.log(arr); // [ 'a', 'b', 0, 1, 2 ]

2)Math.max/Math.min 求得数组中的最大/小值

const numbers = [5, 6, 2, 3, 7];

// 基本等同于 Math.max(numbers[0], ...) 或 Math.max(5, 6, ..)
Math.max.apply(null, numbers); // 7

Math.min.apply(null, numbers); // 2

3)将数组的空元素变为undefined。数组的map方法会跳过空元素,但是不会跳过undefined。

const arr = ['a',,'b']
function f (item) {
    console.log(item);
};

arr.map(f);
// a
// b

Array.apply(null, arr).map(f);
// a
// undefined
// b

4)转换类似数组的对象

[].slice.apply({0: 'a', length: 1})              // ['a']
Array.prototype.slice.apply({0: 'a', length: 1}) // ['a']

[].slice.apply('abc') // ['a', 'b', 'c']

5)绑定回调函数的对象

var nm = 'wNm';
const obj = {
  nm: 'oNm'
}
function f(cb) {
  cb()
  cb.apply(this);
  cb.apply(obj);
}
function print() {
  console.log(this.nm);
}
f(print)
// 'wNm'
// 'wNm'
// 'oNm'

3、Function.prototype.bind()

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

语法:function.bind(thisArg[, arg1[, arg2[, ...]]])

用法:

1)bind()创建绑定函数

var count = 100;
var counter = {
    count: 0,
    inc: function () {
        this.count++;
    }
};

let fun1 = counter.inc;
fun1();       // --> this 指向 window
count         // 101
counter.count // 0

var func = counter.inc.bind(counter);
func();       // --> this 指向 counter
count         // 101
counter.count // 1

2)bind() 使一个函数拥有预设的初始参数。

function addArguments(arg1, arg2) {
    return arg1 + arg2
}
let addOne = addArguments.bind(null, 1/*arg1 = 1*/);
console.log(addOne(2/*arg2的值*/)); // 3

The end.

参考连接:

this 关键字 - JavaScript 教程 - 网道

Function.prototype.call() - JavaScript | MDN

猜你喜欢

转载自blog.csdn.net/weixin_43932309/article/details/128952152