V8引擎(三)

2022/10/11 V8

# 前瞻

JavaScript中"函数是一等公民"背后的含义是什么?

​ 在JavaScript中,可以将一个函数赋值给一个变量,还可以将函数作为一个参数传递给另外一个函数,亦是可以使得一个函数返回另外一个函数。由此可以看出JavaScript中的函数非常灵活,其根本原因在于JavaScript中的函数是一种特殊的对象,将JavaScript中的函数称为一等公民(First Class Function)。基于这样的设计,使得JavaScript非常容易实现一些特性,如闭包、函数式编程。

# JavaScript中的对象是什么

​ 在前瞻中提到:在JavaScript中函数是一种特殊的对象,那到底特殊在哪里呢?什么是JavaScript中的对象呢?

​ 和其他语言不同的是,JavaScript是一门基于对象(Object-Based)的语言,函数、数组都是JavaScript中的对象。并且,在这些对象运行时可以动态修改其内容。

image-20221011170939810

虽然JavaScript是基于对象设计的,但它并不是传统的面向对象(OOP)的语言,因为OOP语言天然支持封装、继承、多态,但JavaScript并没有直接提供多态的支持。除了多态,在实现继承的方面同样与传统的OOP语言存在着较大的差异。JavaScript实现继承的方式只是在对象中添加了一个称为原型的属性,把继承的对象通过原型链接起来,这就实现了继承,这种继承方式称为基于原型链继承

​ 其实,JavaScript中的对象非常简单,每个对象就是由一组组属性和值构成的集合,如下方代码创建了一个person对象:

var person = new Object();
person.name = "licodeao";
person.age = 20;
person.sex = "male";
属性
name "licodeao"
age 20
sex "male"

表格展示了person对象的结构,由多组属性和值组成,这就是JavaScript中的对象。当然,对象中属性的值可以是任意类型的数据

var person = new Object();
person.name = "licodeao";
person.sex = "male";
person.info = new Object();
person.info.age = 30;
person.info.eyeColor = "blue";
person.showInfo = function() {
  console.log("showInfo")
}

上方代码的内存图,如下图所示:

image-20221011174356775

从图中,可以看出对象的属性值有三种类型:

  • 原始类型
    • 所谓的原始类的数据,是指值本身无法被改变。比如,字符串就是原始类型,如果修改了字符串的值,那么V8引擎会返回给你一个新的字符串,而原始字符串并没有被改变
    • JavaScript中的原始类型的值null、undefined、boolean、number、string、bigint、symbol这七种
  • 对象类型
    • 对象的属性值也可以是另外一个对象
  • 函数类型
    • 如果对象中的某个属性的属性值是一个函数,那么这个属性称为方法

# 函数的本质

了解了对象,就能更好的理解函数了。

一直说的:在JavaScript中函数是一种特殊的对象,那到底特殊在哪里呢?特殊在函数能被调用

函数是一种特殊的对象,它和对象一样可以拥有属性和值

function foo() {
  var test = 1;
  console.log(test);
}
// 函数的属性和值
foo.name = "foo";
foo.id = "123";

// 函数被调用
foo();

# V8引擎是如何实现函数能被调用的特点呢?

一段很长的代码中,假设有几十个函数被调用了,那么V8引擎是如何知道哪些函数被调用了?

以及如何正确地执行对应的函数体中的代码呢?

能够猜到的是,V8引擎应该是在每个函数的内部做了某些标记。

​ 实际上,V8引擎确实对函数做了一些手脚。在V8引擎内部,会为函数对象添加两个隐藏属性(name和code),如下图:

image-20221011195207385

  • 隐藏属性name的值就是函数名称如果某个函数没有设置函数名,如下方代码:

    (function(){
      var test = 1;
      console.log(test);
    })()
    

    那么该函数对象的默认的name属性值就是anonymous,表示该函数对象没有被设置名字

  • 隐藏属性code的值表示函数体中的代码以字符串的形式存储在内存中

  • 执行到一个函数调用语句时,V8引擎便会从函数对象中取出code属性值,即对应的函数体代码,然后再解释执行这段函数代码

# 函数是一等公民

语言中的函数可以和数据类型做一样的事情,则该语言中的函数称为一等公民

执行JavaScript函数的过程中,为了实现变量的查找,V8引擎会为其维护一个作用域链,如果函数中使用了某个变量,但是在函数内部又没有定义该变量,那么函数就会沿着作用域链去外部的作用域中查找该变量

function foo() {
  var number = 0;
  function bar() {
    number++;
    console.log(number);
  }
  return bar;
}
var myBar = foo();
myBar();

在上方的代码中,在foo函数中定义了一个新的bar函数,并且bar函数引用了foo函数中的变量number,当调用foo函数时,它会返回bar函数。那么,"函数是一等公民"就体现在:如果要返回函数bar给外部(即myBar),那么即便foo函数执行结束了,其内部定义的number变量也不能销毁,因为bar函数依然引用了该变量

为什么不能销毁?

  • 其一:bar函数依然引用了number变量
  • 其二:被引用的外部变量是需要确定存在的,因为虚拟机还需要处理函数引用的外部变量,贸然删除会导致虚拟机懵逼的。

这种将外部变量和函数绑定起来的技术称为闭包,关于闭包更详细的内容可以翻阅前面的文章《关于JavaScript闭包的理解》,看完前面的文章再看此篇,相信会对函数以及闭包会有更加偏底层的理解。

# 总结

  • JavaScript中的对象是由多组属性和值组成的集合
  • 与传统OOP语言实现继承不同的是,JavaScript是基于原型链继承
  • JavaScript中"函数是一等公民"
  • V8引擎在处理函数的"可被调用"特性时,会在函数对象内部添加隐藏属性name和code,name是函数名称,code是函数体中的代码;name属性的默认值是anonymous,code所存储的值以字符串的形式存储在内存中
  • "函数是一等公民"体现在:返回内部函数给外部时,即便父函数执行结束,其内部定义的变量也不能销毁(因为被内部函数引用了或与内部函数产生了绑定)

度过了一个无聊且惬意的国庆,该使劲输出了~