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+ 非线性(字典)常规属性的数据
- 小于等于10个
对象是一组属性+值
正常查询流程
- 先查询排序属性的数据
- 再查询常规属性的数据
通过对象内置提升查询的效率
例子
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__
- 是内部隐藏属性
- 会导致严重的内存问题
- 通过new 来处理原型的指向,实现继承
- new只是当年蹭java的热度使用的关键字,跟java的意思new不同
- 不建议直接操作__proto__
- 查找原理
- 依次从当前对象-> 当前对象的原型对象,依次往上找,直到找不到位置
- 原型链
- 查找属性的路径称
代码实现
- 直接操作__proto__
var obj = {a:1}
var obj2 = {}
obj2.__proto__ = obj
console.log(obj2.a)
- 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
执行流程
- V8启动初始化全局作用域
- 启动消息循环,依次执行代码
- 遇到顶层代码Top Level ,编译代码,把顶层代码定义的变量设置到全局作用域
- 执行全局代码阶段,当遇到函数,又会进行编译,把函数内定义的变量设置到函数作用域
- 执行函数的代码,当当前函数的变量找不到时,会往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
流程
- 编译顶层代码 全局作用域存放 变量name 方法b 方法d 的声明,outer 和 this 指向window
- 执行代码 调用 方法d ,开始编译d ,函数作用域d 里面存放 变量 name
- 执行方法d ,里面调用 方法b,开始编译b,函数作用域b包含 方法c声明。
- 执行方法b,里面调用 方法c ,编译c,执行c,由于c里面没有name 通过 outer 属性往外查找
- b里面也没有name,查找b对应于的outer,找到是window
- 最终name是属于全局作用域上的'a'
6.类型转换
类型系统 Type System
- 机器语言
- 二进制10001001
- 直接位移 取或与等
- 二进制10001001
- 高级语言
- 定义不同的类型,各自相同类型进行处理
- 各种不同类型,定义不同规则进行操作
1 + "1" = 11
- v8默认会优先以字符串为标准处理
- 转化的方法
- toPrimitive返回原生类型(如数字,或者字符串)
- 依次执行流程 toPrimitive
- -> valueOf -> toString ->报错
- 最终转化为
- Number("1").toString() + "1"
- 先通过 1 转化为"1" 叫做强刷类型转化,也叫隐式转化
处理流程
- 如果输入的值已经是一个原始值,则直接返回它
- 否则,如果输入的值是一个对象,则调用该对象的valueOf()方法, 如果valueOf()方法的返回值是一个原始值,则返回这个原始值。
- 否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。
- 否则,抛出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
下载
其他
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