本文源于本人关于《JavaScript设计模式与开发实践》(曾探著)的阅读总结。想详细了解具体内容建议阅读该书。
1. this
this总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的。
this的指向
除去with和eval,实际应用中,this指向分为以下四类:
- 作为对象的方法调用
- 作为普通函数调用
- 构造器调用
- Function.prototype.call & Function.prototype.apply
作为对象的方法调用
var obj = {
a: 1,
getA: function(){
console.log(this === obj); // true
console.log(this.a); // 1
}
}
obj.getA();
这个时候,由于是obj调用的getA()函数,故this指向obj。
作为普通函数调用
var obj = {
name: aaaa,
getName: function(){
console.log(this === obj); // false
console.log(this.name); // yozo
}
}
window.name = yozo;
var getGlobalName = function(){
console.log(this.name);
}
var getName = obj.getName;
getGlobalName(); // yozo;
getName();
只要函数没有调用者,则this就指向全局对象,在浏览器中的Js里,全局对象是window。严格模式中,this不指向全局对象。
这里主要看getName
,它本身只是一个函数,由于被抽离出来了,故执行getName()
时,它是没有调用者的,和getGlobalName
一样,作为普通函数调用。
构造器调用
var Myclass = function(){
this.name = 'yozo';
};
var obj = new Myclass();
console.log(obj.name); // yozo
这个时候的this代表被新创建出来的那个对象,这里指obj
。
但是:
var Myclass = function(){
this.name = 'yozo';
return {
name: 'ann'
}
};
var obj = new Myclass();
console.log(obj.name); // ann
当显示返回一个对象时,那么这个时候显示返回的这个对象会被obj引用,故这个时候 obj.name就是ann而不是yozo。
Function.prototype.call & Function.prototype.apply
var obj1 = {
name: 'yozo',
getName: function(){
console.log(this.name);
}
}
var obj2 = {
name: 'ann'
}
console.log(obj1.getName()); // yozo
console.log(obj1.getName.call(obj2)); // ann
- 第一个console中,
getName
的调用者是obj1,则this指向obj1; - 第二个console中,
getName
的调用者本是obj1,但是这个时候出现了.call(obj2)
。就好像是打电话告诉obj2:“喂,obj2,你去执行obj1的getName
方法吧”,故这个时候调用者就变成了obj2,我们知道,this总是指向调用者,故这个时候this就指代obj2。
2. call和apply
call和apply的区别
刚刚我们已经说了call的作用了,就是改变函数的调用者。那么apply的作用呢?也是改变函数的调用者。区别在于,call和apply都是Function.prototype上的方法,故只有函数才能调用这两个函数, 既然是函数的call和apply,那么函数在执行的时候总会有参数吧,那么改变了调用者之后,函数的参数怎么处理?
- call: 把参数一个一个列举出来(不固定参数,第一个为新的调用者,其他的为传入该函数的其他参数)
- apply: 把参数一锅传(只有两个参数,第一个为新的调用者,另一个参数为参数数组)
var func = function(a, b, c){
console.log([a, b, c]);
}
func.apply(null,[1, 2, 3]);
func.call(null, 1, 2, 3);
这个函数没this,所以不需要调用者,我们只用来区别call和apply的用法:
- call:从第二个参数开始,一一对应func的各个参数
- apply:参数数组与func的各个参数一一对应。
call和apply用途
- 改变this指向(改变调用者)
document.getElementById('div1').onclick = function(){
console.log(this.id); // div1
var func = function(){
console.log(this.id);
}
func(); // undefined (无调用者)
func.call(this); // div1
}
onclick回调函数中this表示document.getElementById('div1'), 但是执行func()的时候没有调用者,默认调用者为window,但是我们希望它仍能保持指向document.getElementById('div1'),则把this传给了func,让他调用该函数。
- Function.prototype.bind:将一个函数的this固定为某个对象,之后使用时就不用再使用call和apply了。
简易版:
Function.prototype.bind = function (context) {
var self = this;
return function () {
self.apply(context, arguments);
}
}
var obj1 = {
name: 'yozo',
getName: function () {
console.log(this.name);
}
}
var obj2 = {
name: 'ann'
}
var obj2getName = obj1.getName.bind(obj2);
obj2getName(); // ann
这时的this就已经被固定为obj2了。
这个版本在固定对象时不能保存参数,故升级版:
Function.prototype.bind = function () {
var self = this; // 保留该函数的调用者
var context = [].shift.call(arguments); // 获取参数数组中第一个参数,即需要被固定的那个对象
var args = [].slice.call(arguments); // 保存剩下的参数
// 返回一个新的函数
return function () {
// 改变调用者为需要被固定的那个对象,将原本剩下的参数,和新传入的参数组成一个参数数组并执行
self.apply(context, [].concat.call(args, [].slice.call(arguments)));
}
}
var obj = {
name: 'yozo'
}
var func = function (a, b, c, d) {
console.log(this.name);
console.log([a, b, c, d]);
}.bind(obj, 1, 2);
func(3, 4); // 1, 2, 3, 4
- 借用其他对象的方法:之前的
obj1.getName.call(obj2)
就是obj2借用obj1的方法。我们常用的是借用Object或者Array的prototype的方法:
(function(){
Array.prototype.push.call(arguments, 3, 4);
console.log(arguments);
})(1, 2)
// 1, 2, 3, 4
这其实是arguments参数数组对象利用了真数组对象,参数数组为伪数组(DOM节点数组也是),但是伪数组可以借用真数组的方法。
伪数组需要满足两个条件:
- 对象本身要可以存取属性
- 对象length属性可以读写