this----你不知道的JavaScript(上)
问题:重复使用一个函数,但引入不同的上下文对象,需要动态根据引入的对象编写不同版本的函数,引入this可自动引用合适的上下文对象。
function identify(){
return this.name.toUpperCase();
}
var me = {name:'kk'}
var you = {name:'ll'}
identify.call(me);//KK
identify.call(you);//LL
一、this的误解:
- this指向函数本身。
this并不指向函数本身。
function foo(num){
console.log('foo:'+num);
//记录foo调用次数
this.count++;
}
foo.count=0;
for(var i=0;i<5;i++){
if(i>1){
foo(i);
}
}
//打印foo:2,3,4三次
console.log(foo.count)//0
上述证明,this不指向函数本身。
解决方案一,创建一个带有count变量的对象data,用data.count引用。
function foo(num){
console.log('foo:'+num);
//记录foo调用次数
data.count++;
}
var data = {
count:0
}
foo.count=0;
for(var i=0;i<5;i++){
if(i>1){
foo(i);
}
}
console.log(data.count)//3
解决方案二:将this换为foo来引用,即为foo.count++;
解决方案三:在foo被调用时,强制this指向foo函数对象。
foo(i)----->foo.call(foo,i)
- this指向函数作用域。
this不指向函数的作用域。不能用this来引用一个作用域内部的东西。
二、this的理解:
this在运行时绑定,只取决于函数的调用方式。函数被调用时,创建一个执行上下文记录函数在哪里被调用、函数的调用方法、传参等信息。
调用位置:函数被调用的位置。最重要是分析调用栈(为了达到当前执行位置所调用的所有函数)
function baz(){
//调用栈baz
//调用位置全局作用域
console.log('baz')
bar();//<-bar调用位置
}
function bar(){
//调用栈baz->bar
//调用位置baz中
console.log('bar')
foo();//<-foo调用位置
}
function foo(){
//调用栈baz->bar->foo
//调用位置在bar中
console.log('foo')
}
baz();//<-baz调用位置
三、this的绑定:
- 独立函数调用:this指向全局作用域,默认绑定全局。
function foo(){
console.log(this.a)
}
var a = 2;
foo();
//打印2
非严格模式下指向全局作用域。严格模式下,this会绑定undefined。
因为foo是直接使用不带任何修饰的函数引用进行调用的,只能使用默认绑定。
- 隐式绑定:指向上下文对象(调用时所处的执行上下文)
- 对象属性引用链中只有最顶层或者说最后一层会影响调用位置。
隐式绑定需要在一个对象内部包含指向函数的属性,通过属性间接引用函数,从而间接绑定到对象上。
function foo(){
console.log(this.a)
}
var obj2 = {
a:42,
foo:foo
}
var obj1 = {
a: 2,
obj1: obj1
}
obj1.obj2.foo();//42
- 隐式丢失:被隐形绑定的函数会丢失绑定对象,会用默认绑定。
function foo(){
console.log(this.a)
}
var obj = {
a:2,
foo:foo
}
var bar = obj.foo;
var a = 'ohhhh!!'
bar()//'ohhhh!!'
bar是obj.foo的引用。实际上,引用的是foo本身,因此bar是一个不带任何修饰的函数调用,应用默认绑定。
当传入回调函数时,参数传递就是一种隐式赋值。和上述情况一样默认绑定。
function foo(){
console.log(this.a)
}
function doFoo(fn){
//fn引用的是foo
fn();//<-调用位置
}
var obj = {
a:2,
foo:foo
}
var a = 'ohhhh!!'
doFoo(obj.foo)//'ohhhh!!'
- 显式绑定:call(fn(),arguement),apply(fn(),arguement))
隐式绑定需要对象的属性指向函数。当不想在函数内部包含函数引用而又想在对象中调用函数,那么需要call,apply。调用函数的同时指定this。
this只能显示绑定一次,再绑定也不会改。
- new构造函数
fucntion foo(a){
this.a = a;
}
var bar = new foo(2);
console.log(bar.a)//2
构造函数的this指向对象。
- 优先级
默认绑定<隐式绑定<显式绑定<new
new相当于创建了一个新的对象,与之前的绑定无关,新建了一个对象绑定了函数的this。
先看有没有new,再看显式绑定,如果都没有就默认绑定。
- 例外
当apply绑定的是null时,用于将数组展开成参数。
function foo(a,b){
console.log('a:'+a+',b:'+b)
}
foo.apply(null,[2,3])
当bind绑定的是null时,可进行柯里化。
- 箭头函数this
this指向箭头函数所在的上下文,箭头函数的绑定无法被修改。
检测所学。做一下下面这道题:
function Foo() {
getName = function () { alert (1); };
return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}
//请写出以下输出结果:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
正确结果:
Foo.getName();//2
getName();//4
Foo().getName();//1
getName();//1
new Foo.getName();//2
new Foo().getName();//3
new new Foo().getName();//3
看代码,首先,声明了一个foo函数,然后给foo添加了一个getname属性。
- Foo.getName()访问属性,为2.
- getName()在代码中有两个位置涉及到,一个是函数表达式var getName= function(){};一个是function getName(){}声明。之前在词法作用域部分,提到了函数声明会提升,然后运行函数表达式,表达式的4覆盖了声明的5,此处显示4.
- foo()调用完,return的是this作用域,然后调用其中的getName属性,但注意到,此处并没有var声明getName,所以是getName赋值。那么getName是哪的,也就是第二个涉及到的全局的getName,将其赋值为1.显示为1.
- 通过第三个,已经修改了getName赋值为1,所以显示为1.
- new Foo.getName()先进行属性调用,foo.getName取到的是2这个函数,然后new当做构造函数调用,显示2
- new Foo().getName()先执行foo()得到了this,然后new。new一个构造函数只有当返回一个对象的时候,结果为对象,否则new完之后得到的都是实例化对象。此时new完得到的就是实例化对象,访问其getName属性。但构造函数中没有定义任何属性,因此访问原型链得到3.
- new Foo()得到的实例化对象,实例化对象的getName属性,得到3函数,再实例化该构造函数。显示3.
题目2:
/**
* Question 2
*/
var name = 'window'
function Person (name) {
this.name = name;
this.show1 = function () {
console.log(this.name)
}
this.show2 = () => console.log(this.name)
this.show3 = function () {
return function () {
console.log(this.name)
}
}
this.show4 = function () {
return () => console.log(this.name)
}
}
var personA = new Person('personA')
var personB = new Person('personB')
personA.show1()//personA
personA.show1.call(personB)//personB
personA.show2()//personA
personA.show2.call(personB)//personA
personA.show3()()//window
personA.show3().call(personB)//personB
personA.show3.call(personB)()//window
personA.show4()()//personA
personA.show4().call(personB)//personA
personA.show4.call(personB)()//personB
- show1是一般函数的this,调用和绑定时候均正常显示,personA和personB
- show2为箭头函数的this,直接调用的时候,this指向箭头函数所在的上下文的this,所在上下文的this.name = name即为personA;call绑定的时候,由于箭头函数this指向无法更改,还是指向所在上下文,所以还是personA。
- personA.show3()调用之后,return了一个包含this的普通函数,普通函数调用,默认this绑定window。
包含this的普通函数进行call绑定,this指向personB。
personA.show3得到的是包含return的function函数,call绑定该函数。再调用运行,最后返回一个console.log。默认绑定window。
- personA.show4()得到箭头函数,箭头函数this指向show4的function的函数内部的上下文,也就是person的作用域,因此调用箭头函数后显示personA
箭头函数无法绑定this,因此指向的还是function中的上下文,和上一个一样,是personA。
personA.show4得到function函数,function函数绑定this为personB,call执行完毕后得到立即函数,调用后为personB。