持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第30天,点击查看活动详情
函数:内部特殊值和函数方法
- 罗列 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 的直接调用者。
- 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,正常执行。
- arguments 对象
arguments 是一个类数组对象。
- 实现 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
};
- 学习 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' 方法
作为构造函数使用的绑定函数
- 实现 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'
参考文章: