11. 函数:内部特殊值和函数方法

  1. 罗列 this 值的常见情况。

另一个特殊的对象是 this。this 的值取决于多种因素。

(1) 全局上下文

this 在全局上下文中,即在任何函数体的外部时,都指向全局对象,在浏览器宿主环境中就是 window。

来看下面的例子:

  1. let o1 = {
  2. this: this,
  3. o2: {
  4. this: [this],
  5. },
  6. };
  7. console.log(o1.this === window); // true
  8. console.log(o1.o2.this[0] === window); // true

在这个例子中,对象 o1的 this 属性引用 this 值,而这个 this 值在全局上下文中指向 window。同样的,o2 的 this 值引用一个数组对象,这个数组的第一个元素为 this,但无论如何 this 值都在全局上下文中,所以都指向 window。

(2) 标准函数上下文

this 出现在标准函数上下文中,此时的 this 取决于函数调用的方式。

  • 在非严格模式下直接调用标准函数,则其内部的 this 指向全局对象。

来看下面几个例子:

  1. function getThis() {
  2. return this;
  3. }
  4. console.log(getThis() === window);
  5. // -> true

在这个例子中,getThis 是一个函数声明,它返回内部的 this 值。之后在全局上下文中直接调用了 getThis,它返回了全局对象。

非严格模式下直接调用标准函数,其内部的 this 值指向全局对象,而不论调用的上下文:

  1. function foo() {
  2. function getThis() {
  3. console.log(this === window);
  4. // -> true
  5. }
  6. getThis();
  7. }
  8. foo();

在这个例子中,getThis 在函数 foo 的上下文中直接调用,但不论是在全局上下文还是函数上下文中,非严格模式下直接调用标准函数,其内部的 this 都指向全局对象。

  • 严格模式下直接调用标准函数,则其内部的 this 为 undefined。

来看下面几个例子:

  1. function getThisInStrict() {
  2. 'use strict';
  3. return this;
  4. }
  5. console.log(getThisInStrict() === window);
  6. // -> false
  7. console.log(getThisInStrict() === undefined);
  8. // -> true

在这个例子中,函数 getThisInStrict 的作用域内使用了严格模式。之后,在全局上下文中调用了 getThisInStrict,它内部的 this 为 undefined 而不是全局对象。

同样的,严格模式下直接调用标准函数,其内部的 this 为 undefined,而不论调用的上下文。

  • 标准函数作为方法调用时,this 指向直接调用者,而非间接调用者。

来看下面几个例子:

  1. function getThis() {
  2. return this;
  3. }
  4. let o = {
  5. name: 'o',
  6. };
  7. o.getThis = getThis;
  8. console.log(o.getThis().name);
  9. // -> 'o'

在这个例子中,getThis 作为 o 的方法在全局上下文中调用。getThis 内部的 this 指向 o。

这个规则不受严格模式和调用方法时的上下文影响:

  1. 'use strict';
  2. function getThis() {
  3. return this;
  4. }
  5. function foo() {
  6. const o = {
  7. name: 'o',
  8. };
  9. o.getThis = getThis;
  10. console.log(o.getThis().name);
  11. // -> 'o'
  12. }
  13. foo();

在这个例子中,全局使用了严格模式。之后在函数 foo 上下文中,getThis 作为 o 的方法被调用。其内部的 this 指向 o。

复杂的情况是通过间接方式调用函数:

  1. function getThis() {
  2. return this;
  3. }
  4. console.log(getThis.prototype.constructor === getThis);
  5. // -> true
  6. console.log(getThis.prototype.constructor() === getThis.prototype);
  7. // -> true
  8. let constructor = getThis.prototype.constructor;
  9. console.log(constructor() === window);
  10. // -> true

在这个例子中,getThis.prototype.constructor 返回getThis函数对象本身。在执行 getThis.prototype.constructor()时,getThis 的直接调用者为getThis.prototype,因此 this 指向getThis.prototype。之后,将 getThis.prototype.constructor 赋给 constructor,并直接调用,因此 this 指向全局对象。

  • 标准函数作为构造函数调用,this 指向正在构建的对象:
  1. function Person() {
  2. this.name = 'Nicholas';
  3. console.log(this instanceof Person);
  4. // -> true
  5. }
  6. new Person();

在这个例子中,Person 作为构造方法调用,this 指向被构建的对象,因此 this instanceOf Person 为 true。

(3) 箭头函数

箭头函数的 this 行为和标准函数的截然不同。

  • 箭头函数在全局上下文中,则其 this 绑定全局对象,且严格模式和方法调用对该 this 没有影响。

来看下面几个例子:

  1. const getThisInArrowFunc = () => {
  2. 'use strict';
  3. return this;
  4. };
  5. console.log(getThisInArrowFunc() === window);
  6. // -> true

在这个例子中,getThisInArrowFunc位于全局上下文,其内部使用了严格模式,但这不影响 this 的值。this 依旧绑定全局对象。

  1. var name = 'window';
  2. const o1 = {
  3. name: 'o1',
  4. o2: {
  5. name: 'o2',
  6. getThis: () => this,
  7. },
  8. };
  9. console.log(o1.o2.getThis().name);
  10. // -> 'window'

在这个例子中,getThis作为 o2的方法被o1间接在全局上下文中调用,但不论是否作为方法被调用,getThis 都在全局上下文中,因此其内部的 this 还是绑定全局对象。

  • 箭头函数在函数上下文中,则其 this 绑定紧邻的外层函数的 this。
  1. var name = 'window';
  2. function foo() {
  3. const o1 = {
  4. name: 'o1',
  5. o2: {
  6. name: 'o2',
  7. getThis: () => this,
  8. },
  9. };
  10. console.log(o1.o2.getThis().name); // window
  11. }
  12. foo();

在这个例子中,箭头函数 getThisfoo函数的上下文中。这里 foo 在非严格模式下直接调用,所以函数 foo 的 this 指向全局对象,箭头函数绑定了该值。

当然,如过外层的函数foo以方法调用,则该箭头函数内部的 this 就绑定 foo 的直接调用者。

  1. new.target

ECMAScript中的函数始终可以作为构造函数实例化一个新对象,也可以作为普通函数被调用。ECMAScript 6 新增了检测函数是否使用 new 关键字调用的 new.target属性。如果函数是正常调用的,则new.target 的值是 undefined;如果是使用 new 关键字调用的,则 new.target将引用被调用的构造函数。

来看这个例子:

  1. function foo() {
  2. const funcName = arguments.callee.name;
  3. if (new.target) {
  4. throw `函数${funcName}不能用作构造函数!`;
  5. } else {
  6. console.log(`函数${funcName}用为非构造函数!`);
  7. }
  8. }
  9. new foo();
  10. foo();

在这个例子中,函数foo 的名字使用 arguments.callee.name 取得,如果foo用作构造函数,则new.target 指向构造函数本身,为非空对象,则抛出错误。如果foo 没有用作构造函数,则 new.target为 undefined,正常执行。

  1. arguments 对象

    aruments 对象是一个类数组对象。

注意:

  • arugments[i]对应的参数名如name1内存是分开的,只不过是保持同步
  • 如果只传如了一个参数,想要通过arugments[1]来设置第二个参数是不可行的
  • 严格模式下,修改aruguments无法影响到num2的值 , 在函数中重写aruguments会报错
  1. 模拟 Function.prototype.call方法
    • 不传入第一个参数,默认为window
    • 给新的对象添加一个函数,然后执行完再删除 ```javascript //call const { log } = console;

Function.prototype.myCall = function (thisArg, …args) { thisArg ? thisArg : whindow thisArg.fn = this let res = thisArg.fn(…args); delete thisArg.fn return res; };

function sum(num1,num2) { return num1 + num2; } console.log(sum.myCall(this,3,2)); // 5

  1. 5. 学习 applybind 方法
  2. - `bind() :` 会创建一个新的函数实例,其`this`值会绑定传给`bind()`的对象.
  3. ```javascript
  4. color = 'red';
  5. let o = {
  6. color : 'blue'
  7. }
  8. function sayColor() {
  9. console.log(this.color);
  10. }
  11. sayColor.bind(o)(); //blue
  • apply() : 接受两个参数: 函数内this的值和一个参数数组与call()不同的是call必须一个一个传
    1. 模拟 bind 方法
  1. const { log } = console;
  2. Function.prototype.myBind = function (thisArg, ...args) {
  3. if(typeof this !== 'function') {
  4. return
  5. }
  6. let _this = this
  7. return function() {
  8. return _this.apply(thisArg,...args);
  9. }
  10. };