深刻理解 Javascript 之 this

深刻浅出this的理解


问题的由来


var obj = {
    foo: function(){}
}

var foo = obj.foo;

// 写法一
obj.foo();

// 写法二
foo();

虽然obj.foo和foo指向同一个函数,可是执行结果可能不同。javascript

var obj = {
    foo: function() {
        conosle.log(this.bar)
    },
    bar: 2
};

var foo = obj.foo;
var bar = 3;

obj.foo(); // 2
foo(); // 3

这种差别的缘由就是由于内部使用了this关键字,this指向的是函数运行的所在环境,对于obj.foo()来讲,this执行obj,对于foo()来讲,this指向window全局环境html

this的原理


内存的数据结构

JavaScript 语言之因此有this的设计,跟内存里面的数据结构有关系。java

var obj = {foo: 5}

clipboard.png

也就是或变量obj是一个地址,后面读取obj.foo引擎先从obj拿到地址,而后再从该地址读取原始对象,返回它的属性值。

原始的对象以字典结构保存,每个属性名都对应一个属性描述对象。举例来讲,上面例子的foo属性,其实是如下面的形式保存的。面试

clipboard.png


函数


这样的结构是很清晰的,问题在于属性的值多是一个函数。json

var obj = { foo: function () {} };

这时,引擎会将函数单独保存在内存中,而后再将函数的地址赋值给foo属性的value属性。数组

clipboard.png

因为函数是一个单独的值,因此它能够在不一样的环境(上下文)执行。数据结构

var f = function () {};
var obj = { f: f };

// 单独执行
f()

// obj 环境执行
obj.f()

环境变量


var f = function () {
  console.log(x);
};

上面代码中,函数体里面使用了变量x。该变量由运行环境提供。app

如今问题就来了,因为函数能够在不一样的运行环境执行,因此须要有一种机制,可以在函数体内部得到当前的运行环境(context)。因此,this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。函数

var f = function () {
  console.log(this.x);
}

var x = 1;
var obj = {
  f: f,
  x: 2,
};

// 单独执行
f() // 1

// obj 环境执行
obj.f() // 2

在obj环境执行,this.x指向obj.x。
函数f在全局环境执行,this.x指向全局环境的x。post

回到咱们最初的问题 obj.foo()是经过obj找到foo,因此就是在obj环境执行。一旦var foo = obj.foo,变量foo就直接指向函数自己,因此foo()就变成在全局环境执行。

阮一峰老师的 this原理


继续咱们的this

this在js中一直是谜同样的存在着,在面试中也是常常会被问道

this的指向在函数建立的时候是决定不了的,在调用的时候才能决定

  • 全局范围内
this;    //在全局范围内使用`this`,它将会指向全局对象

var name="zhoulujun";

function say(){
    console.log(this.name)
}
say(); //zhoulujun

当执行 say函数的时候, JavaScript 会建立一个 Execute context (执行上下文),执行上下文中就包含了 say函数运行期所须要的全部信息。 Execute context 也有本身的 Scope chain, 当函数运行时, JavaScript 引擎会首先从用 say函数的做用域链来初始化执行上下文的做用域链。

  • 函数调用
foo();    //this指向全局对象
  • 方法调用*
test.foo();    //this指向test对象
  • 调用构造函数*
new foo();    //函数与new一块使用即构造函数,this指向新建立的对象
  • 显式的设置this*
function foo(a, b, c) {}
var bar = {};
foo.apply(bar, [1, 2, 3]);    //this被设置成bar
foo.call(bar, 1, 2, 3);       //this被设置成bar

从函数调用理解this

实例1

myObj3={
        site:"zhoulujun.cn",
        andy:{
            site:"www.zhoulujun.cn",
            fn:function(){
                console.log(this)
                console.log(this.site)
            }
        }
    };
var site="111";
var fn=myObj3.andy.fn;
fn();  // 这里的调用环境是window


// Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
// 111

clipboard.png

实例2

  • 若是一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象
myObj3={
    site:"zhoulujun.cn",
    andy:{
        site:"www.zhoulujun.cn",
        fn:function(){
            console.log(this)
            console.log(this.site)
        }
    }
};
var site="111";
myObj3.andy.fn();


VM51:6 {site: "www.zhoulujun.cn", fn: ƒ}
VM51:7 www.zhoulujun.cn

clipboard.png

实例3

document.getElementById( 'div1' ).onclick = function(){
    console.log( this.id );// 输出: div1
    var func = function(){ 
        console.log ( this.id );// 输出: undefined
    } 
    func();
}; 
//修正后
document.getElementById( 'div1' ).onclick = function(){
    var func = function(){ 
        console.log ( this.id );// 输出: div1
    } 
    func.call(this);
};

实例4

var A = function( name ){ 
    this.name = name;
};
var B = function(){ 
    A.apply(this,arguments);
};
B.prototype.getName = function(){ 
    return this.name;
};
var b=new B('sven');
console.log( b.getName() ); // 输出:  'sven'

实例5

function foo() {
    console.log( this.a );
}

var obj1 = {
    a: 2,
    foo: foo
};

var obj2 = {
    a: 3,
    foo: foo
};

obj1.foo(); // 2
obj2.foo(); // 3

obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2

apply、call

由于apply、call存在于Function.prototype中,因此每一个方法都有这两个属性。

call
函数名.call(对象,arg1....argn)
//功能:
    //1.调用函数
    //2.将函数内部的this指向第一个参数的对象
    //3.将第二个及之后全部的参数,做为实参传递给函数
apply主要用途是直接用数组传参
函数名.apply(对象, 数组/伪数组);
//功能:
    //1.调用函数
    //2.将函数内部的this指向第一个参数的对象
    //3.将第二个参数中的数组(伪数组)中的元素,拆解开依次的传递给函数做为实参
//案例求数组中最大值
var a=Math.max.apply( null, [ 1, 2, 5, 3, 4 ] );
console.log(a);// 输出:5

call应用(将伪数组转为数组)

var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }
Array.prototype.join.call(arrayLike, '&'); // name&age&sex
Array.prototype.slice.call(arrayLike, 0); // ["name", "age", "sex"] 
// slice能够作到类数组转数组
Array.prototype.map.call(arrayLike, function(item){
    return item.toUpperCase();
}); 
// ["NAME", "AGE", "SEX"]


console.log(
    Object.prototype.toString.call(num),
    Object.prototype.toString.call(str),
    Object.prototype.toString.call(bool),
    Object.prototype.toString.call(arr),
    Object.prototype.toString.call(json),
    Object.prototype.toString.call(func),
    Object.prototype.toString.call(und),
    Object.prototype.toString.call(nul),
    Object.prototype.toString.call(date),
    Object.prototype.toString.call(reg),
    Object.prototype.toString.call(error)
);
// '[object Number]' '[object String]' '[object Boolean]' '[object Array]' '[object Object]'
// '[object Function]' '[object Undefined]' '[object Null]' '[object Date]' '[object RegExp]' '[object Error]'
相关文章
相关标签/搜索