javascript function 一些解惑

本文探讨了JavaScript与Java在编程语言底层的差异,重点在于JS的内存结构、上下文、this指针、方法作为第一等公民以及闭包的概念。通过示例代码解析了变量查找规则,方法调用时的上下文确定以及this的动态性。文章强调了JS中function的特殊地位和内存分配方式,并通过闭包解释了如何保持变量的状态。
摘要由CSDN通过智能技术生成

前序

笔者因为一直在做 java ,所有在了解 javascript 的时候,总是在与 java 进行对比和联系。但是 jsjava 在编程语言的底层有很多不同的地方,导致一直联系一直懵逼,与旧的知识产生不到共鸣。

直到最近,了解到js 内存结构,从底层了解 js 的运行机制,对一些概念就从底层可以理解。特意写文章来总结一下。

本文阅读思路是,罗列一些有理解上有困扰的代码(分别是,上下文、this指针、方法是第一等公民、闭包等概念),描述分析过程,对比 java语言的差异,加深 javascript 的理解。

困惑的代码

取哪个变量

讨论 方法上下文,或者说是 变量作用域 ( 记得 与this指针无关 )。

// step1
var a = 1;
var f = function(){
    console.log(a);
}
f(); // 1

// step2
var f1 = function(k) {
    var a = 22;
    k();
}
f1(f); //1

Q:
step2 中 为什么 a 不是取 k() 运行代码前定义的值 22 的吗?
A:

  1. step1 中,定义 f@function 的时候,方法上下文windows,找 a@variable 的时候,会在上下文中找,所以值是 1
  2. step2 中, f@function 作为入参给到 f1@function,并且在 f1@function 中执行,且f1@function 也是定义了 a = 22, 但是 f@function 运行后 console 打印 还是 1.

因为 方法上下文定义时 已指定了,并不是 运行时指定。所以 k@function 运行的时候,还是会找回 定义时的 方法上下文 变量a的值,输出 1
用代码简写一下:

define function f {
  f.context = windows  
}

找变量就从 context 中找。

var k = function() {
    var a = 22;
    var f = function(){
        console.log(a);
    }
    return f;
}
var a = 1;
k()(); //22

定义 f@function 的时候,指定了 方法上下文k@function( PS: 不是 k@object), 所以 找到 k@function.a的值。
用代码简写:

define function k {
  define function f {
    f.context = k@function  
  }
} 

结论

  1. 上下文(不论是 方法上下文,还是下文提到的 运行时上下文 )或者说是 变量的作用域,只有两种可能:1. windows, 2. function。换句话,变量的作用域只存在这两个主体内。
  2. 方法上下文 已经在 定义时 已经是决定了,即 定义时的 windows 和 function 中。
  3. 找变量的值(PS:没有 this关键字 指定),都是在 方法上下文 中找

扩展
java一样, 在一片内存中分配变量且赋值。
但是 java 是以 object 为单位 划分内存,javascript 是以 function 为单位划分内存 (windows 为父内存单位 )。

this 指针后,取哪个变量

讨论 this 对读取变量的影响

// 前置条件
var a = 2;
var v = {
    a: 1,
    b: function(){
        console.log(this.a)
    }
}

// 验证
v.b(); // 1  //step1

var f = v.b; //step2
f(); // 2

Q:
同样是调用一样的方法,step1 取到 a 的变量是 1 ,step2 取到 a 的变量是 2?
A:

  1. step1 中,v.b() 就是指,先找到 v@obj,再调用 b@function。所以他的 _运行时上下文 (调用时)_是 v@obj, runningContext = v@obj,同时 this 会指向 runningContext, 所以 this.a = v.a,所以值是 1.
  2. step2 中,f = v.b就是,把 v.b 方法的地址赋值给 f,再运行 f@function,等于在 windows上下文中运行 f@function,所以他的 _运行时上下文 是 windows, _runningContext = windows _。_所以 this.a = windows.a

可能换种写法,会更加的清晰。

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

var a = 2;
var v = {
    a: 1,
    b: f    // 这种写法,仅仅是属性的赋值,定义还是在外部的上下文
}

// 验证
v.b(); // 1

var f = v.b;
f(); // 2

this 是动态的。根据方法绑定在哪个 运行时上下文,他就会在哪里去找变量。

结论

  1. this 指针指向的变量,都是取自绑定的 _运行时上下文 _
  2. this动态的,绑定不同的对象,就有不同的 运行时上下文

扩展

  1. 如果想强制的绑定上下文,固定 this 指向,可以使用 call apply bind 等方法。
  2. this概念,是 javascriptjava 有大的差异点。java this,是指向本对象内部,一个对象生成了,this 的指向就确定了;js this 是指 运行时上下文,随着绑定的 运行时上下文不同,他的指向的变量都不同。

结合第一个示例,得到下面的结论:

  1. 方法内的变量,如果没有 this指定的变量,是在 定义上下文 中找值
  2. 方法内的变量,如果有 this指定的变量,是在 运行时上下文 中找值

方法是第一等公民

在 javascript 中,function 有特殊的用法

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

var a = 2;
var v = {
    a: 1,
    b: f  // step1
}

// 验证
v.b(); // 1  // step2

var fn = v.b;  // step3
fn(); // 2

结论

  1. 方法调用,对比 java的方法调用,必须是经过对象,再调起方法;javascript 的 方法,可以有自己的内存地址,可以直接的调用,同时 方法又可以赋值给其他的变量。

javascriptfunction 是一等公民。这个是经常听到的一句话。在理解上可以有两个衍生的层次:

  1. function 可以有自己的内存地址
    1. function 可以作为入参,传入其他的方法
    2. function 可以直接调用,不用依附于某个对象
    3. function 可以绑定在不同的 运行时上下文 中,动态改变 this 的值
  2. function 定义的时候,就已经有自己的 定义时上下文
    1. 从底层就是能够开辟了一个内存空间

扩展:对比 java

  1. 方法的调用
    1. java,必须是 obj.function(),必须是经过对象才找到方法,并且调用
    2. jsfunction(),可以是直接的调用,也可以绑定某个运行时上下文后再调用
  2. 方法的定义
    1. java,必须 obj 内部
    2. js,在在 不同的上下文中,即是 windows 或者 不同的 function
  3. 内存的划分
    1. java,分配内存的单位 生成对象(不是定义类),就可以指定一片内存
    2. js,分配内存的单位 定义方法,就可以划分了一片内存

dive in 内存结构

js function 是有地址的,并且可以单独调用。

var f = function(){}
var v = { 
    b: f  
}

方法作为入参

主要是讨论方法地址

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

var a = 2;
var v = {
    a: 1,
    b: f  // step0
}

var callback = function(fn) {
    fn();
}

// step1
callback(v.b);  //2
// step2
callback(()=>{v.b()});  //1

Q:
大家都是调用 v.b,为什么会有差异呢?
A:

  1. step0 中,定义 v.b 的属性,是 f@function 的地址
  2. step1 中,调起 callback@function,入参 fn 会复制 v.b 的值,而 v.b 的值就是 f@function 的值,即是直接调用了 f@function,所以就会看到 this 绑定了 windows,与预期调用不一致
  3. step2 中,入参是匿名方法,方法体内,显式调用v.b(),f 里面的 this 就会绑定 v@obj 上,符合预期

结论

  1. function 有独立的地址,可以直接调用,也可以传参;也可以赋值给对象 obj 的属性,经过 obj 调用仅仅是改变 this 的值,就是运行时上下文
  2. 解决 this 指针不符合预期,可以在外层封装一个函数,把 v.b() 显示的调起,这样就符合调用的预期

闭包

涉及到 方法的定义上下文

var getCalculator = function(num){   // step1
    return function(){  // step2
        return num +=1 
    }
}

var inc = getCalculator(7);  //step3
inc();  // 8  //step4
inc();  // 9
var a = inc();
console.log(a); // 10

Q:
为什么没有传参给 inc,但是 num 却可以一直保持
A:

  1. step1, 定义了 getCalculator@function,开辟了 定义上下文(简称 getCalculator@context ),在 上下文中就有 num 定义 和 匿名函数 定义
  2. step2,匿名函数 持有 getCalculator@context,所以 num 一直对 匿名函数 accessable
  3. step3,调用 getCalculator@function,进入栈帧,入参值 7 赋值给 num,同时返回 匿名函数 到外部且 inc 持有。 inc 一直被持有,即 getCalculator@context 一直保持,即 num 一直保持
  4. step4,调用 inc@function,持续更新 getCalculator@context.num 的值

内存结构

不论是深绿和浅绿色,都是开辟的内存。外部的绿色,就是 anonymous@function 的定义上下文,inc 可以随意进入,找变量的值。

结论

  1. 持有方法,就等于持有了定义这方法时的 定义上下文
  2. _定义上下文,_包含了 父方法定义的变量父方法的入参变量

结论

  1. js 中的 function 是 第一等公民,他有 独立的内存空间 和 内存地址,可以直接的调用,也可以作为入参传递
  2. 找变量的时候,没有 this,就找 定义上下文
  3. 找变量的时候,有 this,就找 运行时上下文
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江南西肥肥

你的小小鼓励是给我巨大的奖励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值