JS
闭包
1)什么是闭包
如果内部函数持有被外部函数作用域的变量,即形成了闭包。
可以在内部函数访问到外部函数作用域。使用闭包,一可以读取函数中的变量,二可以将函数中的变量存储在内存中,保护变量不被污染。而正因闭包会把函数中的变量值存储在内存中,会对内存有消耗,所以不能滥用闭包,否则会影响网页性能,造成内存泄漏。当不需要使用闭包时,要及时释放内存,可将内层函数对象的变量赋值为null。
2)闭包原理
函数执行分成两个阶段(预编译阶段和执行阶段)。
- 在预编译阶段,如果发现内部函数使用了外部函数的变量,则会在内存中创建一个“闭包”对象并保存对应变量值,如果已存在“闭包”,则只需要增加对应属性值即可。
- 执行完后,函数执行上下文会被销毁,函数对“闭包”对象的引用也会被销毁,但其内部函数还持用该“闭包”的引用,所以内部函数可以继续使用“外部函数”中的变量
利用了函数作用域链的特性,一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中,函数执行完毕,其执行作用域链销毁,但因内部函数的作用域链仍然在引用这个活动对象,所以其活动对象不会被销毁,直到内部函数被销毁后才被销毁。
3)优点
- 可以从内部函数访问外部函数的作用域中的变量,且访问到的变量长期驻扎在内存中,可供之后使用
- 避免变量污染全局
- 把变量存到独立的作用域,作为私有成员存在
4)缺点
- 对内存消耗有负面影响。因内部函数保存了对外部变量的引用,导致无法被垃圾回收,增大内存使用量,所以使用不当会导致内存泄漏
- 对处理速度具有负面影响。闭包的层级决定了引用的外部变量在查找时经过的作用域链长度
- 可能获取到意外的值(captured value)
原型和原型链
原型的概念:每一个javascript对象(除null外)创建的时候,就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型中“继承”属性。
原型链:每个函数都有一个prototype属性,这个属性指向函数的原型对象,每个对象(除null外)都会有的属性,叫做__proto__,这个属性会指向该对象的原型。每个原型对象都有一个constructor属性,指向该关联的构造函数。从而形成一个构造函数,实例对象,原型对象的链式结构,而原型对象也是一个对象,那必然也有__proto__属性,它指向的是Object函数的原型(Object函数作为JS的内置对象,也是充当了很重要的角色。Object函数是所有对象通过原型链追溯到最根的构造函数),Object函数原型对象的__proto__指向null,这就是原型链的终点
数据类型
基本数据类型:string,number,boolean,undefined,null,symbol,bigint
引用数据类型:object
判断数据类型:
typeof
instanceof:主要的实现原理就是只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false,告诉我们左边变量并非是右边变量的实例
Object.prototype.toString.call()
Array.isArray()
this
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的其中一个属性,会在函数执行的过程中用到。(PS:所以this并不等价于执行上下文)
this是在运行时(runtime)进行绑定的,而不是在编写时绑定的,它的上下文(对象)取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
this的绑定规则
-
默认绑定:在不能应用其它绑定规则时使用的默认规则,通常是独立函数调用。指向全局对象
-
隐形绑定:函数的调用是在某个对象上触发的
-
显性绑定:通过call,apply,bind方法绑定this指向
-
new绑定:
1.创建一个空对象,构造函数中的this指向这个空对象
2.这个新对象被执行 [[原型]] 连接
3 执行构造函数,属性和方法被添加到this引用的对象中
4.如果构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象。
5.箭头函数:箭头函数会默认帮我们绑定外层this的值,所以在箭头函数中this的值和外层的this是一样的。
绑定优先级:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
执行上下文
内容:
-
变量对象
-
活动对象
-
作用域链: 作用域 规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做 作用域链。
-
调用者信息(this)
生命周期:
创建阶段
用当前函数的参数列表(arguments)初始化一个 “变量对象” 并将当前执行上下文与之关联 ,函数代码块中声明的 变量 和 函数 将作为属性添加到这个变量对象上。在这一阶段,会进行变量和函数的初始化声明,变量统一定义为 undefined 需要等到赋值时才会有确值,而函数则会直接定义。
构建作用域链
确定 this 的值
执行阶段
执行阶段中,JS 代码开始逐条执行,在这个阶段,JS 引擎开始对定义的变量赋值、开始顺着作用域链访问变量、如果内部有函数调用就创建一个新的执行上下文压入执行栈并把控制权交出……
销毁阶段
一般来讲当函数执行完成后,当前执行上下文(局部环境)会被弹出执行上下文栈并且销毁,控制权被重新交给执行栈上一层的执行上下文。
JS执行机制
同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入 Event Table 并注册函数。当指定的事情完成时,Event Table 会将这个函数移入 Event Queue。主线程内的任务执行完毕为空,会去 Event Queue 读取对应的函数,进入主线程执行。上述过程会不断重复,也就是常说的 Event Loop(事件循环)。
async function async1() {
console.log('async1 start');
await async2();
console.log('asnyc1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeOut');
}, 0);
async1();
new Promise(function (reslove) {
console.log('promise1');
reslove();
}).then(function () {
console.log('promise2');
})
console.log('script end');
- script start
- async1 start
- async2
- promise1
- script end
- asnyc1 end
- promise2
- setTimeOut
浏览器渲染机制
步骤:
-
浏览器将获取的HTML文档解析成DOM树(在这步中,浏览器从开始解析开始就会启用另外一个线程来下载其他的css,js,静态资源等文件,但是如果遇到
-
CSS下载完成后对CSS文件进行解析,解析成CSS对象,然后对CSS对象进行组装,生成CSSOM树。
-
当DOM树和CSSOM树都构建完成后,浏览器根据DOM树和CSSOM树,构建一个渲染树(
rendering tree)代表一系列即将被渲染的对象。 -
浏览器用一种流式处理的办法对渲染树上的每个节点,计算其在屏幕上的位置,这一步称之为布局layout.
-
遍历渲染树,将其绘制到屏幕上。这一步称之为绘制(painting).
CSS
盒子模型
每个盒子由四个部分(或称区域)组成,分别为:内容区域 content area、内边距区域 padding area、边框区域 border area 、外边距区域 margin area,分别对应 width、height、padding、border、margin。
注意 box-sizing 这个 CSS 属性。
content-box(W3C 盒模型)是默认值。如果你设置一个元素的宽为100px,那么这个元素的内容区会有100px宽,并且任何边框和内边距的宽度都会被增加到最后绘制出来的元素宽度中。也就是说,最终盒子的宽高 >= 设置的宽高。border-box(IE 盒模型)告诉浏览器:你想要设置的边框和内边距的值是包含在width内的。也就是说,如果你将一个元素的width设为100px,那么这100px会包含它的border和padding,内容区的实际宽度是width减去(border+padding)的值。大多数情况下,这使得我们更容易地设定一个元素的宽高。
BFC
BFC称为块级格式化上下文,是CSS中的一种渲染机制。是一个拥有独立渲染区域的盒子(也可以理解为结界),规定了内部元素如何布局,并且盒子内部元素与外部元素互不影响。