扒下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'



目录
相关文章
|
12天前
|
Web App开发 JavaScript 前端开发
如何确保 Math 对象的方法在不同的 JavaScript 环境中具有一致的精度?
【10月更文挑战第29天】通过遵循标准和最佳实践、采用固定精度计算、进行全面的测试与验证、避免隐式类型转换以及持续关注和更新等方法,可以在很大程度上确保Math对象的方法在不同的JavaScript环境中具有一致的精度,从而提高代码的可靠性和可移植性。
|
24天前
|
缓存 监控 前端开发
JavaScript 实现大文件上传的方法
【10月更文挑战第17天】通过以上步骤和方法,我们可以实现较为可靠和高效的大文件上传功能。当然,具体的实现方式还需要根据实际的应用场景和服务器要求进行调整和优化。
|
11天前
|
JavaScript 前端开发 索引
js中DOM的基础方法
【10月更文挑战第31天】这些DOM基础方法是操作网页文档结构和实现交互效果的重要工具,通过它们可以动态地改变页面的内容、样式和行为,为用户提供丰富的交互体验。
|
11天前
|
缓存 JavaScript UED
js中BOM中的方法
【10月更文挑战第31天】
|
12天前
|
JavaScript 前端开发 Java
[JS]同事:这次就算了,下班回去赶紧补补内置函数,再犯肯定被主管骂
本文介绍了JavaScript中常用的函数和方法,包括通用函数、Global对象函数以及数组相关函数。详细列出了每个函数的参数、返回值及使用说明,并提供了示例代码。文章强调了函数的学习应结合源码和实践,适合JavaScript初学者和进阶开发者参考。
25 2
[JS]同事:这次就算了,下班回去赶紧补补内置函数,再犯肯定被主管骂
|
11天前
|
JavaScript 前端开发
.js方法参数argument
【10月更文挑战第26天】`arguments` 对象为JavaScript函数提供了一种灵活处理参数的方式,能够满足各种不同的参数传递和处理需求,在实际开发中具有广泛的应用价值。
27 7
|
11天前
|
前端开发 JavaScript 开发者
除了 Generator 函数,还有哪些 JavaScript 异步编程解决方案?
【10月更文挑战第30天】开发者可以根据具体的项目情况选择合适的方式来处理异步操作,以实现高效、可读和易于维护的代码。
|
12天前
|
JavaScript 前端开发 图形学
JavaScript 中 Math 对象常用方法
【10月更文挑战第29天】JavaScript中的Math对象提供了丰富多样的数学方法,涵盖了基本数学运算、幂运算、开方、随机数生成、极值获取以及三角函数等多个方面,为各种数学相关的计算和处理提供了强大的支持,是JavaScript编程中不可或缺的一部分。
|
17天前
|
JavaScript 前端开发 Go
异步加载 JS 的方法
【10月更文挑战第24天】异步加载 JavaScript 是提高网页性能和用户体验的重要手段。通过使用不同的方法和技术,可以实现灵活、高效的异步加载 JavaScript。在实际应用中,需要根据具体情况选择合适的方法,并注意处理可能出现的问题,以确保网页能够正常加载和执行。
|
25天前
|
JavaScript 前端开发
JavaScript 函数语法
JavaScript 函数是使用 `function` 关键词定义的代码块,可在调用时执行特定任务。函数可以无参或带参,参数用于传递值并在函数内部使用。函数调用可在事件触发时进行,如用户点击按钮。JavaScript 对大小写敏感,函数名和关键词必须严格匹配。示例中展示了如何通过不同参数调用函数以生成不同的输出。