资讯专栏INFORMATION COLUMN

深入理解 Javascript 之 this

OnlyMyRailgun / 2943人阅读

摘要:深入浅出的理解问题的由来写法一写法二虽然和指向同一个函数,但是执行结果可能不一样。该变量由运行环境提供。所以,就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。

深入浅出this的理解 问题的由来
var obj = {
    foo: function(){}
}

var foo = obj.foo;

// 写法一
obj.foo();

// 写法二
foo();

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

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全局环境

this的原理 内存的数据结构

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

var obj = {foo: 5}

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

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

函数

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

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

这时,引擎会将函数多带带保存在内存中,然后再将函数的地址赋值给foo属性的value属性。

由于函数是一个多带带的值,所以它可以在不同的环境(上下文)执行。

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

// 多带带执行
f()

// obj 环境执行
obj.f()
环境变量
var f = function () {
  console.log(x);
};

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

现在问题就来了,由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(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。

回到我们最初的问题 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

实例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

实例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]"

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/96796.html

相关文章

  • 深入理解 Javascript 继承

    摘要:继承继承子类可以使用父类的所有功能,并且对这些功能进行扩展。类继承父类父类添加共有方法子类继承父类子类添加共有方法其中最核心的一句代码是将原型指向了父类的原型对象。 继承 继承:子类可以使用父类的所有功能,并且对这些功能进行扩展。继承的过程,就是从一般到特殊的过程。 类继承 // 父类 var supperClass = function() { var id = 1; thi...

    wuyangnju 评论0 收藏0
  • 深入理解 Javascript JS的封装

    摘要:封装常见的封装类中的共有和私有执行结果如下原因是调用的时候,指向的其实是,因此没有输出解决办法更改指向工厂函数也有对于程序员来说这三个关键字应该是很熟悉的哈,但是在中,并没有类似于这样的关键字,但是我们又希望我们定义的属性和方法有一定的访问 封装 常见的封装 function Person (name,age,sex){ this.name = name; this.a...

    张汉庆 评论0 收藏0
  • JavaScript深入浅出

    摘要:理解的函数基础要搞好深入浅出原型使用原型模型,虽然这经常被当作缺点提及,但是只要善于运用,其实基于原型的继承模型比传统的类继承还要强大。中文指南基本操作指南二继续熟悉的几对方法,包括,,。商业转载请联系作者获得授权,非商业转载请注明出处。 怎样使用 this 因为本人属于伪前端,因此文中只看懂了 8 成左右,希望能够给大家带来帮助....(据说是阿里的前端妹子写的) this 的值到底...

    blair 评论0 收藏0
  • 【进阶2-2期】JavaScript深入从作用域链理解闭包

    摘要:使用上一篇文章的例子来说明下自由变量进阶期深入浅出图解作用域链和闭包访问外部的今天是今天是其中既不是参数,也不是局部变量,所以是自由变量。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周正式开始前端进阶的第二期,本周的主题是作用域闭包,今天是第7天。 本计划一共28期,每期重点攻克一个面试重难点,如果你还不了解本进阶计...

    simpleapples 评论0 收藏0
  • 深入理解JavaScript系列6:S.O.L.I.D五大原则单一职责

    摘要:,开始我们的第一篇单一职责。通过解耦可以让每个职责工更加有弹性地变化。关于本文本文转自大叔的深入理解系列。深入理解系列文章,包括了原创,翻译,转载,整理等各类型文章,原文是大叔的一个非常不错的专题,现将其重新整理发布。 前言 Bob大叔提出并发扬了S.O.L.I.D五大原则,用来更好地进行面向对象编程,五大原则分别是: The Single Responsibility Princi...

    walterrwu 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<