V8-学习2-js设计思想-函数-表达式/快慢属性/原型链/作用域/D8

174 阅读11分钟

1.函数即对象

JavaScript 是一门基于对象 (Object-Based) 的语言

  • 都是对象
    • function函数
    • array数组
    • object对象
  • 由于对象可以运行时修改,导致语言过于灵活
  • 每个对象就是由一组组属性和值构成的集合
    • 值可以是任意类型
      • 原始类型
        • null、undefined、boolean、number、string、bigint、symbol 这七种。
      • 对象
      • 函数

js不是面向对象的语言

  • 不支持 继承,封装, 多态
    • 没有对应的关键字如public interface extend等
  • 不过通过原型链的方式间接实现继承

函数是一等公民,一个特殊的对象

  • 如果函数可以和它的数据类型做一样的事情,这个函数称为一等公民
  • 可以被调用,在被调用时,会关联相关的执行上下文
  • 做参数,变量
  • 做返回值
  • 因为是对象也可以由一组组属性和值构成的集合
function foo(){
    var test = 1
}
foo.myName = 1
foo.uName = 2
console.log(foo.myName)
  • 能实现函数式编程
  • 在闭包的场景下依然给函数保留调用外部变量的权利
function a(){
    var cnt = 1//这里为b函数保留了引用cnt变量
    function b(){
        cnt++
    }
    return b
}
var b = a()
b()

V8函数实现

函数对象会包含隐藏的 name 和 code 两个字段

  • name 函数名
    • 匿名函数默认给anonymous
  • code 函数体

2.快属性和慢属性

变量存储3种方式

快属性

  • 直接存在对象 线性属性列表
  • 排序属性列表
    • elements
    • 存放数字,按数字的大小升序排序
        obj[50] = '50'obj[2] = '2'obj[1] = '1'
        obj[1] = '1'obj[2] = '2'obj[50] = '50'
    
    • 使用线性存储
      • 效率高
      • 不支持大数据量

慢属性

  • 存储在独立的非线性数据结构 (词典)
  • 常规属性列表
    • properties
    • 存放字符串,按存入的时间先后升序排序
        obj['B'] = 'B'obj['A'] = 'A'obj["C"] = 'C'
    
      - 位置不变
    
    • 使用字典存储
      • 支持大数据量
      • 效率低

对象内属性 (in-object properties)

当查询obj['C'] 都要先查询排序属性,再查询常规属性,才找到obj的C属性,通过把 C属性直接保存在对象中,直接访问obj['C']

  • 触发条件
    • 小于等于10个
      • n个对象内置
    • 大于10个小于20
      • 10个对象内置
      • 10个线性常规属性的数据
    • 大于20
      • 10个对象内置
      • 10+ 非线性(字典)常规属性的数据

对象是一组属性+值

正常查询流程

  1. 先查询排序属性的数据
  2. 再查询常规属性的数据

通过对象内置提升查询的效率

例子

var obj = {}
obj[50] = '50'
obj[2] = '2'
obj["B"] = 'B'
obj[1] = '1'
obj[3] =  '3'
obj[8] = '8'
obj[10] = '10'
obj[5] = '5'
obj["A"] = 'A'
obj["C"] = 'C' 
for(key in obj){
    console.log(`index:${key}  value:${obj[key]}`)
} 
//打印结果
// index:1  value:1
// index:2  value:2
// index:3  value:3
// index:5  value:5
// index:8  value:8
// index:10  value:10
// index:50  value:50
// index:B  value:B
// index:A  value:A
// index:C  value:C

3.函数表达式

函数声明

  • 是一种语句,操作值的式子,没有明确的返回值
  • 在编译阶段,会对函数进行提升的操作,执行的时候可以正常使用
a()
function a(){
    console.log('xxx')
}
  • 实际function a()等价于一个对象实例
  • 所以被编译阶段被提升

函数表达式

  • 是表示值的式子,有具体的返回值
  • V8 并不会将表达式中的函数对象提升到全局作用域中,所以无法在函数表达式之前使用该函数
  • 函数立即表达式是一种特别的表达式,主要用来封装一些变量、函数,可以起到变量隔离和代码隐藏的作用
fun()
var fun = function a(){
    console.log('xxx')
}

函数表达式可以拆分为

var fun = undefined
fun = function a(){
    console.log('xxx')
}
  • 实际编译时,只有var fun = undefined
  • 导致执行的时候fun 为undefined无法调用

提升函数

提升整个对象

提升变量

提升到作用域中默认是undefined

V8执行js的流程是编译,然后再执行

1.编译阶段会把,声明的变量做全局的提升,即表达式左边的内容 2. 执行代码时,只执行表达式右侧的内容 function a(){}跟 var a = {} 类似,都是左边的声明,在编译阶段会做变量提升。

4.原型链

主要实现继承效果

定义

就是一个对象可以访问另外一个对象中的属性和方法

语言实现

基于类的设计

  • 关键字实现
  • 加大了维护的复杂度

基于原型继承的设计

  • __proto__为指向当前对的原型 (prototype)
    • 不建议直接操作__proto__
      1. 是内部隐藏属性
      2. 会导致严重的内存问题
      • 通过new 来处理原型的指向,实现继承
      • new只是当年蹭java的热度使用的关键字,跟java的意思new不同
  • 查找原理
    • 依次从当前对象-> 当前对象的原型对象,依次往上找,直到找不到位置
  • 原型链
    • 查找属性的路径称

代码实现

  1. 直接操作__proto__
var obj = {a:1}
var obj2 = {}
obj2.__proto__ = obj
console.log(obj2.a)
  1. new创建
var Obj = function(){
    this.a = 1
} 
var obj2 = new Obj() 
console.log(obj2.a)



// 底层实现
var tempObj = {}  
tempObj.__proto__ = Obj.prototype
Obj.call(tempObj) //这里设置后 a属性已经是obj2的了

函数对象prototype 属性-实现属性共享

  • 函数对象包含 3个隐藏属性(这里指的是函数的定义,而不是实例)
    • name
    • code
    • prototype
      • 新对象的原型对象指向了构造函数的 prototype 属性
//例子
function Obj(type,color){
    this.num = 100
}
var obj1 = new Obj()
var obj2 = new Obj()
var obj3 = new Obj()

//优化后
function Obj(type,color){
  
}
Obj.prototype.num = 100
var obj1 = new Obj()
var obj2 = new Obj()
var obj3 = new Obj()

5.作用域链

  • V8启动就会创建全局作用域,一直在内存不销毁,直到V8销毁
  • 函数作用域,在函数执行才创建,函数结束自动销毁
  • 全局作用域的变量
    • window
    • document
    • opener

执行流程

  1. V8启动初始化全局作用域
  2. 启动消息循环,依次执行代码
  3. 遇到顶层代码Top Level ,编译代码,把顶层代码定义的变量设置到全局作用域
  4. 执行全局代码阶段,当遇到函数,又会进行编译,把函数内定义的变量设置到函数作用域
  5. 执行函数的代码,当当前函数的变量找不到时,会往outer的对象往上查询变量

作用域链

  • js是使用词法作用域
    • 函数定义时候已经确定了变量访问的关联
    • 由于js是先编译,后执行,编译阶段已经确定的对应的outer关系是当前的所在的执行作用域,可以是全局作用域,也可以是函数作用域

例子

var name = "a"
function b() {
    // var name = "b"
    function c() { 
        // var name = "c"
        console.log(name)
    }
    c()
} 
function d() {
    var name = "d"
    b()
}
d()

当在函数c里面执行输出name 使用的是 定义时的 全局a

流程

  1. 编译顶层代码 全局作用域存放 变量name 方法b 方法d 的声明,outer 和 this 指向window
  2. 执行代码 调用 方法d ,开始编译d ,函数作用域d 里面存放 变量 name
  3. 执行方法d ,里面调用 方法b,开始编译b,函数作用域b包含 方法c声明。
  4. 执行方法b,里面调用 方法c ,编译c,执行c,由于c里面没有name 通过 outer 属性往外查找
  5. b里面也没有name,查找b对应于的outer,找到是window
  6. 最终name是属于全局作用域上的'a'

6.类型转换

类型系统 Type System

  • 机器语言
    • 二进制10001001
      • 直接位移 取或与等
  • 高级语言
    1. 定义不同的类型,各自相同类型进行处理
    2. 各种不同类型,定义不同规则进行操作

1 + "1" = 11

  • v8默认会优先以字符串为标准处理
  • 转化的方法
    • toPrimitive返回原生类型(如数字,或者字符串)
    • 依次执行流程 toPrimitive
      • -> valueOf -> toString ->报错
  • 最终转化为
    • Number("1").toString() + "1"
    • 先通过 1 转化为"1" 叫做强刷类型转化,也叫隐式转化

处理流程

  1. 如果输入的值已经是一个原始值,则直接返回它
  2. 否则,如果输入的值是一个对象,则调用该对象的valueOf()方法, 如果valueOf()方法的返回值是一个原始值,则返回这个原始值。
  3. 否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。
  4. 否则,抛出TypeError异常。

例子

var myObj = {
    toString() {
      return new Object()
    }, 
    valueOf() {
      return new Object()
    }   
}
myObj+3
//这里由于操作的是数字,所以会以valueOf优先执行,valueOf 是object对象,不满足原生类型,继续调用toString 依然不是,则报错

//当  
valueOf() {
    return 6
}    
//返回 9     


// 当  
toString() {
    return 9
}, 
valueOf() {
    return new Object()
}   
//返回12


// 当 
valueOf() {
    return '6'
}
//返回'63'
//这里有再次触发隐式转化,把3 变成 Number('3').toString() 


7.D8

下载

作者地址:github.com/binaryacade…

其他

mac平台:https://storage.googleapis.com/chromium-v8/official/canary/v8-mac64-dbg-8.4.109.ziplinux32平台:https://storage.googleapis.com/chromium-v8/official/canary/v8-linux32-dbg-8.4.109.ziplinux64平台:https://storage.googleapis.com/chromium-v8/official/canary/v8-linux64-dbg-8.4.109.zipwin32平台:https://storage.googleapis.com/chromium-v8/official/canary/v8-win32-dbg-8.4.109.zipwin64平台:https://storage.googleapis.com/chromium-v8/official/canary/v8-win64-dbg-8.4.109.zip
https://github.com/GoogleChromeLabs/jsvu/fork
https://storage.googleapis.com/chrome-infra/depot_tools.zip

grep地址

https://sourceforge.net/projects/gnuwin32/files/grep/2.5.4/grep-2.5.4-setup.exe/download?use_mirror=managedway

配置

在系统配置path D:\my_project\geektime-v8-master\out.gn\x64.release

命令

// 简单例子
var a = 'xxx'
d8 --print-ast a.js

// 查看支持命令
d8 --help |grep print

//抽象语法树
--print-ast 
// --- AST ---
// FUNC at 0
// . KIND 0
// . LITERAL ID 0
// . SUSPEND COUNT 0
// . NAME ""
// . INFERRED NAME ""
// . DECLS
// . . VARIABLE (0x7ff0e3022298) (mode = VAR, assigned = true) "a"
// . BLOCK NOCOMPLETIONS at -1
// . . EXPRESSION STATEMENT at 11
// . . . INIT at 11
// . . . . VAR PROXY unallocated (0x7ff0e3022298) (mode = VAR, assigned = true) "a"
// . . . . LITERAL "xxxx"


//作用域
---print-scope
// Global scope:
// global { // (0x7fd974022048) (0, 24)
//   // will be compiled
//   // 1 stack slots
//   // temporary vars:
//   TEMPORARY .result;  // (0x7fd9740223c8) local[0]
//   // local vars:
//   VAR a;  // (0x7fd974022298)
// }


//中间代码
--print-bytecode
// [generated bytecode for function:  (0x2b510824fd55 <SharedFunctionInfo>)]
// Parameter count 1
// Register count 4
// Frame size 32
//          0x2b510824fdd2 @    0 : a7                StackCheck
//          0x2b510824fdd3 @    1 : 12 00             LdaConstant [0]
//          0x2b510824fdd5 @    3 : 26 fa             Star r1
//          0x2b510824fdd7 @    5 : 0b                LdaZero
//          0x2b510824fdd8 @    6 : 26 f9             Star r2
//          0x2b510824fdda @    8 : 27 fe f8          Mov <closure>, r3
//          0x2b510824fddd @   11 : 61 32 01 fa 03    CallRuntime [DeclareGlobals], r1-r3
//          0x2b510824fde2 @   16 : 12 01             LdaConstant [1]
//          0x2b510824fde4 @   18 : 15 02 02          StaGlobal [2], [2]
//          0x2b510824fde7 @   21 : 0d                LdaUndefined
//          0x2b510824fde8 @   22 : ab                Return
// Constant pool (size = 3)
// 0x2b510824fd9d: [FixedArray] in OldSpace
//  - map: 0x2b51080404b1 <Map>
//  - length: 3
//            0: 0x2b510824fd7d <FixedArray[4]>
//            1: 0x2b510824fd1d <String[#8]: GeekTime>
//            2: 0x2b51081c8549 <String[#4]: a>
// Handler Table (size = 0)
// Source Position Table (size = 0)
      
      
//查看被优化的代码
--trace-opt-verbose
    
//被优化过的代码
--trace-opt

//被反优化的代码
--trace-deopt


//优化/反优化例子
let a = {x:1}
function bar(obj) { 
  return obj.x 
}
function foo () { 
  let ret = 0
  for(let i = 1; i < 7049; i++) {
    ret += bar(a)
  }
  return ret
}
foo()
//bar被多次执行,会被优化TurboFan 优化为二进制


//合并例子
let a = {x:1}
function bar(obj) { 
  return obj.x 
}
function foo () { 
  let ret = 0
  for(let i = 1; i < 1000000; i++) {
    ret += bar(a)
  }
  return ret
}
foo()
//当循环很大的时候,会把方法foo 和 bar 做合并,优化栈的减少栈的创建和销毁
//On-Stack Replacement ,OSR 替换正在运行的函数的栈帧,函数合并



//查看代码 垃圾回收情况
d8 --trace-gc test.js
//例子
function strToArray(str) {
  let i = 0
  const len = str.length
  let arr = new Uint16Array(str.length)
  for (; i < len; ++i) {
    arr[i] = str.charCodeAt(i)
  }
  return arr;
}


function foo() {
  let i = 0
  let str = 'test V8 GC'
  while (i++ < 1e5) {
    strToArray(str);
  }
}


foo()
//1. 在方法里,每次调用都会 实例化数组,销毁数组,会频繁触发 新生代垃圾回收,一般空间只有1-8m 超过了就会触发,
// 优化 把new Uint16Array 提前到 foo 里,则只创建已一份内存空间,后续的读写都在同一块区域,不触发垃圾回收 



// js调用 V8 提供的内部方法
--allow-natives-syntax

function Foo(property_num,element_num) {
  //添加可索引属性
  for (let i = 0; i < element_num; i++) {
      this[i] = `element${i}`
  }
  //添加常规属性
  for (let i = 0; i < property_num; i++) {
      let ppt = `property${i}`
      this[ppt] = ppt
  }
}
var bar = new Foo(10,10)
console.log(%HasFastProperties(bar));
delete bar.property2
console.log(%HasFastProperties(bar)); 
//%HasFastProperties可以查看对应的 快属性

//详细接口:https://github.com/v8/v8/blob/4b9b23521e6fd42373ebbcb20ebe03bf445494f9/src/runtime/runtime.h