this指向分析
指向
-
直接调用,指向window
-
通过对象调用,指向对象
-
call/apply
总结:跟位置无关,跟调用方式有关。只有在执行的时候this指向才会被确定
绑定规则:
-
默认绑定
// 独立函数调用,this指向window function foo(){ console.log(this) } foo() // window // 跟位置无关,跟调用方式有关 var obj = { foo:function(){ console.log(this) } } var baz = obj.foo baz() // window // 高阶函数 function foo1(fn){ fn() } foo1(obj.foo) // window // 严格模式下,独立调用的函数中的this指向的是undefined "use strict" var obj = { foo:function(){ console.log(this) } } var baz = obj.foo baz() // undefined
-
隐式绑定
var obj = { foo:function(){ console.log(this) } } obj.foo() // obj
-
显示绑定
-
call(obj,[item1],[item2])
-
apply(obj,[item1,item2])
var obj={ name:'hyf' } funciton foo(){ console.log(this) } foo.call(obj) // obj
-
bind
:创建一个绑定函数BF,怪异函数对象。永久绑定var obj={ name:'hyf' } funciton foo(){ console.log(this) } var f = foo.bind(obj,[item1],[item2]...)
-
-
new
/* 1. 创建一个新的空对象 2. 将this指向这个空对象 3. 执行函数体重的代码 4. 如果函数没有返回其他对象时,默认返回这个对象 */ function Foo(){ console.log(this) } var foo = new Foo() // Foo {}
内置函数的调用绑定
- forEach(fn,this): 默认绑定window, 可以通过第二个参数绑定this
- setTimeout(): this指向window
- el.coclick: this指向 el
优先级
默认绑定 < 隐式绑定 < 显示绑定 < new绑定。new不可以和apply/call一起使用。bind优先级大于call和apply
null/undefined
function Foo(){
console.log(this)
}
Foo.call(null) // window, 忽略显示绑定,使用默认规则
Foo.apply(undefined) // window,忽略显示绑定,使用默认规则
间接函数引用
var obj1 = {
foo:function(){
console.log(this)
}
}
var obj2 = {
name:'hyf'
}
obj2.foo = obj1.foo
obj2.foo() // obj2
(obj2.foo = obj1.foo)() // window
箭头函数
- 不会绑定this、arguments
- 不能作为构造函数
var foo = (arg) => {
// do some thing
}
var obj = (arg) => ({name:33}) // obj = {name:33}
浏览器原理
网页解析过程
- DNS域名解析
- 通过IP地址,与服务器三次握手
- 获取URI资源
- html下载到浏览器中
浏览器渲染过程
-
下载并解析index.html,生成DOM树
-
下载并解析CSS,生成样式规则,CSSOM,CSS对象模型
-
DOM Tree+ CSSOS 生成渲染树,render Tree
-
在Reader Tree上计算节点尺寸和位置等信息,进行布局Layout
-
绘制Paint,将每个frame转为屏幕上的实际的像素点
link元素不会阻塞DOM Tree 但会阻塞Reader Tree构建
Reader Tree 与 DOM Tree不是一一对应的,
回流和重绘
- 回流reflow: 对节点的大小、位置修改重新计算称为回流
- DOM结构发生改变,添加或删除
- 改变布局(width,height,padding,font-size)
- resize(修改了窗口的尺寸)
- 调用getComputedStyle方法获取尺寸、位置信息
- 重绘 repaint: 重新渲染
- 修改背景色,文字颜色,边框颜色,样式
回流一定会引起重绘
-
如何避免回流
-
修改样式时尽量一次性修改
-
尽量避免频繁的操作DOM, DocumentFragment
const list = document.querySelector('#list') const fruits = ['Apple', 'Orange', 'Banana', 'Melon'] const fragment = new DocumentFragment() fruits.forEach((fruit) => { const li = document.createElement('li') li.textContent = fruit fragment.appendChild(li) }) list.appendChild(fragment)
-
尽量避免通过getComputedStyle获取尺寸,位置等信息
-
对某些元素使用position的absolute或者fixed, 开销相对较少
-
合成层
- 默认情况下,标准流中的内容都是被绘制在同一个图层中的
- 创建新的合成层
- 3D transforms
- video、canvas、iframe
- opacity动画转换时
- position: fixed
- will-change:一个实验性的属性,提前告诉浏览器元素可能发生哪些变化
- animation或transition设置了opcity、transform
script元素与页面解析
遇到script,会停止DOM解析,先加载执行script脚本
- defer属性:
- 不会阻塞DOM Tree构建过程。
- 在DOMContentLoaded之前执行
- 多个脚本顺序执行
- 建议放到head中,让浏览器先加载
- async属性:
- 不阻塞页面
- 脚本完全独立
- 不能保证多个脚本顺序
- 不能保证在DOMContentLoaded之前或之后执行
JS原理
Webkit = WebCore + JavaScriptCore
V8引擎原理
V8引擎是由C++编写的Google开源高性能JavaScript和WebAssembly引擎,它用于Chrome和Node.js等,V8可以独立运行,也可以嵌入到任何C++应用程序中
- Parse模块,会将JS代码转为AST(抽象语法树),解释器并不认识JS代码。如果函数没有被调用是不会被转为AST树的
- lgnition是一个解释器,会将AST转为ByteCode(字节码),同时会搜集TurboFan优化所需的信息(如函数参数类型信息),如果函数只调用一次,lgnition会解释执行ByteCode
- TurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码
- 如果一个函数被调用多次,那么会被标记为热点函数,就会经过TurboFan转换为优化的机器码,提高代码的执行性能
- 但是,机器码实际上也会被还原为ByteCode,这是因为函数在执行中传入的类型改变,之前优化的机器码并不能准确的处理,就会逆转为字节码
JS执行上下文
整体执行流程
-
js代码执行之前,初始化全局对象Global Object(GO)
-
该对象在堆内存中创建,所有作用域都可访问
-
包含
Date、Array、String、Number、setTimeout、setInterval
等 -
还有
window
指向自己
-
-
每一个执行上下文都会关联一个VO(Variable Object,变量对象),变量和函数声明都会被添加到这个VO对象中
-
全局代码被执行的时候,VO就是GO对象
-
AO对象:函数执行上下文,会创建一个AO(Activation Object)
- 这个AO会使用
arguments
作为初始化,并且初始值是传入的参数 - 这个AO对象会作为执行上下文的VO来存放变量的初始化
- 这个AO会使用
var message = 'aaa'
function bar(){
var message = 'bb'
}
var num1= 0
var num2= 1
var result = num1+num2
函数会被先创建
函数代码的多次执行
函数在第一次执行完成以后,会被栈移除,AO对象的移除需要看情况(垃圾回收)
作用域和作用域链
函数在创建的时候,作用域链就被确定了,跟调用位置无关
作用域链综述:
-
首先,在代码执行之前,创建GO(Global Object)--VO(Variable Object)对象
在GO对象中 message = undefined foo = 0x001(指向一个函数对象 Function Object),这个函数对象中包含一些属性,arguments/name/length/[[scopes]]... [[scopes]] 中包含0:Global Object bar = undefined test = undefined
-
代码执行,
运行 var message = 'global message' 时,GO对象的message被赋值为'global message' 调用 foo() 时,运行 foo函数代码,此时创建 foo VO(Variable Object)即AO(Activation Object) foo AO中 name = undefined , bar = 0x002(指向一个函数对象 Function Object) 这个函数对象中包含一些属性,arguments/name/length/[[scopes]]... [[scopes]] 包含 0:Global Object 返回 bar函数的内存地址 0x002 将0x002赋值给bar变量 此时GO中的bar = 0x002
-
代码执行bar()
调用函数bar(),运行0x002中的代码,创建test=0x003 函数(Function Object)arguments/name/length/[[scopes]]... [[scopes]] 中包含0:foo AO, 1:Global Object 打印name, 寻找name顺序 0--->1 返回0x003 将0x003赋值给GO中test变量
面试题
// 1.
var n = 100
function foo(){
n = 200
}
foo()
console.log(n) // 200
// 2
var n = 100
function foo(){
console.log(n) // undefined
var n = 200
console.log(n) // 200
}
foo()
// 3
var n = 100
function foo1(){
console.log(n)
}
function foo2(){
var n = 200
console.log(n) // 200
foo1()
}
foo2()
/*
分析:
1. 创建GO,n=undefined,foo1=0x001(arguments/name/[[scopes]]:{"0":GO}),foo2=0x002(arguments/name/[[scopes]]:{"0":AO,"1":GO}) AO 包含 n = undefined
2. 调用foo2() 执行foo2代码体中的var n = 200, foo2 AO n = 200,打印200,调用foo1()
3. foo1执行代码,打印n,n的作用域只用GO,所以打印n为100
*/
// 4
var a = 100
funciton foo(){
console.log(a) // undefined
return
var a = 100
}
foo()
// 5
function foo(){
var a = b = 100
}
foo()
console.log(a) // c is not defined
console.log(b) // 100 , 相当于 var a = 100; b = 100
内存管理
内存的生命周期
- 分配申请你所需的内存
- 使用分配的内存(存放变量,对象等)
- 不需要使用时,对其进行释放
JS内存分配
- JS对原始数据类型的内存分配直接在栈空间进行分配
- JS对复杂数据类型内存的分配会在堆内存中开辟空间,将空间指针地址返回给变量引用
垃圾回收机制 GC(Garbage Collection)
对于那些不再使用的对象,称之为垃圾,需要被回收。Lisp最先提出
引用计数 (Reference counting)
-
概念
- 一个对象有一个引用指向它时,对象的引用就+1
- 当一个对象的引用为0时,这个对象就可以被销毁
-
缺陷
-
循环引用
obj1 = {} obj2 = {} obj1.info = obj2 obj2.info = obj1
-
标记清除 (mark-Sweep)
- 概念
- 核心思路:可达性
- 设置一个根对象,定期从根对象开始,找所有从根开始有引用到的对象,对于那些没有引用到的对象,就认为是不可用的对象
- 可以很好的解决循环引用的问题
标记整理
- 与标记清除类似
- 不同的是,回收期间将保留的存储对象搬运汇集到连续的内存空间,从而整合空闲空间,避免内存碎片化
分代回收
- 将内存中对象分为新生代、老生代
- 新创建的对象,都放到新生代,使用结束以后,GC清除垃圾,经过多次回收,还剩下的对象放到老生代中
- 老生代的检查频率是很低的。
增量收集
- 如果有很多对象,并且我们试图一次遍历并标记整个对象集,则可能需要一些时间,并在执行过程中带来明显的延迟
- 所以引擎试图将垃圾收集工作分为几部分来做,然后将这几部分逐一处理,这样会有许多微小的延迟而不是一个很大的延迟
闲时收集
- GC只会在CPU空闲时尝试运行,以减少可能对代码执行的影响
闭包
- 最早出现在Scheme
- 如果一个函数,能够访问外层作用域中的变量,那么这个函数和周围环境就是一个闭包
- 所以,JS中的函数都可以称为闭包函数,因为每当创建一个函数时,这个函数都可以访问外层,如GO中的变量
function createAdder(con){
function adder(num){
return con+num
}
return adder
}
var adder5 = createAdder(5) // con固定为5 addr函数中num+5
adder5(10) // 15 5+10
var adder10 = createAdder(10) // con固定为10 addr函数中num+5
adder10(10) // 20 10+10
内存泄漏与释放
- 对于某些内存不再使用,需要手动进行释放
add10 = null
内存优化
- AO不使用的属性会被浏览器优化