前言
文中主要介绍浏览器运行html过程、BFC机制、原型链于与js生产工厂、执行上下文以及相关的代码示例等。
html构建相关知识
DOM和BOM
DOM
文档对象模型 (DOM) 是HTML和XML文档的编程接口。它提供了对文档的结构化的表述,并定义了一种方式可以使从程序中对该结构进行访问,从而改变文档的结构,样式和内容。DOM 将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合。简言之,它会将web页面和脚本或程序语言连接起来。
简单来理解就是浏览器不仅提供很多方法,可以改变html的布局、样式和文本,并且将html文档解析成节点。也可以说是文档构建成对象之后的模型。
可操作DOM常用API
您在DOM编程时,通常使用的最多的就是 Document 和 window 对象。简单的说, window 对象表示浏览器中的内容,而 document 对象是文档本身的根节点。
document.getElementById(id)document.getElementsByTagName(name)document.querySelector(class)document.createElement(name)parentNode.appendChild(node)element.innerHTMLelement.style.leftelement.setAttribute()element.getAttribute()element.addEventListener()
BOM
JS 浏览器对象模型(Browser Object Model, BOM)被广泛应用于 Web 开发之中,主要用于客户端浏览器的管理。
BOM API
window 是 JS 的最顶层对象,其他的 BOM 对象都是 window 对象的属性document 文档对象location 浏览器当前URL信息navigator 浏览器本身信息screen 客户端屏幕信息history 浏览器访问历史信息
DOM和BOM的联系
DOM的根本对象是document,而document是window下的对象。document对象既属于BOM又属于DOM,BOM是浏览器相关,DOM是文档相关,他们之间是存在交集的。其实也就是说相关文档调用的api是隶属BOM的。
HTML构成元素和几个重要的标签
构成元素
html构成元素可分为三类
- 行级元素:行内元素不能独占一行,与其他行内元素排成一行。不能设置宽高等
- 块级元素:块级元素独占一行,当没有设置宽高时,它默认设置为100%。可以设置宽高等
- 行内块元素:能设置宽高等,可以和其他元素同行 其实除去几个特殊标签,使用div和span就可以实现html大部分页面
特殊元素
有几个特殊块级元素只能包含内联元素或者可变元素,不能包含块级元素。这几个特殊标签是 h1~h6、p、dt。
DOCTYPE(⽂档类型)
DOCTYPE是HTML5中一种标准通用标记语言的文档类型声明,它的目的是告诉浏览器(解析器)应该以什么样(html或xhtml)的文档类型定义来解析文档,不同的渲染模式会影响浏览器对 CSS 代码甚⾄ JavaScript 脚本的解析。它必须声明在HTML⽂档的第⼀⾏。
meta元信息
meta 标签由 name和 content属性定义,用来描述网页文档的属性,比如网页的作者,网页描述,关键词等,除了HTTP标准固定了一些name作为大家使用的共识,开发者还可以自定义name。
meta 元素定义的元数据的类型包括以下几种:
- 如果设置了
name属性,meta元素提供的是文档级别(document-level)的元数据,应用于整个页面。 - 如果设置了
http-equiv属性,meta元素则是编译指令,提供的信息与类似命名的HTTP头部相同。 - 如果设置了
charset属性,meta元素是一个字符集声明,告诉文档使用哪种字符编码。 - 如果设置了
itemprop属性,meta元素提供用户定义的元数据。 其中name=viewport 可用于移动端的适配,其content可配置其类型 其中,content参数有以下几种:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">\
width viewport:宽度height viewport:高度initial-scale:初始缩放比例maximum-scale:最大缩放比例minimum-scale:最小缩放比例user-scalable:是否允许用户缩放
DTD规范
文档类型定义(DTD)可定义合法的XML文档构建模块。它使用一系列合法的元素来定义文档的结构。 DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用。
渲染机制和阻塞
浏览器渲染网页的过程:
- 构建DOM树:词法分析然后解析成DOM树(dom tree),是由dom元素及属性节点组成,树的根是document对象
- 构建CSS规则树:生成CSS规则树(CSS Rule Tree)
- 构建render树:Web浏览器将DOM和CSSOM结合,并构建出渲染树(render tree)
- 布局(Layout):计算出每个节点在屏幕中的位置
- 绘制(Painting):即遍历render树,并使用UI后端层绘制每个节点
阻塞
图中除了异步步骤,同步的步骤任一步未执行,后面的步骤都将会被阻塞。比如说在css未加载完之前,在浏览器里面是查不到相关dom节点的。
css相关
link和@import的区别
- 二个都是外部引用文件的方式,link除了可以加载css文件,还可以定义rss等事务时间,@impoert只能加载css。
- link引用时,页面加载css同时加载。@import网页完全加载完后加载。
- @import底部本不支持
Webkit CSS 引擎分析
一些自动生成css3动画的网站
物理像素逻辑像素和css像素
- 物理像素:设备像素(物理像素),顾名思义,显示屏是由一个个物理像素点组成的,通过控制每个像素点的颜色,使屏幕显示出不同的图像,屏幕从工厂出来那天起,它上面的物理像素点就固定不变了,单位pt。
- 逻辑像素:也叫“设备独立像素”(Device Independent Pixel,DIP),可以理解为反映在CSS/JS程序里面的像素点,也就是说css像素是逻辑像素的一种。
- css像素:CSS像素是Web编程的概念,指的是CSS样式代码中使用的逻辑像素。在CSS规范中,长度单位可以分为两类,绝对(absolute)单位以及相对(relative)单位。px是一个相对单位,相对的是设备像素(device pixel)。
- 设备像素比dp:dpr = 物理像素/逻辑像素
适配大屏和手机端的样式方案
大屏
- 设计二套样式,一个用于大屏。一个用于普通屏幕。这样不用考虑每个屏幕的字体、美观、溢出等问题,但缺点也很明显,扩展性不强,如果换一个场景,又得重新设配样式。
- 使用room对屏幕进行等比例的缩放,但前提是图表字体使用百分比。但这样缩放会引起,节点点击位置错位等问题。如果只是语音操作可以考虑这种方式。
- 使用@medio做多套方案,其实这个和第一种方案大同小异。
手机端
- 其实安卓端大部分的样式只要布局设计好,基本不用做适配。主要是对弹出键盘、特殊边(苹果)、缩放、不同设备等做兼容。
BFC机制
BFC(Block Formatting Context)即是盒模型格式化上下文,是Web页面中盒模型布局的CSS渲染模式。
css定位机制
普通流
除非专门指定,否则所有框都在普通流中定位。普通流中元素框的位置由元素在(X)HTML中的位置决定。块级元素从上到下依次排列,框之间的垂直距离由框的垂直margin计算得到。行内元素在一行中水平布置。
浮动
盒称为浮动盒(floating boxes); 它位于当前行的开头或末尾;这导致常规流环绕在它的周边,除非设置 clear 属性;
定位
相对定位、绝对定位都脱离了常规流,不会影响常规流的布局。所以它可以覆盖页面上的其他元素,可以通过设置Z-Iindex属性来控制这些框的堆放次序。
创建BFC
- 绝对定位定位值为absolute或者fixed
- 浮动,除去float:none
- 设置了overflow,除去overflow:visible
- 行内块元素
- 表格单元格,元素的display: table-cell,HTML表格单元格默认属性
- 弹性布局,display值为flex活着liline-flex
- body元素,以及body的父元素
BFC的范围
一个BFC包含创建该上下文元素的所有子元素,但不包括创建了新BFC的子元素的内部元素。
BFC的特性
- 内部的盒会在垂直方向一个接一个排列(可以看作BFC中有一个的常规流);
- 处于同一个BFC中的元素相互影响,可能会发生margin collapse;
- 每个元素的margin box的左边,与容器块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此;
- BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素,反之亦然;
- 计算BFC的高度时,考虑BFC所包含的所有元素,连浮动元素也参与计算;
- 浮动盒区域不叠加到BFC上;
BFC的例子
例子1:解决margin塌陷和margin合并
<style>
.demo{
overflow: hidden;
}
.demo div{
height: 50px;
border: 1px solid gold;
margin: 20px;
overflow: hidden;
}
.demo>div:first-child{
margin: 50px;
}
</style>
<body>
<div class="demo">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
</div>
</body>
在demo元素不加overflow属性时,你会发现此时demo的margin-top不见了,而且神奇的是demo外边距加到body上。这是由于margin塌陷和margin合并。
margin塌陷现象:在垂直方向如果有两个元素的外边距有相遇,在浏览器中加载的真正的外边距不是两个间距的加和,而是两个边距中值比较大的,边距小的塌陷到了边距值大的值内部。
此时demo的外上边距为8,body外上边距为42px。
加上属性后,此时demo距离浏览器顶部成58px,且与body间隔50px。其实这是由于demo创建了一个BFC,而body也有一个BFC。此时二个元素间相互隔离,避免了margin塌陷和margin合并
例子2:融合高度、隐藏溢出
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
border: 1px solid yellow;
width: 250px;
overflow: hidden;
}
.block {
height: 100px;
width: 200px;
opacity: 0.3;
background: black;
}
.other {
height: 90px;
width: 50px;
float: left;
background: blue;
}
</style>
</head>
<body>
<div class="box">
<div class="block"></div>
<div class="other">
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
</div>
</div>
</body>
在box未有bfc机制前,元素会溢出并且box不会包含所有子元素的高度。
有bfc机制后,元素相互隔离,计算高度是会把浮动元素也计算进去。
JS相关内容
原型链
从原型工厂到生产各类型的对象
原型链
JavaScript 只有一种结构:对象。每个实例对象(object)都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象prototype。该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。
另外隐式原型和显式原型都是协助构成原型链的。protoType关联函数而__proto__ 关联对象。我想分为显式和隐式的原因应该是prototype为最终指向的实例,而__proto__为协助指向。
Object实例
在JavaScript中,几乎所有的对象都是Object类型的实例,它们都会从Object.prototype继承属性和方法。Object 构造函数为给定值创建一个对象包装器。Object构造函数,会根据给定的参数创建对象。
解图
这张图不知出处,但在我看过的大多数文章中都出现了,基本没有把它说清楚的。总是附图一张,然后自说自的。我想看了这多遍,原型链。除了官方的解释让人醍醐灌顶,其他的都讲的模糊不清。
首先要看明白这个图要把原型链和对象实例的概念了解清楚。
-
图中分为三列:
- 第一列:实际应用中的对象或者函数,如let f1 = new Foo();中的f1;let o1 = new Object()中的o1;(即是工厂产生的最终产品)
- 第二列: 对象的构造函数,函数的构造函数。(工厂的加工机器)
- 第三列:构造函数的原型。(工厂最终原材料)。而在第三列中所有材料的最终材料是Object实例,Object实例之上则为空
-
生产函数的产品线:
-
从f1往下看。f1的隐式原型指向f1构造函数(Foo)的原型对象(Foo.protoType)
-
Foo.protoType因是对象,所以拥有私有属性
__proto__,它的__proto__指向对象实例(Object.protoType) -
而实例对象的尽头
Object.protoType.__proto__则为null,null没有原型
-
-
生产构造函数Foo的产品线:
- Foo的隐式原型指向,Function构造函数的原型。
- Function.protoType指向对象实例
- Function是成产function的机器,即为所有的函数的构造函数。
-
生产对象的产品线:o1的隐式原型指向其构造函数Object的原型(即指向原型终点)。但是Object()的隐式原型是指向Function的。看图中的关系,Object()、Function.protoType、Object.protoType是一个闭环。Object.protoType为其圈的终点,先有Object.protoType,再有Function.protoType,再有Object()。
-
梳理一遍逻辑,Object.protoType是神(神之上是一片虚无),神下面有皇帝(Function.protoType)负责所有事务,皇帝有二个大臣(二个构造函数Object()和Function()),分别负责生产对象和函数,从此构建了整个js王朝根基。
具体应用
例1:属性查找
// Object.prototype.hasOwnProperty()
console.log(obj.hasOwnProperty('son'));//true
例2: instanceof && typeof
function instanceof(L, R) {
let O = L.__proto__
const P = R.prototype
while(O) {
if (O === P) return true
O = O.__proto__
}
return false
}
function typeof(value) {
return Object.prototype.toString.call(value).slice(8, -1)
}
实现new()
function _new(constructor, ...arg) {
var obj = {};
obj.__proto__ = constructor.prototype;
var res = constructor.apply(obj, arg);
return Object.prototype.toString.call(res) === '[object Object]' ? res : obj;
}
迭代协议
作为 ECMAScript 2015 的一组补充规范,迭代协议并不是新的内置实现或语法,而是协议。这些协议可以被任何遵循某些约定的对象来实现。迭代协议具体分为两个协议:可迭代协议和迭代器协议。
可迭代协议
可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为,例如,在一个 for..of 结构中,哪些值可以被遍历到。一些内置类型同时是内置可迭代对象,并且有默认的迭代行为,比如 Array 或者 Map,而其他内置类型则不是(比如 Object))。
要成为可迭代对象, 一个对象必须实现 @@iterator 方法。这意味着对象(或者它原型链上的某个对象)必须有一个键为 @@iterator 的属性,可通过常量 Symbol.iterator 访问该属性。
当一个对象需要被迭代的时候(比如被置入一个 for...of 循环时),首先,会不带参数调用它的 @@iterator 方法,然后使用此方法返回的迭代器获得要迭代的值。
值得注意的是调用此零个参数函数时,它将作为对可迭代对象的方法进行调用。 因此,在函数内部,this关键字可用于访问可迭代对象的属性,以决定在迭代过程中提供什么。
此函数可以是普通函数,也可以是生成器函数,以便在调用时返回迭代器对象。 在此生成器函数的内部,可以使用yield提供每个条目。
迭代器协议
迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式。当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值。
只有实现了一个拥有以下语义(semantic)的 next() 方法,一个对象才能成为迭代器:
- 属性: next
- 值:一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象:
done(boolean)如果迭代器可以产生序列中的下一个值,则为false。(这等价于没有指定done这个属性。)如果迭代器已将序列迭代完毕,则为true。这种情况下,value是可选的,如果它依然存在,即为迭代结束之后的默认返回值。value迭代器返回的任何 JavaScript 值。done 为 true 时可省略。next()方法必须返回一个对象,该对象应当有两个属性:done和value,如果返回了一个非对象值(比如false或undefined),则会抛出一个TypeError异常
定义一个简单的迭代器
let iterator = function (array) {
let index = 0;
return {
next: function() {
return index < array.length ? {
value: array[index++],
done: false
}: {
done: true
}
},
done: false
}
}
let p = new iterator([1,2,3,4])
p.next() //{value: 1, done: false}
p.next() //{value: 2, done: false}
p.next() //{value: 3, done: false}
p.next() //{value: 4, done: false}
p.next() //{done: true}
定义总结
可迭代协议是用于内置是内置可迭代对象,并且有默认的迭代行为的值,比如array、map;而迭代器协议一般用于普通对象。只有拥有协议二者之一的对象都是可被迭代的。String、Array、TypedArray、Map和Set都是内置迭代器的。
for...in、for...of、forEach
for in遍历的是元素的索引(即键名),而for of遍历的是元素的value
for...in
- 在遍历对象时会出现乱序的现象,因为对象的值以hash值的方式去存储的,当你去遍历读取对象的值时,不会按你定义对象时的上下顺序去读取,一般有数字先排列数字,在排列顺序排列其他,所以遍历时不能保证顺序。
- for in 会遍历数组所有的可枚举属性,包括原型。
- 遍历原型:
let obj = {
b: 1,
12: 2,
1: 3
}
obj.__proto__.a = 4
for(let i in obj){
console.log(i) // 1,12,a,b
}
for...of
- 内部使用迭代器实现,只要对象可被迭代(对象拥有方法Symbol.iterator),即可被迭代。
- for of不会迭代原型
- 简单实现对象迭代器:
let obj = {
b: 1,
12: 2,
1: 3
}
obj[Symbol.iterator] = function () {
console.log(this)
let array = Object.keys(this)
let index = 0
return {
next: function () {
return index < array.length ? {
value: array[index++],
done: false
} : {
done: true
}
},
done: false
}
}
obj.__proto__.a = 4
for (let i of obj) {
console.log(i) // 1,12,a
}
forEach
foeEach其实是比较特殊的。不同于其他,它是不能跳出循环的。
- 下面简单实现一下forEach代码
let arr = [1, 2, 3, 4, 5, 6]
Array.prototype.forEach = function (callback, thisArg) {
var len = this.length;
for (var i = 0; i < len; i++) {
// 可以让callback根据返回值判断是否返回
// if(callback() === 'break') break
callback.call(thisArg, this[i] + '--', i, this)
}
}
arr.forEach(e => {
console.log(e) //1--,2--......
// throw new Error("StopIteration") 也可使用throw强制跳出循环
})
闭包
描述
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
应用
这些代码都是找的例子,今天没时间了,拖了很久了。先发布吧。
例1:返回一个函数
function test() {
var n = 0
function f() {
n++;
console.log(n)
}
return f
}
let fn = test()
fn()
例2:循环赋值
for (var i = 0; i < 10; i++) {
(function (j) {
setTimeout(function () {
console.log(j)
}, 1000)
})(i)
}
例3:实现柯里化
function curry(fn, len = fn.length) {
return _curry(fn, len)
}
function _curry(fn, len, ...arg) {
return function (...params) {
let _arg = [...arg, ...params]
if (_arg.length >= len) {
return fn.apply(this, _arg)
} else {
return _curry.call(this, fn, len, ..._arg)
}
}
}
let fn = curry(function (a, b, c, d, e) {
console.log(a + b + c + d + e)
})
fn(1, 2, 3, 4, 5) // 15
fn(1, 2)(3, 4, 5)
fn(1, 2)(3)(4)(5)
fn(1)(2)(3)(4)(5)
例4:防抖节流
// 节流
function throttle(fn, timeout) {
let timer = null
return function (...arg) {
if (timer) return
timer = setTimeout(() => {
fn.apply(this, arg)
timer = null
}, timeout)
}
}
// 防抖
function debounce(fn, timeout) {
let timer = null
return function (...arg) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, arg)
}, timeout)
}
}
事件循环与primise
描述
JavaScript有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。这个模型与其它语言中的模型截然不同,比如 C 和 Java。在往期中介绍过了。 juejin.cn/post/698911…
上下文环境
执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。执行上下文 为我们的可执行代码块提供了执行前的必要准备工作,例如变量对象的定义、作用域链的扩展、提供调用者的对象引用等信息。
执行上下文的三种环境
- 全局执行上下文:这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置
this的值等于这个全局对象。一个程序中只会有一个全局执行上下文 - 函数执行上下文:每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤
- eval函数执行上下文:执行在
eval函数内部的代码也会有它属于自己的执行上下文
执行上下文内容
- 变量对象:全局上下文中的变量对象就是全局对象,以浏览器环境来说,就是
window对象。函数执行上下文中的变量对象内部定义的属性,是不能被直接访问的,只有当函数被调用时,变量对象(VO)被激活为活动对象(AO)时,我们才能访问到其中的属性和方法。所以调用函数时this总是指向调用者。 - 活动对象:函数进入执行阶段时,原本不能访问的变量对象被激活成为一个活动对象,自此,我们可以访问到其中的各种属性。
- 作用域链:作用域 规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
- 调用者信息:如果当前函数被作为对象方法调用或使用
bindcallapply等API进行委托调用,则将当前代码块的调用者信息(this value)存入当前执行上下文,否则默认为全局对象调用。
ES5 执行上下文过程
-
程序启动,全局上下文被创建
-
创建全局上下文的 词法环境
- 创建 对象环境记录器 ,它用来定义出现在 全局上下文 中的变量和函数的关系(负责处理
let和const定义的变量) - 创建 外部环境引用,值为
null
- 创建 对象环境记录器 ,它用来定义出现在 全局上下文 中的变量和函数的关系(负责处理
-
创建全局上下文的 变量环境
- 创建 对象环境记录器,它持有 变量声明语句 在执行上下文中创建的绑定关系(负责处理
var定义的变量,初始值为undefined造成声明提升) - 创建 外部环境引用,值为
null
- 创建 对象环境记录器,它持有 变量声明语句 在执行上下文中创建的绑定关系(负责处理
-
确定
this值为全局对象(以浏览器为例,就是window)
-
-
函数被调用,函数上下文被创建
-
创建函数上下文的 词法环境
- 创建 声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的
arguments对象(此对象存储索引和参数的映射)和传递给函数的参数的 length。(负责处理let和const定义的变量) - 创建 外部环境引用,值为全局对象,或者为父级词法环境(作用域)
- 创建 声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的
-
创建函数上下文的 变量环境
- 创建 声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的
arguments对象(此对象存储索引和参数的映射)和传递给函数的参数的 length。(负责处理var定义的变量,初始值为undefined造成声明提升) - 创建 外部环境引用,值为全局对象,或者为父级词法环境(作用域)
- 创建 声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的
-
确定
this值
-
-
进入函数执行上下文的执行阶段:
- 在上下文中运行/解释函数代码,并在代码逐行执行时分配变量值。
执行上下文栈
当一段脚本运行起来的时候,可能会调用很多函数并产生很多函数执行上下文,那么问题来了,这些执行上下文该怎么管理呢?为了解决这个问题,javascript 引擎就创建了 “执行上下文栈” (Execution context stack 简称 ECS)来管理执行上下文。
顾名思义,执行上下文栈是栈结构的,因此遵循 LIFO(后进先出)的特性,代码执行期间创建的所有执行上下文,都会交给执行上下文栈进行管理。
当 JS 引擎开始解析脚本代码时,会首先创建一个全局执行上下文,压入栈底(这个全局执行上下文从创建一直到程序销毁,都会存在于栈的底部)。
每当引擎发现一处函数调用,就会创建一个新的函数执行上下文压入栈内,并将控制权交给该上下文,待函数执行完成后,即将该执行上下文从栈内弹出销毁,将控制权重新给到栈内上一个执行上下文。
例子
// 例1
var foo = function () {
console.log('foo1');
}
foo();
var foo = function () {
console.log('foo2');
}
foo();
// 结果:fool、fool2
// 解析:变量声明时foo全部赋值为undefined,代码执行时,从上往下执行。
// 例2
foo();
var foo = function foo() {
console.log('foo1');
}
function foo() {
console.log('foo2');
}
foo();
// 结果:foo2、fool1
// 解析:函数变量和变量声明是同步的,且同名发送函数声明提升,此事一foo()打印fool2
然后开始执行代码,foo的变量被赋予一个函数,后面的function不会再次执行。
// 例3
var foo = 1;
function bar () {
console.log(foo);
var foo = 10;
console.log(foo);
}
bar();
// 结果:undefined、10
// 解析:函数内存在变量,先找函数作用域。
// 例4
ar a = 1;
function foo () {
var a = 2;
return function () {
console.log(this.a);
}
}
var bar = foo().bind(this);
bar();
下期内容
- webpack相关
- ts相关
参考
developer.mozilla.org/zh-CN/
juejin.cn/post/684490…
juejin.cn/post/693746…