浏览器的内核是指支持浏览器运行的最核心的程序,分为2个部分,一是渲染引擎,另一个是js引擎,
渲染引擎在不同的浏览器中也是不同的,目前市面上常见的浏览器内核有Trident(IE),Gecko(火狐,
Blink(Chrome),Webkit(Safari),
webkit是当下浏览器世界真正的霸主
页面加载过程:
浏览器根据dns服务器得到域名的ip地址
向这个ip的机器发送http请求
服务器收到,处理并返回http请求
浏览器得到返回内容
一堆HTML格式的字符串,因为只有HTML格式浏览器才能正确解析,
HTML树 -> DOM树形结构
CSS -> CSS规则树
js脚本,等到js脚本文件加载后,通过dom api和cssom api来操作dom树和css规则树
解析完成后,浏览器引擎会通过DOM Tree 和 CSS Rule Tree 来构造render tree,渲染树。
渲染树会包括需要显示的节点和这些节点的样式信息。
css的规则树主要为了完成匹配吧css规则添加到渲染树上的每个节点
最后,计算每个frame的位置,这又叫layout和reflow过程。
字节数据 => 字符串 => token => node => dom
在网络中传输的内容其实都是 0 和 1 这些字节数据。当浏览器接收到这些字节数据以后,它会将这些字节数据转换为字符串,也就是我们写的代码。
将字符串转化为token,例如,
渲染树只会包括需要显示的节点和这些节点的样式信息
渲染过程中,如果遇到script就停止渲染,执行js代码,因为浏览器有GUI渲染线程和JS引擎线程。为了防止渲染出现不同的结果,这两个线程是互斥的关系,JavaScript的加载、解析与执行会阻塞DOM的构建,也就是说,在构建DOM时,HTML解析器若遇到了JavaScript,那么它会暂停构建DOM,将控制权移交给JavaScript引擎,等JavaScript引擎运行完毕,浏览器再从中断的地方恢复DOM构建。
首屏渲染的越快的话,就越不应该在首屏就加载js文件,这也是将js放在body底部的原因。
js不仅阻塞dom的构建,也会导致cssom和dom的构建。
因为js不仅可以修改DOM,也可以修改样式。
因为js需要修改cssom.所以浏览器将延迟脚本执行和dom构建,直至完成cssom的下载和构建,
在这种情况下,浏览器会先下载和构建CSSOM,然后再执行JavaScript,最后在继续构建DOM
根据渲染树进行布局,也可以叫做回流,
输出一个精准的盒模型,每个元素在视口内的准确位置。
重绘:当我们对dom的修改导致了样式的变化,却并未影响到几何属性,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式
回流:当我们对dom的修改引发了dom几何尺寸的变化,浏览器会重新计算,这个过程就是回流。
回流一定会引发重绘,但重绘不一定引发回流.
减少重绘或者回流:
使用visibility替换display:none.因为前者会引发重绘。
不要使用table布局。
动画速度越快,回流次数越多
defer 使脚本在文档解析完成后执行。不会影响html的解析。
async:用于异步下载文件,下载完后立即解释执行。不会阻塞html的执行
模块化概念
将一个复杂的程序依据一定的规则封装成几个块(文件),并进行组合在一起
块的内部数据与实现是私有的,只是向外部暴露一些接口(方法),与外部其他模块通信。
模块化的好处
避免命名冲突(减少命名空间污染)
更好的分离,按需加载
更高复用性
高可维护性
引入多个script后出现的问题
请求过多,会发送多个请求。依赖模糊,难以维护
可能出现牵一发而动全身的情况。
模块化规范:commonjs,AMD,ES6,CMD规范
commonjs node应用由模块组成,采用commonjs模块规范,
每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。
暴露模块 module代表当前模块1
2module.exports = value;
exports.xxx = value;
引入模块1
require(xxx);
commonjs规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作
amd规范则是非同步的,允许指定回调函数,由于node js主要用于服务端编程,,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,
浏览器环境,要从服务器端加载模块,这时必须采用非同步模式,这时AMD就比较实适用。1
2
3define['modlue1','module2'],function(m1,m2){
return 模块;
};
引入使用模块1
2
3require('module1','module2',function(m1,m1){
});
requurejs是一个工具库,,主要用于客户端的模块管理。它的模块管理遵守AMD规范,
基本思想:通过define,将代码定义为模块,通过require,实现代码的模块加载。
CMD规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。
CMD整合了commonjs和amd规范的特点,在sea.js中,
通过define配置文件,通过requre导入文件。
AMD和CMD都适用于浏览器端,AMD同步,CMD异步。
ES6的模块化语法
export{basicNum,add};
import{add} from ‘./main’;
commonjs提出的是一个值的拷贝,而es6是值的引用。
commonjs是模块运行时加载,es6模块是**编译时输出的接口。
类型转换
js是一门动态类型语言,所谓的动态类型可以确定是在语言中一切内容是不确定的
如果运算符发现,运算子的类型与预期不符合,就会自动进行转换。
自动转换时基于强制转换的,
强制转换主要指使用Number,String,Boolean.3个函数
其他数据类型转换为string
toString()方法
null和undefined这2个值没有toString方法,如果调用,会报错
采用number类型的toString()方法的基模式,可以用不同的基输出数字,
例如二进制基为2,八进制为81
2var num = 10;
alert(num.toString(2)); //输出1010
方式二 采用string函数
string函数作强制转换,1
2
3var a = null;
String(a);//为字符串"null"
String({a:1});//转化为[object Object]
转为数字
如果字符串中有非数字的内容,则转为NaN,1
2
3
4Number("324");//324
Number("324anc");//NaN
Number(undefined);//转为NaN
Number({a:1}) //转化为NaN
parseInt()把一个字符串转为整数,有效体转为整数1
parseInt(10.43a") //为10
parseFloat()把一个字符串转为浮点数。
空字符串,null,undefined,+0,-0,和NaN转为布尔型为false.
作用域
作用域是一个独立的地盘,让变量不会外泄,暴露出去,作用域最大的好处是隔离变量,不同作用域下的同名变量不会冲突。
es6之间只有全局作用域和函数作用域。
es6提供了块级作用域。
let和const的声明并不会提升到当前代码块的顶部,因此需要手动提升。
let不能重复声明1
2
3
4
5
6var count = 30;
function outFun2() {
let count = 20;
console.log(count);
}
以上这种情况 在嵌套的作用域内使用let声明一个同名的新变量,
并不会抛出错误。1
2var cnt = 30;
let cnt = 40;
作用域链,更确切的说是在创建这个函数的时候的作用域,而不是调用时候的
以上这种情况便会报错。
js的执行分为解释阶段和执行阶段
解释阶段:词法分析,语法分析,作用域规则确定
执行阶段:创建执行上下文,执行函数代码,垃圾回收
执行上下文最明显的就是this的指向是在执行时候确定的。
js事件循环机制
js是单线程任务,所有任务都需要排队,前一个任务结束,后一个任务才会接上,js中有2种任务,一种是同步任务,一种是异步任务,
异步任务指的是,不进入主线程,而进入消息队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程,异步任务又包括宏任务和微任务,
在所有同步任务执行完之前,异步任务是不会执行的.
event loop,只要主线程空了,就会去读取”任务队列”内的东西。
放入异步任务的代码:setTimeout,DOM事件,promise,ajax请求。
setTimeout和setInterval是过几秒后被放入异步队列
异步任务分为宏任务和微任务,宏任务队列可以有多个,而微任务队列只有一个
宏任务包括:script全局任务,setTimeout,setInterval, setImmediate, I/O, UI rendering。
微任务包括:new Promise().then,
由于执行代码入口都是全局任务scrpit,而全局任务是属于宏任务,所以当栈为空的时候,首先执行微任务队列里的任务,再去执行宏任务队列中最前面的任务。执行宏任务过程中,遇到微任务,再依次加入微任务队列。
栈空后,再次读取微任务队列里的任务,以此类推。
当某个宏任务中代码全部执行完毕后,会查看是否有微任务队列,如果有,先执行微任务队列中的任务,如果没有,就查看是否有其他宏任务队列。
浅拷贝和深拷贝
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存,
但是深拷贝会另外创建一个一模一样的对象,新对象和原对象不共享内存,修改新对象不影响原对象。
赋值赋的是栈中的地址,而不是堆中的地址。
浅拷贝的实现方式object.assign()
var initObj = Object.assign({},obj);
initObj.a.a = “wade”;
console.log(obj.a.a);//打印出来wade
深拷贝实现方式
json.parse(json.stringfy())
函数库loadash
该函数库有提供_.cloneDeep来做深拷贝
函数柯里化
函数柯里化
只传递函数一部分参数来调用他,让他返回一个函数去处理剩余的参数1
2
3
4
5
6
7
8
9
10
11
12普通add函数
function add(x,y){
return x + y;
};
//柯里化后
function curryAdd(x){
return function(y){
return x + y;
}
}
add(1,2);
curryAdd(1)(2);
柯里化作用 可以起到参数服用 复用的结果
可以将一个reg返回后,返回一个带有一个正则表达式校验的函数,
在剩余函数中调用。
视口
代表当前可见的计算机图形区域,在web浏览器中,通常与浏览器窗口相同,但不包括浏览器的UI,菜单栏等。
布局视口:布局视口是网页布局的基准窗口,在 PC浏览器上,布局视口就等于当前浏览器窗口大小(不包括 borders 、 margins、滚动条)。,移动端默认为980px
document.documentElement.clientWidth/clientHeight来获取
视觉视口:用户通过屏幕真实看到的区域:
相当于浏览器的窗口大小,当用户对浏览器进行缩放时,不会改变布局视口的大小,所以页面布局是不变的,但是缩放会改变视觉视口的大小
window.innerWidth和innerHeight来获取
理想视口:网站页面在移动端显示的理想大小。
当页面缩放比例为 100%时, CSS像素=设备独立像素, 理想视口=视觉视口。
screen.width /height
window.innerHeight 获取浏览器视觉视口高度
window.screen 屏幕理想视口宽度,设备的分辨率,设备像素比
document.clientHeight 布局视口高度
offsetHeight 获取包括内边距,滚动条,边框和外边距
scrollHeight在不使用滚动条的情况下适合视口中的所有内容所需的最小宽度。测量方式与 clientHeight相同:它包含元素的内边距,但不包括边框,外边距或垂直滚动条。
移动端适配 @media移动端查询 flexible方案,统一使用rem来布局,
vh,vw方案将视觉视口window.innerWidth和height等分为100份
(连接)[https://mp.weixin.qq.com/s/oF6oAjdzguv9OwE9cdLrPQ]
闭包
闭包是js的一个难点,
作用:可以访问函数内部的变量,使变量的值长期保存在内存中
形成条件:函数嵌套,内部函数引用外部函数的局部变量
能够读取其他函数内部变量的函数
只有函数内部的子函数才能读取内部变量,因此可以把闭包理解为”定义在一个函数内部的函数”,
闭包的特点记住诞生的环境,比如f2记住了它诞生的环境f1,所以从f2可以得到f1的内部变量.
闭包将内部函数和外部函数连接起来的一座桥梁**
内存泄漏指的是任何对象在您不需要拥有或者使用的时候它仍然存在,闭包不能滥用,否则导致内存泄漏,影响网页的性能。闭包使用完了后,要立即释放资源,将引用变量指向null.
闭包作用:读取函数内部的变量,可以使变量的值长期保存在内存中,生命周期比较长。
用来实现js模块,js模块:具有特定功能的js文件,将所有数据和功能都封装在一个函数内部,只向外暴露一个具有n个方法的对象或者函数,只需要通过暴露模块的对象调用来实现对应的功能即可。
forEach,map,filter,find,every,some,reduce它们都有共同的特点,不会改变原来数组。
线程与进程的区别
node中的事件循环
一个进程只有一个主线程
进程是cpu资源分配的最小单位,线程是cpu调度的最小单位,
一个进程可以由一个线程或多个线程组成,线程是一个进程中代码的不同执行路径
进程的内存空间是共享的,每个线程都可以共享这些内存
浏览器内核是多线程的,在内核控制下各个线程互相配合以保持同步,
GUI渲染线程,js引擎线程,定时触发线程,事件触发线程,异步http请求线程。
node中的event loop
node中的event loop和浏览器中的完全是不相同的东西,node js采用v8作为js的解析引擎,而I/O处理方面采用了自己设计的libuv.
libuv是一个基于事件驱动的跨平台抽象层,封装了不同操作系统的底层特性,对外提供统一的api,事件循环机制也是这里面实现的。
node js运行机制如下:
v8引擎解析js脚本。
解析后的代码,调用node api.
libuv库负责node api的执行,将不同的任务分给不同的线程执行,形成一个event loop,以异步的方式将任务的执行结果返回v8引擎.
v8引擎将结果返回给用户
libuv执行node api分为以下几个阶段
事件循环分为6个阶段,会按照顺序反复执行,每当进入一个阶段时,都会从对应的回调队列中取出函数去执行,当队列为空或者执行的回调函数达到系统设定时,将会进入下一个阶段。
外部输入数据 -> 轮询阶段(poll) -> 检查阶段(check) -> 关闭事件回调阶段(close callback) -> 定时器检测阶段(timer) -> I/O事件回调阶段(I/O callbacks) -> 闲置阶段(idle,prepare)
timers阶段(这个阶段执行timer(setTimeout,setInterval))的回调
i/o callback阶段,处理一些上一轮循环中少数的未执行的i/o回调
idle 仅node内部使用
poll阶段 获取新的i/o事件。
check阶段 执行setimmediate的回调
close callback 执行socket的close事件回调
poll阶段
1.回到timer阶段执行回调
2.执行i/o回调
并且在进入该阶段如果没有设定timer的话,会发生如下事件
如果poll队列不为空,会遍历回调队列并且同步执行,直到队列为空或者达到系统上线
如果poll队列为空,如果有setimmediate回调需要执行,poll阶段停止并且进入到 check 阶段执行回调
如果没有setimmediate,会等待回调被加入到队列中并立即执行。
check阶段 setimmediate的回调会被加入到check中
node中事件也有宏任务(macro)和微任务(micro)
macro:setTimeout,setInterval,setImmediate,script代码
micro:process.nextTick,new Promise().then(回调)
process.nextTick,独立于event loop外,有自己的一个队列,
当每个阶段完成后,如果存在nextTick队列,会清空队列中的所有回调函数,并且优于其余的微任务执行。
浏览器环境中,microtask的任务队列是每个macrotask执行完后再执行
而在node js中,microtask会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行microtask中的任务。
js中的垃圾回收和内存泄漏
内存泄漏指的是:不再用到的内存,没有来级的释放。js具有自动回收垃圾机制
js的垃圾回收机制:找出不再使用的变量,然后释放掉其内存即可。垃圾回收期会按照固定的时间间隔周期性的回收
垃圾回收有2种方法:标记清除和引用计数
标记清除:当变量进入执行环境时,就标记这个变量进行环境,逻辑上讲,永远不要释放进入环境的变量所占用的内存,因为只要执行刘进入相应的环境,就可能会用到他们,当变量离开环境的时候,就将其标记为离开环境。
引用计数:是指语言引擎有一张‘引用表’,保存了内存里面的所有资源(通常是各种值)的引用计数。
如果一个值的引用计数为0,就表示该值不会再用到,因此可以将该值释放。
如果一个值不再需要了,引用计数却不为0,垃圾回收机制将无法识别这块内存。从而导致内存泄漏。
哪些情况会造成内存泄漏:
会让变量一直处于进入环境的状态,而无法被回收。1
2
3function foo(arg){
bar = "this";
}
创造了一个全局变量,在页面关闭之前是不会释放的。
被遗忘的计时器或者回调函数 闭包可以维持内部函数的局部变量,使其得不到释放。