this 的重要性不言而喻,比如面试题经常考到,其次,如果彻底理解了 this, 那么对理解框架源码及编写高质量代码都有很大的帮助。本文就是要深入剖析 this 的几种情况,理解了原理,以后妈妈再也不用担心你的 this 了。。
this 是动态绑定的,其实相对应的是作用域,因为作用域是在代码刚刚写完的时候,就已经定义好了。理解了作用域,对理解闭包很有帮助。本文主要讲解 this 绑定,大家心里先有和作用域的一个大致对比就行,以后的文章会专门讲解。
所谓动态绑定,就是只有在函数被调用的时候,this 才能确定它真正指向的是哪个对象。
this 分为以下四种情况,这四种掌握了,就打遍天下无敌手了~
我们先定义一个函数:
- function foo(name){
- this.name=name;
- console.log(this.name);
- }
- varobj=newfoo("yx");
控制台输出结果:"yx"
继续在控制台输入: obj.name ,结果依然是 "yx"
对于上面的 foo 函数,
如果改成
- function foo(name) {
- var o = {
- name: "peggy"
- };this.name = name;console.log(this.name);
- return o;
- }
- var obj = new foo();
控制台输出结果依然是 "yx"
继续在控制台输入 obj.name , 发现结果竟然变成了 "peggy"!
其实我觉得原因大家已经猜出来了:
如果 foo 函数返回了一个对象,那么 obj 等于返回的那个对象 o,如果没返回对象的话,则默认是 return this ;
可能有些小伙伴会认为 foo 应该大写,而且 foo 是构造函数,其实压根没有什么构造函数,函数名大写也只是一种约定而已,其实真实的情况是使用 new 关键字构造调用了 foo 函数!那么此时的 this 在 foo 函数被调用的过程中,绑定到了一个新创建的对象上,如果没有指定对象返回的话,那么返回的就是这个新创建的对象。这个新创建的对象的内部 [[proto]] 属性会指向 foo.prototype ,这就涉及到原型与继承问题了,以后再讨论。
所以结果为什么这样就很清晰了:两次调用 console.log(this.name) 会返回 "yx" 是因为 this 就是新创建的这个对象。而最后 obj.name 不一样,是因为函数返回的对象不一样了,所以此时 obj 代表的对象也就不一样了。
所谓的显示绑定有三种: call , apply , bind
我们定义函数:
- function person(age, hobby){
- this.age=age;
- this.hobby=hobby;
- console.log(this.name);
- console.log(this.age);
- console.log(this.hobby);
- }
因为函数也是对象,所以也是有方法的,每个函数其实可以理解为是由 Function 函数通过 new 构造调用的,即 new Function()
所以每一个函数内部 [[proto]] 属性都指向 Function.prototype ,所以可以继承 Function.prototype 中的方法,比如; call , apply , bind
call 方法和 apply 方法的共同点是:第一个参数代表 this 指向的对象
不同点是: call 方法从第二个参数开始,传递的都是 person 函数需要的对应的参数, 挨个挨个的传递
apply 方法的第二个参数,传递的是一个数组,数组中的每一项与 person 函数需要的参数对应
- var obj = {
- name: "yx"
- };person.call(obj, 24, "singing");
此时 this 指向 obj , 所以控制台输出结果是 "yx",24,"singing'
- var obj1 = {
- name: "peggy"
- };person.apply(obj1, [24, "running"]);
此时, this 指向 obj1 , 控制台输出结果是 "yx",24,"running"
使用 apply 还可以用来展开数组,第二个参数传递 arguments 数组( arguments 数组代表函数实际传递的参数)
比如:继承会用到
- var obj2 = {
- name: "xixi"
- };
- function student(age, hobby) {person.apply(this, arguments);
- }
student.call(obj2,24,"singing");
此时 student 函数中的 this 指向传入的 obj , arguments 是一个类数组,代表传递的实参,
所以如果想实现继承 person 函数的属性或者方法,可以用 apply 展开 arguments
- var obj3 = {
- name: "nancy"
- };
- var obj4 = {
- name: "mingming"
- };
- var newFn = person.bind(obj3);
调用 bind 函数会返回一个新函数,新函数的 this 会绑定在 obj3 上,无论以后是怎么调用 newFn , this 都会一直绑定在 obj3
比如在控制台输入: newFn.call(obj4,24,"singing") 或者输入 newFn(24,"singing") 都不能改变 this 的绑定
发现结果依然是输出:"nancy",而不是 "mingming"
bind 还可以用来进行科里化,就是可以先传参数进去,比如:
- varnewFn1 = person.bind(obj3,24);
- newFn1("singing");
- newFn1("running");
相当于先传了一个参数,之后调用的时候再传另外的参数就好。
不是所有的函数都有 protoype , 用 bind 创建的函数就没有 prototype 。比如:
- function fn() {
- return this.x
- };
- var boundFn = fn.bind({
- x: 1
- })
那么 boundFn 就是没有 prototype 属性的。
- function foo(){
- console.log(this.name)
- }
- varobj={
- name:"yx",
- fn:foo
- }
- obj.fn();
控制台结果:"yx"
此时 this 指向 obj , obj 为此时的上下文环境
- varname="yx";
- function foo(){
- varname="peggy";
- console.log(this.name);
- }
- foo();
控制台结果:"yx"
此时: this 指向 global 对象
(1)浏览器环境中: this=window
(2)node 开发环境中: this=global
其实上面四种情况的顺序,我是刻意这样安排的,因为从上到下优先级依次降低。
根据这几种不同的调用方式, this 指向的对象是不同的。
依次判断 new 绑定 -> 显式绑定 -> 隐式绑定 -> 默认绑定
大家可以亲自去验证下,是不是这个顺序。
(1)
- function a(xx) {
- this.x = xx;
- return this
- };
- var x = a(5);
- var y = a(6);
- console.log(x.x);
- console.log(y.x);
(2)
- var obj = {
- x: 1,
- y: 2,
- t: function() {
- console.log(this.x)
- }
- }
- obj.t();
- var dog = {
- x: 11
- };
- dog.t = obj.t;
- dog.t();
- show = function() {
- console.log('show' + this.x);
- }
- dog.t = show;
- dog.t();
(3)
- varnumber=2;
- varobj={
- number:4,
- fn1:(function(){
- var number;
- this.number*=2;//4
- number=number*2;//NaNnumber=3;
- return function(){
- varnum=this.number;
- this.number*=2;//6
- console.log(num);
- number*=3;//9
- alert(number);
- }
- })(),
- db2:function(){
- this.number*=2;
- }
- }
- varfn1=obj.fn1;
- alert(number);
- fn1();
- obj.fn1();
- alert(window.number);
- alert(obj.number);
来源: http://www.cnblogs.com/yxhao/p/6825863.html