扒下JS的“底裤”之函数:内部特殊值和函数方法

简介: 扒下JS的“底裤”之函数:内部特殊值和函数方法

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

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

this 的值取决于多种因素。


(1) 全局上下文

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

来看下面的例子:

let o1 = {
  this: this,
  o2: {
    this: [this],
  },
};
console.log(o1.this === window); // true
console.log(o1.o2.this[0] === window); // true
复制代码


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


(2) 标准函数上下文

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

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

来看下面几个例子:

function getThis() {
  return this;
}
console.log(getThis() === window);
// -> true
复制代码


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


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

function foo() {
  function getThis() {
    console.log(this === window);
    // -> true
  }
  getThis();
}
foo();
复制代码


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


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

来看下面几个例子:

function getThisInStrict() {
  'use strict';
  return this;
}
console.log(getThisInStrict() === window);
// -> false
console.log(getThisInStrict() === undefined);
// -> true
复制代码


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


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


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

来看下面几个例子:

function getThis() {
  return this;
}
let o = {
  name: 'o',
};
o.getThis = getThis;
console.log(o.getThis().name);
// -> 'o'
复制代码


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


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

'use strict';
function getThis() {
  return this;
}
function foo() {
  const o = {
    name: 'o',
  };
  o.getThis = getThis;
  console.log(o.getThis().name);
  // -> 'o'
}
foo();
复制代码


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


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

function getThis() {
  return this;
}
console.log(getThis.prototype.constructor === getThis);
// -> true
console.log(getThis.prototype.constructor() === getThis.prototype);
// -> true
let constructor = getThis.prototype.constructor;
console.log(constructor() === window);
// -> true
复制代码


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


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


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


(3) 箭头函数

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

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

来看下面几个例子:

const getThisInArrowFunc = () => {
  'use strict';
  return this;
};
console.log(getThisInArrowFunc() === window);
// -> true
复制代码


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

var name = 'window';
const o1 = {
  name: 'o1',
  o2: {
    name: 'o2',
    getThis: () => this,
  },
};
console.log(o1.o2.getThis().name);
// -> 'window'
复制代码


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

  • 箭头函数在函数上下文中,则其 this 绑定紧邻的外层函数的 this。
var name = 'window';
function foo() {
  const o1 = {
    name: 'o1',
    o2: {
      name: 'o2',
      getThis: () => this,
    },
  };
  console.log(o1.o2.getThis().name); // window
}
foo();
复制代码


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


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


  1. new.target

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

来看这个例子:

function foo() {
  const funcName = arguments.callee.name;
  if (new.target) {
    throw `函数${funcName}不能用作构造函数!`;
  } else {
    console.log(`函数${funcName}用为非构造函数!`);
  }
}
new foo();
foo();
复制代码


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


  1. arguments 对象

arguments 是一个类数组对象。


  1. 实现 Function.prototype.call
const { log } = console;
Function.prototype.myCall = function (thisArg, ...args) {
    // 检查调用方法是不是function
    if(typeof this !== 'function') {
        throw new TypeError('Error')
    }
    // 默认window
    thisArg = thisArg || window
    // 添加方法
    thisArg.fn = this
    const result = thisArg.fn(...args)
    // 删除方法
    delete thisArg.fn;
    // 返回结果
    return result
};
复制代码


  1. 学习 apply, bind 方法

apply() 方法调用一个具有给定 this 值的函数,以及以一个数组(或类数组对象)的形式提供的参数。

const numbers = [5, 6, 2, 3, 7];
const max = Math.max.apply(null, numbers);
console.log(max);
// expected output: 7
复制代码


  • 用 apply 将数组各项添加到另一个数组

和concat不一样。concat是返回的新数组。


  • 使用 apply 和内置函数

对于一些需要写循环以遍历数组各项的需求,我们可以用 apply 完成以避免循环。

注意:如果按上面方式调用apply,有超出 JavaScript 引擎参数长度上限的风险。


解决方法是切块数组,重复比较

function minOfArray(arr) {
  var min = Infinity;
  var QUANTUM = 32768;
  for (var i = 0, len = arr.length; i < len; i += QUANTUM) {
    var submin = Math.min.apply(null, arr.slice(i, Math.min(i + QUANTUM, len)));
    min = Math.min(submin, min);
  }
  return min;
}
var min = minOfArray([5, 6, 2, 3, 7]);
复制代码


  • 使用 apply 来链接构造器

你可以使用 apply 来链接一个对象构造器 (en-US),类似于 Java。在接下来的例子中我们会创建一个全局Function 对象的 construct 方法 ,来使你能够在构造器中使用一个类数组对象而非参数列表。

function MyConstructor () {
    for (var nProp = 0; nProp < arguments.length; nProp++) {
    console.log(arguments,this)
    this["property" + nProp] = arguments[nProp];
    }
   }
   var myArray = [4, "Hello world!", false];
   var myInstance = new MyConstructor(myArray); 
   console.log(myInstance.property1);  // logs "undefined"
   console.log(myInstance instanceof MyConstructor); // logs "true"
   console.log(myInstance.constructor);  // logs "MyConstructor"
复制代码


function MyConstructor () {
    for (var nProp = 0; nProp < arguments.length; nProp++) {
    console.log(arguments,this)
    this["property" + nProp] = arguments[nProp];
    }
   }
   var myArray = [4, "Hello world!", false];
   var myInstance = new MyConstructor(...myArray);
   console.log(myInstance.property1);  // logs "Hello world!"
   console.log(myInstance instanceof MyConstructor); // logs "true"
   console.log(myInstance.constructor);  // logs "MyConstructor"
复制代码


Function.prototype.construct = function(aArgs) {
    var fConstructor = this,
    fNewConstr = function() {
    fConstructor.apply(this, aArgs);
    };
    fNewConstr.prototype = fConstructor.prototype;
    return new fNewConstr();
   };
   function MyConstructor () {
    for (var nProp = 0; nProp < arguments.length; nProp++) {
    console.log(arguments,this)
    this["property" + nProp] = arguments[nProp];
    }
   }
   var myArray = [4, "Hello world!", false];
   var myInstance = MyConstructor.construct(myArray);
   console.log(myInstance.property1);  // logs "Hello world!"
   console.log(myInstance instanceof MyConstructor); // logs "true"
   console.log(myInstance.constructor);  // logs "MyConstructor"
复制代码


  • Function.prototype.bind()

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

const module = {
  x: 42,
  getX: function() {
    return this.x;
  }
};
window.x = 111
const unboundGetX = module.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope
// expected output: 111
const boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
// expected output: 42
复制代码


偏函数

bind() 的另一个最简单的用法是使一个函数拥有预设的初始参数。只要将这些参数(如果有的话)作为 bind() 的参数写在 this 后面。当绑定函数被调用时,这些参数会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们后面。


配合 setTimeout

在默认情况下,使用 window.setTimeout() 时,this 关键字会指向 window(或 global)对象。当类的方法中需要 this 指向类的实例时,你可能需要显式地把 this 绑定到回调函数,就不会丢失该实例的引用。

function LateBloomer() {
  this.petalCount = Math.ceil(Math.random() * 12) + 1;
}
// 在 1 秒钟后声明 bloom
LateBloomer.prototype.bloom = function() {
  window.setTimeout(this.declare.bind(this), 1000);
};
LateBloomer.prototype.declare = function() {
  console.log('I am a beautiful flower with ' +
    this.petalCount + ' petals!');
};
var flower = new LateBloomer();
flower.bloom();  // 一秒钟后,调用 'declare' 方法
复制代码


作为构造函数使用的绑定函数

  1. 实现 bind 方法
'use strict';
const { log } = console;
Function.prototype.myBind = function myBind(thisArg, ...presetArgs) {
    // self指向绑定函数
    var self = this;
    // 绑定函数先传的参数
    var argsOne = Array.prototype.slice.call(presetArgs)
    var fbound = function() {
        // 返回的函数之后传入的参数
        var argsTwo = Array.prototype.slice.call(arguments)
        // this fbound作为构造函数时 指向实例,直接调用指向window 那么就改为指向thisArg 
        self.apply(this instanceof self ?  this : thisArg , argsOne.concat(argsTwo))
    }
        // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承函数的原型中的值
        fbound.prototype = this.prototype;
        return fbound;
};
function logInfo(a, b) {
  log(a, b);
  log(this);
}
const bindedLogInfo = logInfo.myBind('thisArg', 1, 2);
bindedLogInfo(3, 4);
// -> 1 2
// -> 'thisArg'



目录
相关文章
|
17天前
|
JavaScript 前端开发
JavaScript基础知识-函数的返回值
关于JavaScript函数返回值的基础知识。
20 9
JavaScript基础知识-函数的返回值
|
2天前
|
JSON JavaScript 前端开发
JavaScript第五天(函数,this,严格模式,高阶函数,闭包,递归,正则,ES6)高级
JavaScript第五天(函数,this,严格模式,高阶函数,闭包,递归,正则,ES6)高级
|
17天前
|
JavaScript 前端开发
JavaScript基础知识-函数的参数
关于JavaScript函数参数基础知识的介绍。
16 4
JavaScript基础知识-函数的参数
|
17天前
|
JavaScript 前端开发
JavaScript基础知识-立即执行函数
关于JavaScript基础知识中立即执行函数的介绍。
16 2
JavaScript基础知识-立即执行函数
|
17天前
|
JavaScript 前端开发
JavaScript基础知识-方法
文章通过示例代码讲解了JavaScript中如何给对象添加方法以及如何调用这些方法。
18 2
JavaScript基础知识-方法
|
16天前
|
JavaScript 前端开发
JavaScript基础知识-数组的常用方法
关于JavaScript基础知识-数组的常用方法。
15 1
JavaScript基础知识-数组的常用方法
|
1天前
|
JavaScript 前端开发
JavaScript Array map() 方法
JavaScript Array map() 方法
|
1天前
|
存储 JavaScript
js切割截取字符串方法
js切割截取字符串方法
|
1天前
|
JavaScript
JS数组合并的常用方法
JS数组合并的常用方法
|
14天前
|
JavaScript 前端开发
JavaScript 函数
JavaScript 函数
21 9