zoukankan      html  css  js  c++  java
  • JS的关键字this

    1.关于this

    this关键字是JavaScript中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。

    1.1 为什么要用this?

    this提供了一种更优雅的方式来隐式地“传递”一个对象引用,因此可以将API设计得更加简洁并且易于复用。

    function identify(){
    	return this.name.toUpperCase();
    }
    
    function speak(){
    	var greeting = "Hello, I'm " + identify.call(this);
            console.log(greeting);
    }
    
    var me = {
    	name : "Kyle"
    };
    
    var you = {
    	name : "Reader"
    };
    
    console.log(identify.call(me));//KYLE
    console.log(identify.call(you));//READER
    
    speak.call(me);//Hello, I'm KYLE
    speak.call(you);//Hello, I'm READER
    

    如果不使用this,那就需要给identify()speak()显式传入一个上下文对象。

    function identify(context){
    	return context.name.toUpperCase();
    }
    
    function speak(context){
    	var greeting = "Hello, I'm " + identify(context);
    	console.log(greeting);
    }
    
    var me = {
    	name : "Kyle"
    };
    
    var you = {
    	name : "Reader"
    };
    
    console.log(identify(you));//READER
    speak(me);//Hello, I'm KYLE
    

    1.2 误解

    人们很容易把this理解成指向函数自身。事实上,this并不是指向函数本身。
    如果要从函数对象内部引用它自身,那只使用this是不够的。一般来说你需要通过一个指向函数对象的词法标识符(变量)来引用它。

    function foo(){
    	foo.count = 4;//foo指向它自身
    }
    
    setTimeout(function(){
    	//匿名函数无法指向自身
    },10);
    

    在需要自引用时使用具名函数(表达式)。

    function foo(num){
    	console.log("foo: " + num);
    	this.count++;
    }
    foo.count = 0;
    var i;
    for(i=0; i<10; i++){
    	if(i > 5){
    		//使用call(..)可以确保this指向函数对象foo自身
    		foo.call(foo,i);
    	}
    }
    //foo: 6
    //foo: 7
    //foo: 8
    //foo: 9
    console.log(foo.count);//4
    

    1.3 this到底是什么

    this是 在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

    2.this全面解析

    2.1 调用位置

    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的调用位置
    

    可以使用浏览器的开发者工具查看调用栈。

    2.2 绑定规则

    默认绑定

    function foo(){
    	console.log(this.a);
    }
    
    var a = 2;
    
    foo();//2
    

    this的默认绑定:this指向全局对象。
    foo()是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。
    如果使用严格模式(strict模式),则不能将全局对象用于默认绑定,因此this会绑定到undefined。
    通常来说你不应该在代码中混合实用strict模式和非strict模式。

    隐式绑定

    function foo(){
    	console.log(this.a);
    }
    
    var obj = {
    	a : 2,
    	foo : foo
    };
    
    obj.foo();//2
    

    foo()被调用时,它的前面加上了对obj的引用。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因为调用foo()this被绑定到obj,因此this.aobj.a是一样的。

    function foo(){
    	console.log(this.a);
    }
    
    var obj = {
    	a : 2,
    	foo : foo
    };
    
    var bar = obj.foo;
    var a = "oops, global";
    bar();//oops, global
    

    虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

    显式绑定

    在某个对象上强制调用函数,可以使用函数的call(...)apply(...)方法。
    JavaScript提供的绝大多数函数以及你自己创建的所有函数都可以使用call(...)apply(...)方法。

    function foo(){
    	console.log(this.a);
    }
    
    var obj = {
    	a : 2
    };
    
    foo.call(obj);//2
    

    通过foo.call(...),我们可以在调用foo时强制把它的this绑定到obj上。
    如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作this的绑定对象,这个原始值会被转换成它的对象形式(也就是new String(..)new Boolean(..)或者new Number(..))。这通常被称为“装箱”。

    硬绑定
    function foo(){
    	console.log(this.a);
    }
    
    var obj = {
    	a : 2
    };
    
    var bar = function(){
    	foo.call(obj);
    };
    
    bar();//2
    setTimeout(bar,100);//2
    
    //硬绑定的bar不可能再修改它的this
    bar.call(window);//2
    

    硬绑定是一种非常常用的模式,ES5提供了内置的方法Function.prototype.bind

    function foo(something){
    	console.log(this.a,something);
    	return this.a + something;
    }
    
    var obj = {
    	a : 2
    };
    
    var bar = foo.bind(obj);
    
    var b = bar(3);//2 3
    console.log(b);//5
    

    new 绑定

    在JavaScript中,构造函数只是一些使用new操作符时被调用的函数,它们并不会属于某个类,也不会实例化一个类。
    包括内置对象函数在内的所有函数都可以用new来调用,这种函数调用被称为构造函数调用。
    使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

    1. 创建(或者说构造)一个全新的对象。
    2. 这个新对象会被执行[[Prototye]]连接。
    3. 这个新对象会绑定到函数调用的this
    4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
    function foo(a){
    	this.a = a;
    }
    
    var bar = new foo(2);
    console.log(bar.a);//2
    

    2.3 优先级

    默认绑定的优先级是四条规则中最低的。
    显式绑定的优先级比隐式绑定的优先级高。
    new绑定比隐式绑定优先级高。
    硬绑定(显式绑定的一种)比new绑定的优先级高。
    我们可以根据优先级来判断函数在某个调用位置应用的是哪条规则:

    1. 函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。
    2. 函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。
    3. 函数在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。
    4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。

    2.4 绑定例外

    如果你把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的时候默认绑定规则。

    function foo(){
    	console.log(this.a);
    }
    
    var a = 2;
    foo.call(null);//2
    

    总是使用null来忽略this绑定可能产生一些副作用。

    function foo(){
    	console.log(this.a);
    }
    
    var a = 2;
    var o = {a:3, foo:foo};
    var p = {a:4};
    
    o.foo();//3
    (p.foo = o.foo)();//2
    

    赋值表达式p.foo = o.foo的返回值是目标函数的引用,因此调用位置是foo()而不是p.foo()或者o.foo(),所以这里会应用默认绑定。

  • 相关阅读:
    链式队列(先进先出)
    单链表的基本操作
    线性表的基本操作
    面向对象分析与设计
    结构化分析与设计
    图片
    2021-ACM-ICPC-济南站 K Search For Mafuyu 【树的遍历与回溯,dfs, 邻接表】
    Codeforces Round #754 (Div. 2), problem: (A) A.M. Deviation泪目 万万没想到狂wa是因为这
    Codeforces Round #704 (Div. 2), problem: (C) Maximum width还是要多学习
    Educational Codeforces Round 116 (Rated for Div. 2), problem: (C) Banknotes
  • 原文地址:https://www.cnblogs.com/gzhjj/p/9015458.html
Copyright © 2011-2022 走看看