-
promise实现中的cbs为什么是个数组
-
bind最终实现中,通过空函数做了中转,为什么可以这么做
-
vue项目中 request.js中为什么不能用this.$store:在跟组件注册了store之后,所有的vue文件可以使用this.$store访问store,但是js文件不可以
-
require('')和import ''的区别
-
生成路由为什么写到store的action里面
-
vue官方文档,不熟悉的语法
-
vue-cli官方文档,尤其是webpack相关怎么写,以及项目中很多文件如何配置等工程化的东西
-
代码格式Eslint插件
-
vue全局变量有什么
-
自定义指令
-
vue 装饰器
-
调试
-
v-loading替代方法
-
nextTick原理:还有一点不理解,怎么把任务放到渲染任务后面
-
sort()打乱数组,第一个排到最后一个的概率
-
BFF
-
vue3
-
vite
-
webpack原理
-
模块更新 npm怎么操作
-
浏览器如何执行一个js脚本
-
computed的原理
文档:https://juejin.cn/post/6844903476661583880#heading-9
源码:https://gitee.com/panjiachen/vue-element-admin?_from=gitee_search
登录获取token,根据token获取用户权限,根据权限生成该用户可访问的router
router.beforeEach:
- 在这里对当前用户可访问的路由进行计算
- 进行页面级的权限判断
对axios进行封装 -> request:请求级的权限判断
- 易修改、易维护。
- 无障碍阅读支持。
- 搜索引擎友好,利于 SEO。
- 面向未来的 HTML,浏览器在未来可能提供更丰富的支持。
br换行,hr下划线
 :no-break space
&emsp:em space全角空格
&ensp:en space半角空格
引入js:import、script标签
引入css:@import、style标签写道head中、link标签可以复用但是不利于维护
DOCTYPE:在文档的头部,声明文档类型
meta:在head标签里面,提供原属性
都是不可编辑
readonly只对input有效,disabled对所有表单组件有效
readonly可以选中不可输入,disable不可选中
通过src指定播放源,不同浏览器支持的格式不同,可以通过source标签规定可替换的音视频文件供浏览器选择
没看感觉很少用了
对于script标签,没有修饰时,脚本的加载和执行会阻塞html解析。defer和async修饰的script标签在加载脚本过程不阻塞html解析,acync脚本执行阶段阻塞html解析,defer不阻塞。
小程序中scroll-view需要设置高度
下面的代码虽然没有直接设置高度,但是container的style决定了scorll-view的高度
<view id="container" style="height:100%; display:flex; flex-flow:column nowrap">
<view>搜索</view>
<scroll-view></scroll-view>
</view>
link通常用于引入外部css和favicon,放在head里面有利于浏览器渲染,不出现白屏
- 页面被加载时,link就会同时被加载;@import写在
<style>
标签或者css文件中的,在页面加载完成后再加载 - link权重更高
display 布局
position: relative absolute fixed
float: 浮动,脱离文档流
overflow: 滚动轴
z-index: 配合float和position使用
scale rotate translate
// 平移,如果参数是百分比,是参考自身的宽高
transform:translateX(x)
transform:translateY(y)
transform:translate(x,y)
// 缩放,类与translate
transform:scaleX(x)
transform:scaleY(y)
transform:scale(x,y)
// 旋转,正数表示顺时针,负数逆时针
translate:rotate(**deg)
给百分比: 基于父元素的宽度
@keyframes name{}定义每一帧的样式
animation: 名字 时间 时间函数 方向 延时 最终状态 循环次数
css属性 时间 时间函数 延时
display:不会占用页面空间,不占用文档流
visible:会占用页面空间,占用文档流
opacity:透明度,0是透明,1不透明
块级元素:新起一行,宽度、高度可控,不设置宽度时,默认100%,可以包含行内元素
行内元素:宽度、高度和内边距都不可控
border-box:宽高包含padding margin border
content-box:宽高只是内容的宽高
伪类:一个冒号,给原本就在dom中的元素添加个类,改变样式
伪元素:在原有的dom元素的指定位置增加一元素,展示在ui上
!important > 行内(1000) > id(100) > 类(10) > 标签(1) > 通配 > 继承 > 浏览器默认
flex:block类型的元素
position:block类型的元素
line-height+text-align:span元素
可以给margin、padding、border、font-size和width等属性设置动态值
重叠:同级元素之间,只设置一个元素的margin,或者bfc
塌陷:子元素和父元素的margin相遇,子元素的margin改为padding
独立的渲染区域,可以清除浮动,解决父元素底部塌陷,解决同级元素margin重叠
触发bfc的条件:
- body元素
- float不为none
- position为absolute和fixed
- display为flex inline-block inline-flex table-cell table-caption
- overflow除visible以外的
撑满容器左右边距,不与其他元素同行,
只包裹内容,不占用多余空间
display:flex
/*以下属性作用于盒子中的元素,盒子中的元素也会自动声明为弹性盒子*/
/* 盒子中元素的间隔:左对齐(默认)、居中对齐、右对齐、两端对齐且项目间隔相等*/
justify-context:flex-start、center、flex-end 、space-between
/*轴对齐方式:向上对齐、居中、向下对齐、拉伸对齐(默认)*/
align-items:flex-start、center、flex-end、stretch
/*主轴为row就是横着排、column就是竖着排,默认row nowrap*/
flex-flow:row nowrap
以上属性只作用于盒子中的子元素,孙子元素不受控制
display:grid
grid-template-columns:列宽
grid-template-row:行宽
子元素高度大于父元素时,超出的部分还是可以正常显示,是因为父元素overflow默认是visible
父元素高度固定,子元素自适应:父元素flex,包含两个子元素,其中一个高度固定,另外一个自适应
父元素高度不确定,根据子元素变化
radio checked +
JS组成:https://blog.csdn.net/qq877507054/article/details/51395830
DOM:https://blog.csdn.net/qq_42127861/article/details/82145842
JS由三部分组成:ES,DOM,BOM
ES:ECMAScript,描述了JS的语法和基本对象。
DOM:文档对象模型 document object model,js可以通过dom操作html元素。浏览器把html文档解析为dom,dom是树形结构,树根是document
BOM:浏览器对象模型 brower oject model,js可以通过bom操作浏览器
下图中:ABCD属于BOM,E是DOM
//window的方法
window.getComputedStyle("元素"[,"伪类"]).元素名//能获取所有的style,包括没有在style中明确写出的
//Screen
//Location
//Navigator
//history
document是window的子对象
//document的方法
document.getElementById("")
document.creatElement("")
//DOM Elmement常用方法
appendChild(node)
removeChild(node)
getAttribute(attribute)
setAttribute(attribute,value)
removeAttribute(attribute)
getBoundingClientRect()//获取元素的大小及位置
//DOM Elmement常用属性
// https://blog.csdn.net/alokka/article/details/81458962
style//或者或修改定义在style=""中的属性
offsetHeight(offsetWidth)//长(宽) + padding + border
clientHeight(clientWidth)//长(宽) + padding
scrollHeight(scrollWidth)//如果没有滚动条,等同于clientHeight(clientWidth),如果有就是内容的高度(宽度)
offsetTop(offsetLeft)//上方(左侧)距离窗口距离
clientTop(clientLeft)//上方的(左侧)border
scrollTop(scrollLeft)//滚动后上方(左侧)被隐藏的具体
offsetX(offsetY)
clientX(clientY)
scrollX(scrollY)
基本类型:boolean,number, string, undefined, null, symbol, bigInt
复杂类型:object,包含function、date、array
number的构造函数Number,string的构造函数String,boolean的构造函数Boolean.....
null是不存在的值,undefined是已声明未赋值
!null !undefined !0 !'' !NaN都是true
除null之外的基本数据类型,object,function
typeof(null)是object
a = Number(1)
typeof a // number
b = new Number(1)
typeof b // object
遍历string:for of,for in效果和array是一样的,不可以用map这类函数
空字符串转Number是0
原始for循环,for of,for in可以响应return continue break,可以修改原数组数据
map,forEach不可以响应
push pop shift unshift splice reverse sort ;concat join slice map forEach fliter every some reduce。分号前改变原始数组,分号后不改变
map&foreach:都可以对数组进行遍历,不改变数组。map需要有返回值,根据返回值生成新的数组,空数组也会调用回调;foreach没有返回值,空数组不调用回调
遍历用for of,添加值用set(key, value)
遍历用for of,添加值用add(value)
- Map Set用for of,Object用for in遍历
- object的key只能是Number String Symbol会自动转为string,map可以是任意类型不会自动转化
- map通过set新增数据,object通过obj[]或者obj.属性; map有delete方法,map.delete(),object只能用delete obj.属性
- weakMap的key只能是object
- 弱引用,在vue3.x版本源码中就用到了
weakMap
作为响应方法的一个存储方式,这样做的一个好处是可以防止内存泄露,如MDN所说如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用WeakMap
。
转布尔:Boolean()
转数字:Number() parseInt() parseFloat()
转字符串:toString() String()
++,while(),if(),!,+
arguments:是一个类数组
rest:...argument可以将有名字的参数去掉
数组:[]=[]
对象:{}={}
变量和属性名不同时,需要用模式
解构化赋值是浅拷贝
先声明再使用,只在自己的作用域内有效,作用域外引用报错
const声明的变量不可以改变
var存在变量提升,let和const不存在
函数才有作用域和作用域链,声明函数时通过[[scope]]指定他的作用域链
闭包:两种概念,理论上的和实际中。
- 创建一个对象
- 对象的
__proto__
指向构造函数的prototype - 通过apply,将构造函数的this指向对象,并执行构造函数
- 构造函数的执行结构如果是个对象,返回结果,否则返回第一步创建的对象
this是在创建上下文时确定的,并且在上下文执行过程中不变
计算:讶羽大佬的 JavaScript深入之从ECMAScript规范解读this.md
直观例子:https://juejin.cn/post/6844903488304971789
有关箭头函数:在直观链接中有讲解,箭头函数会捕获其所在上下文的 this
值,作为自己的 this
值,所以当箭头函数所在上下文的this
值变化时,箭头函数的this
值也会跟着变化
改变this指向,apply和bind的参数以数组形式传入,bind返回一个函数
每个对象都有一个_proto_
属性,指向其构造函数的prototype,只有函数有prototype
构造函数就是类,约定首字母大写,可以通过new的方式创建对象,一些公共的方法和属性可以定义到原型上。
JavaScript有一些内置构造函数如Number、Array等,内置构造函数的_prop_
都指向Function.prototype
,他们都是Function的实例。Object也是Function的实例,所以Object._prop_
指向Function.prototype
。但是一切对象都是Object的实例,所以Function.prototype._prop_
指向Object.prototype
https://zhuanlan.zhihu.com/p/159456303
变量提升:var定义的变量和函数表达式会存在变量提升,const和let不会,变量提升就是将该变量的声明提升到顶端,但是赋值仍在原位置
函数提升:只提升函数声明(function fun(){}),在该函数声明以前就可以调用该函数
如果函数提升和变量提升同时存在,那么首先处理函数声明。
console.log(foo); //[function:foo]
function foo(){
console.log("foo");
}
var foo = 1;
变量提升和函数提升不会覆盖已有的声明(个人理解)
var foo = 1;
console.log(foo); // 1
function foo(){
console.log("foo");
}
console.log(foo); // 1
foo()// foo is not a function
var foo = 1;
console.log(foo); // 1
var foo = function(){
console.log("foo");
}
console.log(foo); // 1
foo()// foo is not a function
Promise对象的参数是一个函数,该函数有两个参数,这两个参数是两个函数resovle和reject,调用resolve()函数,Promise变成resolved状态,抛出错误或者调用reject(),Promise变成rejected状态。
Promise可以通过then方法指定状态改变后的回调函数,then方法有两个参数,分别为resolved和rejected状态回调函数,resolved状态的回调函数参数是resolve的参数,rejected状态回调函数是reject的参数或抛出的错误,一般情况下只给一个回调。通常会通过catch方法指定rejected状态的回调函数,等同于then(null, rejection),但是catch指定的回调会同时处理then指定的回调中的异常。
then()和catch()会返回新的Promise对象,所以后边可以使用then和catch指定回调函数,then()和catch()返回的值会作为then的参数,抛出的错误会作为catch的参数。catch和then里面的return被后面的catch和then接受,不会逃出promise
-
resolve参数是一个promise对象:promise的状态由参数中的promise决定
-
promise(promise1)参数函数体是一个promise对象(promise2),promise1的状态取决于合适调用他的resolve()
new Promise((resolve, reject) => { new Promise(() => { resolve(2) }) }).then(value => { console.log(value) }) //2
-
then指定的回调函数返回promise对象,等待这个promise对象状态发生变化,才会继续执行后续的then和catch(如果有的话),这个promise对象resolve的参数会传入后续的then
参数是常量时相当于,创建一个Promise对象,然后立即resolve(该常量)
Promise.resolve("test")
//相当于
new Promise(resolve => {resolve("test")})
async函数返回一个promise对象,后面可以跟then方法。async函数中return返回的值,是then方法指定的回调函数的参数,抛出错误的话Promise会变成reject状态,错误对象被catch方法指定的的回调接收。
await修饰promise对象时,会等待promise对象执行结束,接受promise对象中resolve或者reject的值,再执行async函数中后边的代码
https://juejin.cn/post/6844903834075021326
添加事件:addEventListener(event, function, useCapture),useCapture默认false也就是冒泡
捕获、冒泡:1-5是捕获过程,5-6是目标阶段,6-10是冒泡阶段;
事件委托:利用事件冒泡的特性,将本应该注册在子元素上的处理事件注册在父元素上
https://segmentfault.com/a/1190000018605776
可达性分析,标记-清楚算法
element-ui image
() => import('')
节流:定时发送请求
防抖:延时发送请求,延时时间内再次触发,重新计时
只用了axios,create函数创建一个实例,添加请求和响应拦截器。
axios封装了promise和XMLHttpRequest
讶羽深入系类13
commonJS:服务端的标准,nodejs的出现使得的js可以作为服务端语言,后端必然需要模块化,commonjs就是服务端的模块化标准。同步加载,运行时加载。
AMD CMD:浏览器端的模块化标准,异步加载,运行时加载。
ES6:编译时加载
深入浅出webpack:http://webpack.wuhaolin.cn/
将浏览器无法解析的代码,如模块化,es6,scss,vue单页面等转换成浏览器可以解析的代码。
entry:入口
output:打包生成的文件存放路径
loader:module.rules[]配置loader
plugins:插件
resolve:指定loader的位置和alias
- npm init创建package.json,保存所需的依赖和版本。可以通过npm install构建所有的依赖。
- npm i -D webpack安装webpack,生成node_module文件夹和package-lock.json
- 在webpack.config.js中写入配置,运行webpack,生成dist文件夹,包含所有的bundle。
初始化:合并命令行和配置文件中的配置,实例化compiler,加载插件
编译:webpack会把每个文件看成一个模块,从entry出发,用配置中的loader去处理模块,再找到它依赖的模块,递归处理,构建模块之间的依赖关系
输出:根据处理的模块生成chunk,再把chunk输出到文件
在这整个过程中,webpack会广播很多事件,插件通过监听这些事件,在合适的时机发挥作用。
- 有一些没有使用模块化的,在module.noparse配置,webpack不会进行处理
- loader中配置exclude和include缩小匹配范围
- 对于性能开销比较大的loader可以在前面添加cache-loader,增加一个缓存
- 让webpack同时处理多个任务,可以配置thread-loader,放在其他loader的前面
HardSourceWebpackPlugin
为模块提供中间缓存
通过静态引入的模块,实际没有使用的部分不会进行打包
全家桶:vue/cli, vue-route, vuex
https://segmentfault.com/a/1190000040971075
官方提供了三种使用vue的方式
- 通过
<script>
引入vue.js - 通过cdn,1和2两种方式都是对当前html页面进行增强
- vue脚手架构建一个单页面应用,自动一键生成vue+webpack的项目模版,包括依赖库,免去手动安装各种插件,寻找各种cdn并一个个引入的麻烦。
官方文档:https://cli.vuejs.org/zh/guide/
vue脚手架的作用是用来自动一键生成vue+webpack的项目模版,包括依赖库,免去手动安装各种插件,寻找各种cdn并一个个引入的麻烦。
package.json是npm init生成的
node_modules和package-lock.json是安装webpack生成的
src:写代码的地方
Vue-cli、Vue-router、VueX等
2.x中v-for优先级高,3中v-if优先级高,但不推荐同时使用
2.x通过definedProperty,3.0通过proxy
数据驱动和组件系统
将多次对dom的操作记录在虚拟dom上,再一次性操作dom,可以避免频繁操作dom
一层一层的比较,如果有key(v-for里面的key),可以增加复用,更高效地更新dom
响应式指数据变化后,视图自动更新,也就是数据影响视图
数据劫持+发布订阅模式
- observe:遍历 data 中的属性,使用 Object.defineProperty 的 get/set 方法对其进行数据劫持;
- dep:每个属性拥有自己的消息订阅器 dep,用于存放所有订阅了该属性的观察者对象;
- watcher:每个组件都对应一个观察者(watcher),通过 dep 实现对响应属性的监听,监听到结果后,主动触发自己的回调进行响应
使用object.defineProperty定义所有的属性,增加get和set方法,每个属性还有一个dep属性,在get方法中收集Watcher到dep中,在set方法中通过notify()通知dep中的Watcher进行更新。
数据劫持+发布订阅模式
proxy是针对整个对象的,在访问对象前添加了一层拦截
defineProperty:https://zhuanlan.zhihu.com/p/107610658
proxy+defineProperty:https://segmentfault.com/a/1190000019700618
双向绑定指数据影响视图(响应式),视图影响数据
视图影响数据:添加事件,值发生变化时触发函数修改model中的数据
在更新完dom节点后调用
原理:vue更新dom是异步(通过调用nextTick实现),调用this.$nextTick会在更新dom的微任务后面,加入指定的回调函数
降级策略:nextTick首先选择通过promise实现,不支持时选择MutationObserver,然后选择macroTask(setImmediate > setTimeout)
源码解析(里面有个微信链接,排版更好):https://zhuanlan.zhihu.com/p/62732142
原理解析:https://zhuanlan.zhihu.com/p/53219652
更细致的讲解:https://www.mybj123.com/5865.html
computed中的属性都有一个watcher,保存计算函数和缓存结果。除此之外类似于data中的属性,computed中的属性也用defineProperty添加了get、set方法进行数据劫持,但是他的get进行了包装,在get中进行缓存控制、关联组件和data属性。
有一个标志位dirty,false的时候直接使用该数据,true的时候,组件使用computed中的数据,会导致数据重新计算
假设a组件使用了计算属性b,计算属性b依赖于data中的属性c,计算属性b的watcher会放到data属性c的deps中,组件a的wacher也会放到data属性c的deps中。
实现方法:计算属性b的watcher中会保存data属性c,在data属性c收集计算属性b的watcher时,data属性的dep也会保存到计算属性的watcher的deps中,随后会遍历watcher的deps,将组件a的watcher存入。这里面组件a的watcher的计算属性b的watcher都保存在一个全局遍历Dep.target中,通过栈的方式进行缓存。
- props
- v-model,默认为:value="" @input="",可以在子组件的model中修改{prop:'', event:''}
https://blog.csdn.net/qq_38824137/article/details/93484802
-
slot-scope
-
$emit
-
$ref
// 父组件 <template> <div> <my-main ref="main"></my-main> </div> </template> <script> import MyMain from "./component/MyMain.vue"; export default { components: { MyMain }, mounted() { console.log(this.$refs.main.array); // [1,2,3] this.$refs.main.getSonData()// [1,2,3] }, }; </script> // 子组件 <template> <div style="display:none"> {{array}} </div> </template> <script> export default { data() { return { array: [1, 2, 3] }; }, methods:{ getSonData(){ console.log(this.array) } } }; </script>
事件总线: 创建一个vue实例eventBus,通过eventBus.$on('event',handle)监听事件,通过eventBus.$emit('event',params)触发事件
vuex:state mutations actions getters modules
__dirname:项目根目录的绝对路径
node.js上有一个代理服务器,浏览器向代理服务器发送请求,代理服务器再向真正的服务器发起请求
浏览器和代理服务器是同源的,就不会发生跨域.
beforeCreated:vue实例还没有完全创建好,data和methods还没有初始化,此时不能操作data
created:vue实例创建好了,data和methods完成了初始化
beforeMounted:模板编译完成,但是还没有挂载,此时获取不到dom
mouted:完成挂载,可以操作dom
beforeUpdated:data数据已经更新,但是虚拟dom未更新
updated:虚拟dom已经更新
beforeDistory:销毁前
distroyed:销毁完成
https://www.cnblogs.com/zhilu/p/13812822.html
官方文档:https://cli.vuejs.org/zh/guide/webpack.html
主进程:界面显示、用户交互、子进程管理、存储
渲染进程:chrome会给每一个tab创建一个渲染进程
网络进程
插件进程
GPU进程
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies
每个服务器,浏览器会保存一份对应的cookies,不管从哪个网站向同一个服务器发送请求时,会自动携带该服务器对应的cookies
httpOnly:js是否可以操作cookies,可以防范xss
samesite:决定浏览时什么情况下发送cookies
Domain 和 Path:标识定义了Cookie的作用域,即允许 Cookie 应该发送给哪些URL。
为什么服务器端不用存储就可以验证token,但是需要存储session才能验证cookies
https://www.cnblogs.com/moyand/p/9047978.html
事件循环机制是浏览器或者nodejs的机制,为了协调事件,用户交互,脚本,渲染,网络等,用户代理必须使用事件循环。
js单线程,可能会出现一些cpu空等的情况,比如网络请求,setTimeout等。为了解决这种情况,当出现了异步操作,主线程将这些任务交给响应的模块去处理,处理完成后推入任务队列,cpu空闲后从异步队列中取出一个任务推入主线程
宏任务:setTimtout setInterval setImmediate MutationObserver
微任务:Promise MutationObserver process.nextTick
https://www.jianshu.com/p/54cc04190252
http://www.alloyteam.com/2016/03/discussion-on-web-caching/
1.先判断缓存中是否存在请求数据,不存在则发起请求,并将数据缓存。
2.如果存在缓存根据cache-control和expires(cache-control优先级高)判断是缓存是否过期,如果有效,直接使用缓存。
3.如果过期向浏览器发起请求,(根据last-modify和eTag字段)携带if-modify-since和if-none-match,浏览器根据这两值判断请求内容是否发生变化,没变化则直接使用缓存,变化则重新返回数据并且更新缓存
在本地存储用户数据,有localStorage和sessionStorage两种。
localStorage生命周期是永久的,sessionStorage关闭浏览器后消失,页面刷新不消除数据,只有在当前页面打开的链接才可以访问
还有cookies
协议 主机 端口号有一个不同即为跨域
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互
- 通常允许跨域写操作 :重定向以及表单提交。
- 通常允许跨域资源嵌入:
<script><link><img><video><iframe>
- 不允许跨域读操作: ajax
JSONP:构造具有src属性的标签,这种标签可以跨域,只支持get请求,即使后端不支持跨域可以发起get请求
CORS:浏览器自动处理,用户无感知,主要靠后端实现,可以支持各种类型的http请求
CORS要把请求分成两类:简单请求和预检请求,复杂请求会先发送预检请求,简单请求不会
简单请求:get post head请求,请求头只有accept accept-language content-language content-type,content-type只能是 text/plain、multipart/form-data 或 application/x-www-form-urlencoded
https://juejin.cn/post/6844904021308735502#heading-74
跨站脚本攻击cross-site scripting:攻击者在web页面中插入一些恶意代码,用户浏览该页面时恶意代码执行,从而攻击用户,可以分为三类。
反射型:浏览器、后台
存储型:浏览器、数据库、后台
文档型:浏览器
防御措施:html编码,将一些敏感字符进行编码;利用HttpOnly
跨站请求伪造cross-site request forgery:用户登录站点A之后,攻击者诱导用户访问B站点,B站点利用已有的cookie发起一个请求。
防御:referer samesite token
promise promise.all promise.race
indexOf forEach map filter reduce
交换排序:快排、冒泡
选择排序:直接选择、堆排序
插入排序:简单插入、希尔排序
归并排序
LeetCode、CodeTop、牛客
https://segmentfault.com/a/1190000011154120
webApp:在手机中浏览网页,不需要下载app
nativeApp:最原始的,需要下载安装,Android用java开发,iOS用object-c开发
hybirdApp:介于webApp和nativeApp之间,在nativeApp里面套了一个webApp,需要下载安装
react Native App:react.js
weex App
小程序:微信、支付宝,rax框架
框架:vue react
nodejs是javascript的运行环境,就像jvm是java的环境一样
javaScript原本只能依赖于浏览器执行,nodejs的底层是浏览器引擎,又加入了io线程池和网络io,使得javascript可以脱离浏览器,从而使得javaScript可以开发后端。
Backend for frontend