JavaScript
js 面试技巧
基础工程师:基础知识 高级工程师:项目经验 架构师:解决方案
几个面试题
- typeof 能得到哪些类型
==>考点:数据类型 - === 和 ==
==>考点:数据类型强制转换 - window.onload 和 DOMContentLoaded 的区别
==>考点:浏览器渲染过程 - 用 js 创建 10 个 a 标签,点击的时候弹出对应的序号
==>考点:作用域 (为什么不是事件委托?) - 简述如何实现一个模块加载器,实现类似 require.js 的基本功能
==>考点:模块化 - 实现数组的随机排序
==>js 基础算法
js 基础知识
原型,原型链 作用域,闭包 异步,单线程
原型,原型链
知识点
- 构造函数
function Foo(name,age) {
this.name = name
this.age = age
this.class = 'class-1'
// return this // 默认有这一行
}
const f = new Foo('lyr', 20) // new 的时候this会先变成一个空对象,
// 扩展
const a = {}; 是 const a = new Object() 的语法糖
const a = []; 是 const a = new Array() 的语法糖
function Foo() {} 是 const a = new Function() 的语法糖
// 使用 instanceof 判断一个函数是否是一个变量的构造函数
// 判断变量是不是数组
// 变量 instanceof Array
- 原型规则和实例, 原型链和instanceof
- 所有的引用类型除了null(数组,对象,函数)都具有对象的特性,可自由扩展属性
- 所有的引用类型除了null(数组,对象,函数)都具有__proto__属性(隐式原型), 属性值是一个普通的对象
- 所有的函数都有一个prototype属性(显式原型), 属性值也是一个普通的对象
- 所有的引用类型除了null(数组,对象,函数), __proto__属性值指向它的构造函数的prototype属性值
- 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__(也就是它的构造函数的prototype)中寻找
const obj = {}
const arr = []
function fn() {}
// 所有的引用类型除了null(数组,对象,函数)都具有对象的特性,可自由扩展属性
obj.a = 100
arr.a = 100
fn.a = 100
// 所有的引用类型除了null(数组,对象,函数)都具有__proto__属性, 属性值是一个普通的对象
console.log(obj.__proto__)
console.log(arr.__proto__)
console.log(fn.__proto__)
// 所有的函数都有一个prototype属性, 属性值也是一个普通的对象
console.log(fn.prototype)
// 所有的引用类型除了null(数组,对象,函数), __proto__属性值指向它的构造函数的prototype属性值
console.log(obj.__proto__ === Object.prototype)
// 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__(也就是它的构造函数的prototype)中寻找
function Foo(name, age) {
this.name = name // this指的时实例f
}
Foo.prototype.alertName = function() {
alert(this.name) // this指的时实例f
}
const f = new Foo('lyr')
f.printName = function() {
console.log(this.name) // this指的时实例f
}
f.printName()
f.alertName()
for (var item in f) {
if(f.hasOwnProperty(item)) {
// 只遍历本身的属性,不遍历原型的属性
// 高级浏览器已经在for in中屏蔽的来自原型的属性
}
}
// 原型链
f.toString() // 要去f.__proto__.__proto__ ... 中去找
// instanceof
f instanceof Foo; // f的__proto__ 一层一层往上,能否对应到 Foo.prototype
f instanceof Object; // f的__proto__ 一层一层往上,能否对应到 Object.prototype
题目1: 如何准确判断一个变量是数组类型: arr instanceOf Array
题目2: 描述new一个对象的过程
- 创建一个新对象
- this指向这个新对象
- 执行代码,即对this赋值
- 返回 this 题目3: 写一个原型链继承的例子
// 封装dom查询到例子
function Elem(id) {
this.elem = document.getElementById(id)
}
Elem.prototype.html = function(val) {
var elem = this.elem
if(val) {
elem.innerHTML = val
return this // 链式操作
} else {
return elem.innerHTML
}
}
Elem.prototype.on = function(type, fn) {
var elem = this.elem
elem.addEventListener(type, fn)
return this // 链式操作
}
var div1 = new Elem('div1')
console.log(div1.html())
div1.html('<a>点击</a>').on('click', function() {
console.log(111)
})
作用域,闭包
知识点:
- 执行上下文 js 是解释型语言, 在函数执行之前会把变量定义和函数声明先拿出来占个位,然后解析 范围: 一段script或者一个函数 全局(script): 变量定义, 函数声明 函数: 变量定义, 函数声明, this, arguments
PS: 函数声明 不是 函数表达式 const a = function() {}
console.log(a) // undefined
var a = 100
// 等价于
var a = undefined
console.log(a)
a = 100
fn('lyr') // 'lyr' 20
function fn(name) {
age = 20
console.log(name, age)
var age
console.log(this)
console.log(arguments)
}
// 等价于
function fn(name) {
var age
age = 20
console.log(name, age)
console.log(this) // window
console.log(arguments)
}
fn('lyr')
- this this在执行时才能确认值, 定义时无法确认 执行的场景: 作为构造函数执行: this指的是实例 作为对象属性执行: this指的是那个对象 作为普通函数执行: this指的是window call apply bind: this是绑定的那个对象
const a = {
name: 'a',
fn: function() {
console.log(this.name)
}
}
const b = {name: 'b'}
a.fn() // this === a
a.fn.call(b) // this === b
const fn = a.fn
fn() // this === window
// 构造函数
function Foo(name) {
const this = {}
this.name = name
return this
}
const f = new Foo('lyr')
// call apply bind
function fn1(name, age) {
console.log(name, this)
}
fn1.call({x: 100}, 'call', 20)
fn1.apply({x: 200}, ['apply', 20])
const fn2 = function(name, age) {
console.log(name, this)
}.bind({x: 300})
fn2('bind', 20)
- 作用域 没有块级作用域(大括号) 只有函数和全局作用域 es6中有块级作用域
// 没有块级作用域
if(true) {
var name = 'lyr'
}
console.log(name) // lyr
for(var i = 0; i < 10; i++) {}
console.log(i) // 10
// es6
if(true) {
const age = 10
}
console.log(age) // error: age is not defined
for(let i = 0; i < 10; i++) {}
console.log(i) // i is not defined
// 函数和全局作用域
var a = 100
function fn() {
var a = 200
console.log('fn', a)
}
console.log('global', a)
fn()
- 作用域链 自由变量:当前作用域没有定义的变量
// 定义时的父级作用域
var a = 100
function fn1() {
var b = 200
function fn2() {
var c = 300
console.log(a, b, c)
}
fn2()
}
fn1()
- 闭包
// 父级作用域是定义时候的作用域,而不是执行时候的作用域
// 函数作为返回值
function F1() {
var a = 100
const b = function() {
console.log(a) // a 自由变量,向父级作用域去寻找 -- 函数定义的父级作用域
}
return b
}
var f1 = F1() // f1 === b
var a = 2000
f1() // 100, 函数执行是全局作用域
// 函数作为参数
function F1() {
var a = 100
const b = function() {
console.log(a) // a 自由变量,向父级作用域去寻找 -- 函数定义的父级作用域
}
return b
}
var f1 = F1() // fn === b
function F2(fn) {
var a = 300
fn()
}
F2(f1)
题目1: 对变量提升的理解 题目2: this几种不同的使用场景 题目3: 创建10个a标签,点击弹出对应的序号
// 错误
var i, a
for(i = 0; i<10;i++) {
a = document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function(e) {
e.preventDefault()
alert(i) // 自由变量,要去父作用域取值, 所有点击的结果都是10
})
}
// 正确
var i, a
for(i = 0; i<10;i++) {
(function(i) {
a = document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function(e) {
e.preventDefault()
alert(i) // 自由变量,要去父作用域取值
})
})(i)
}
题目4: 如何理解作用域 自由变量,作用域链,即自由变量的查找,闭包的两个场景 题目5: 实际开发中闭包的应用:封装变量,收敛权限
function isFirstLoad() {
var _list = []
return function(id) {
if(_list.indexOf(id) >= 0) {
return false
} else {
_list.push(id)
return true
}
}
}
var firstLoad = isFirstLoad()
firstLoad(10) // true
firstLoad(10) // false
firstLoad(20) // true
异步,单线程
知识点:
- 什么是异步: 同步: 阻塞 异步: 执行完之后在回来执行。 等待的场景都需要异步 定时任务: setTimeout, setInverval 网络请求: ajax, 动态img加载 事件绑定
- 前端使用异步的场景
- 异步和单线程
题目1: 同步和异步的区别?各举一例 题目2: 一个关于setTimeout的笔试题 题目3: 前端使用异步的场景
// alert() 同步
// 结果:start, end, ajax
console.log('start')
$.get('/a.json',function(data) {
console.log('ajax')
})
console.log('end')
// 结果:start, end, img
console.log('start')
const img = document.createElement('img')
img.onload = function() {
console.log('img')
}
// img.onerror = function() {
// console.log('onerror')
// }
console.log('end')
// 结果:start, end, button
console.log('start')
const btn = document.getElementById('btn')
btn.addEventListener('click', function() {
console.log('btn')
})
console.log('end')
// 异步和单线程
console.log(1)
setTimeout(function() {
console.log(2)
})
console.log(3)
其他
知识点
// 日期和math
Date.now() // 当前时间毫秒数
const dt = new Date()
dt.getTime() // 毫秒数
dt.getFullYear() // 年
dt.getMonth() // 月 0-11
dt.getDate() // 日 0-31
dt.getHours() // 时 0-23
dt.getMinutes() // 分 0-59
dt.getSeconds() // 秒 0-59
获取随机数 Math.random()
// 数组和对象
const arr = [1, 2, 3]
arr.forEach((item, index) => {
console.log(item, index)
})
arr.every((item, index) => {
if(item < 5) {
return true
}
})
arr.some((item, index) => {
if(item < 5) {
return true
}
})
arr.filter((item, index) => {
if(item < 2) {
return true
}
})
arr.sort((a, b) => (a-b))
arr.map((item, index) => {
return
})
const obj = {a: 1, b: 2}
var key
for(key in obj) {
if(obj.hasOwnProperty(key)) {
console.log(key, obj[key])
}
}
- 获取 2017-06-10 格式的日期
function formatDate(dt) {
if(!dt) {
dt = new Date()
}
const year = dt.getFullYear()
const month = dt.getMonth() + 1
const date = dt.getDate()
if(month < 10) {
month = '0' + month
}
if(date < 10) {
date = '0' + date
}
return `${year}-${month}-${date}`
}
- 获取随机数,要求是长度一致的字符串格式
const random = Math.random()
random = random = '0000000000'
random = random.slice(0, 10)
console.log(random)
- 一个能遍历对象和数组的通用的forEach函数
function forEach(obj, fn) {
var key
if (obj instanceof Array) {
obj.forEach((item, index) => fn(index, item))
} else {
for(key in obj) {
if(obj.hasOwnProperty(key)) {
fn(key, obj[key])
}
}
}
}
// 应用
const arr = [1,2,3]
const obj = { x: 1, y: 2}
forEach(arr, (index, item) => {
console.log(index, item)
})
forEach(obj, (key, val) => {
console.log(index, item)
})
js-web-api
JS基础知识: ECMA 262标准:定义js基础相关的东西,包括变量类型、原型、作用域、异步(Object, Array, Boolean, String, Math, JSON等) js-web-api: W3C标准:定义js操作页面的API和全局变量(window, document, navigator.userAgent等) W3C js相关:DOM 操作、BOM操作、Ajax(包括http协议)、事件绑定、存储(W3C还包括CSS和HTML)
DOM 操作
Document Object Model: 文档对象模型 知识点:
- DOM 本质 xml: 可扩展的语言,可以描述任何一个结构化的数据, 标签组成的树,标签可以自定义 html: 特殊的xml,就是一个字符串。这个文件要让js可以处理,就要转化,什么好处理,结构化的东西好处理,浏览器把这个字符串结构化成树,每个元素就是一个对象,让js可以操作。 DOM: 浏览器把拿到等html代码,结构化一个浏览器可以识别并且js可以操作的一个模型。
- DOM节点操作 Attribute: 标签的属性 style, getAttribute, setAttribute 赋值,取值 prototype: js对象的属性,className 像普通的一样 赋值,取值
// 获取dom节点
docuemnt.getElementById('div1') // 元素
document.getElementsByTagName('div') // 集合
document.getElementsByClassName('.container') // 集合
const pList = document.querySelectorAll('p') // 集合
集合就是类数组: length, arr[0]
const p = pList[0]
p.style.width // 获取样式
p.style.width = '100px' // 修改样式
p.className // 获取class
p.className = 'p1' // 修改class
// prototype
p.nodeName // p 获取元素标签名
p.nodeType // 获取元素标签类型
const obj = { a: 111, b: 222} // obj.a 就是prototype
// Attribute
p.getAttribute('data-name')
p.setAttribute('data-name', 'lyr')
p.getAttribute('style')
p.setAttribute('style', 'font-size: 20px;')
- DOM结构操作(一个树的操作) 新增节点 获取父元素 获取子元素 删除节点 遍历,递归什么的
// 新增节点
var div1 = document.getElementById('div1')
// 新增新节点
var p1 = document.createElement('p')
p1.innerHTML = '1111'
div1.appendChild(p1)
// 移动已有节点(原来的地方就没有了)
var p2 = document.getElementById('p2')
div1.appendChild(p2)
// 获取父元素
p1.parentElement
div1.parentElement
// 获取子元素
div1.childNodes // 数组 [text, p#p1, text, p#p2, text] 会把换行,空格等作为文本显示出来
// 把空节点获取出来,nodeType === 3 是text,nodeType === 1是常规的标签节点。nodeName === 'P' nodeName=== '#text'
// 删除节点
const childNodes = div1.childNodes
div1.removeChild(childNodes[1])
题目1: DOM是哪种基本的数据结构? 树 题目2: DOM操作的常用API? 获取DOM节点,以及节点的property和Attribute 获取父节点,子节点 新增节点,删除节点 题目3: DOM节点等attr和property有什么区别? property: 只是一个js对象的属性修改 attribute:是对html标签属性的修改
BOM操作
Browser Object Model:浏览器对象模型 知识点 navigator screen location history
// navigator
var ua = navigator.userAgent
var isChrome = ua.indexOf('Chrome')
// screen
screen.width
screen.height
// location
location.href // 全部
location.protocol // 协议 http
location.host // 域名 XXX.com
locatoin.pathname // /aaa.html
location.search // ?a=1111
location.hash // #mid=100
// history
history.back()
history.forward()
题目1: 检测浏览器的类型 题目2: 解析url的各部分
事件绑定
知识点:
- 通用事件绑定
- 事件冒泡
- 代理
// 一个通用的事件监听函数
var btn = document.getElementById('btn1')
btn.addEventListener('click', function(event) {
console.log('clicked')
})
function bindEvent(elem, type, fn) {
elem.addEventListener(type, fn)
}
var a = document.getElementById('link1')
bindEvent(a, 'click', function(e) {
e.preventDefault() // 阻止默认行为
alert('clicked')
})
// IE兼容问题
attachEvent
// 事件冒泡, 先触发本身节点事件-->父级元素事件--> ... --> body事件
var child = document.getElementsByTagName('a')
var wrap = document.getElementById('wrap')
var body = document.body
e.stopPropatation() // 阻止冒泡,child不会触发wrap和body事件
// 代理(会随时增加子元素)
wrap.addEventListener('click', function(e) {
const target = e.target // 事件在哪儿触发的就是target
if(target.nodeName === 'A') {
alert(target.innerHTML)
}
})
题目1: 编写一个通用的事件监听函数
function bindEvent(elem, type, selector, fn) {
if(fn === null) {
fn = selector
selector = null
}
elem.addEventListener(type, function(e) {
var target
if(selector) {
target = e.targert
if(target.matches(selector)) {
fn.call(target, e)
}
} else {
fn(e)
}
})
}
// 使用代理
var wrap = document.getElementById('wrap')
bindEvent(wrap, 'click', 'a', function(e) {
console.log(this.innerHTML)
})
// 不适用代理
var child = document.getElementById('a1')
bindEvent(child, 'click', function(e) {
console.log(child.innerHTML)
})
题目2: 描述事件冒泡流程 DOM 树形结构 冒泡 阻止冒泡 题目3: 对于一个无限下拉加载图片的页面,如何给每个图片绑定事件 代理
Ajax
知识点:
- XMLHttpRequest
// XMLHttpRequest
// IE低版本使用 ActiveXObject
var xhr = new XMLHttpRequest()
xhr.open('GET', '/api', false) // false说明是异步
xhr.onreadystatechange = function() {
// 这个函数是异步执行的
if(xhr.readyState === 4) {
if(xhr.status === 200) {
alert(xhr.responseText)
}
}
}
xhr.send(null)
- 状态码 readyState: 0:未初始化,还没有调用send()方法 1:载入,已经调用send()方法, 正在发送请求 2:载入完成,send()方法执行完成,已经接收全部响应内容 3:交互,正在解析响应内容 4:完成,响应内容解析完成,可以在客户端调用了 status: 2XX: 成功处理请求,如200 3XX: 需要重定向,浏览器直接跳转 4XX:客户端请求错误,404, 未定义的地址 5XX:服务器端错误,500
- 跨域
前端解决跨域:JSONP
服务器端设置 http header
response.setHeader("Access-Control-Allow-Origin", "http://a.com, http://b.com")跨域:浏览器的同源策略,不允许ajax访问其他域的接口,跨域条件:协议、域名、端口,有一个不同就是跨域 但是有3个标签允许跨域加载资源<img src="">图片提供方可以做一个防盗链的处理,可以用于大点统计,统计网站可能是其他域<link href=""></link>可以用CDN<script src=""></script>加载 http://aaa.bbb.com/xxx.html, 服务器不一定真的有一个xxx.html文件, 服务器可以根据请求,动态生成一个文件,返回 同理<script src="http://aaa.bbb.com/xxx.html"></script>
<script>
window.callback = function(data) {
// 这是我们跨域得到的信息
console.log(data)
}
</script>
<!-- /该srcipt返回 callback({a: 100, b:200, c: 300}) -->
<script src="ttp://aaa.bbb.com/xxx.js"></script>
题目1: 手动编写一个ajax,不依赖三方库 题目2: 跨域的几种实现方式
存储
题目1: 描述一下cookie, sessionStorage 和 localStorage 的区别 cookie: 本身用于客户端和服务器端通信,它本身有本地存储功能是被借用的。 使用 document.cookei = xxx 获取和修改内容 存储量:4KB 所有http请求都带着 sessionStorage 和 localStorage 存储量:5M setItem(key, value) getItem(key) IOS safari隐藏模式下,localStorage.getItem会报错,建议统一使用try-catch封装
开发环境
版本管理--git 模块化 打包工具 上线回滚
版本管理
模块化
打包工具
运行环境
页面渲染 性能优化
浏览器可以通过访问链接来得到页面的内容 通过绘制和渲染,显示出页面的最终样子 整个过程中,我们需要考虑什么问题?
- 页面加载过程
- 性能优化
- 安全性
页面渲染
题目 1: 从输入 url 到得到 html 的详细过程 题目 2: window.onload 和 DOMContentLoaded 的区别
- 加载资源的形式
- 输入 url(或者跳转页面)加载 html(如:http:baidu.com)
- 加载 html 中的静态资源(js 文件,css 文件,图片等等)
- 加载一个资源的过程
- 浏览器根据 DNS 服务器得到域名的 IP 地址
- 向这个 IP 的机器发送一个 http 请求
- 服务器收到,处理并返回 http 请求
- 浏览器得到返回内容
- 浏览器渲染页面的过程
- 根据 html 结构生成一个 DOM Tree
- 根据 css 生成 CSSOM (DOM Tree, 和 CSSOM 都是结构化处理)
- 将 DOM 和 CSSOM 整合成 RenderTree
- 根据 RenderTree 开始渲染和展示
- 遇到
<script>时,会执行并阻塞渲染(因为 js 会改变 DOM,所以,js 执行完之后才会继续渲染 RenderTree)
为什么把 css 文件放在 head 中 ? 因为 html 文件是顺序执行的。如果 css 放在 dom 后面,当渲染 dom 的时候会先按照默认的样式渲染,渲染完后发现还有 css 文件就会再渲染一次。 页面会出现跳动或者卡住的状况,性能太差
为什么把 js 文件在 body 后面 ? 因为 js 可以改变 dom 的结构或者内容。js 又是阻塞渲染,放在 html 前面 dom 还没有渲染,无法操作。js 放下面会让页面更快的出来
window.onload 和 DOMContentLoaded 的区别 ? window.onload:是把所有资源全部加载完才会执行,包括图片视频等 DOMContentLoaded:dom 渲染完即可执行,此时图片视频没有加载完
window.addEventListener('load', function() { 执行该段 js 文件等的时间较长 }) window.addEventListener('DOMContentLoaded', function() { 执行该段 js 文件等的时间较短 })
性能优化
- 多使用内存,缓存或者其他方法
- 减少 cpu 计算,减少网络
入手:
- 加载页面和静态资源
- 静态资源的合并压缩(webpack 打包)
- 静态资源缓存
- 使用 CDN 让资源加载更快(在不同区域访问,cnd 会转到就近的地址访问)
- 使用 ssr 后端渲染,数据直接输出到 html 中(而不是前端写页面,在 ajax 请求数据)
- 页面渲染
- css 放在前,js 放在后面
- 懒加载(图片懒加载,下拉加载更多)
- 减少 dom 查询,对 dom 查询做缓存
- 减少 dom 操作,多个 dam 尽量合并在一起执行
- 事件节流
- 尽早执行操作(DOMContentLoaded)
<!-- 静态资源缓存:通过链接名称控制缓存,名字变了,链接名称才改变
<script src="abc_1.js"></srcript> -->
<!-- 懒加载:图片,先用一个很小的图片占位,等页面加载好之后用js获取真正的图片地址 -->
<img id="myImg" src="small.png" data-realsrc="abc.png" />
<script>
const img1 = document.getElementById('myImg')
img1.src = img1.getAttribute('abc.png')
</script>
<!-- 缓存DOM查询 -->
<script>
// 没有缓存
let i
for (i = 0; i < document.getElementsByTagName('p').length; i++) {}
// 缓存
let i
const pList = document.getElementsByTagName('p')
for (i = 0; i < pList.length; i++) {}
</script>
<!-- 合并DOM插入 -->
<script>
const listNode = document.getElementById('list')
// 要插入10个li标签
// createDocumentFragment不会触发dom操作
const frag = document.createDocumentFragment()
let x, li
for (x = 0; x < 10; x++) {
li = document.createElement('li')
li.innerHTML = 'List item'
frag.appendChild(li)
}
listNode.appendChild(frag)
</script>
<!-- 事件节流 -->
<script>
const textarea = document.getElementById('text');
const timeoutId
textarea.addEventListener('keyup', () => {
if(timeoutId) {
clearTimeout(timeoutId)
}
timeoutId = setTimeout(() => {
// 连着输入的时候不超过100ms就什么都不管
}, 100)
})
</script>
安全性
XSS:跨站请求攻击,在 input 等输入框中插入 script 脚本,写一段程序,
预防:把 < 替换为 < > 替换为 >,前端替换影响性能,后台替换比较好
XSRF:跨站请求伪造,当你在 A 站生成一个订单,别人收到一个有同样订单信息的的图片,点击之后别人就替你付费了 预防:增加验证流程,如指纹,密码,短信验证码
面试技巧
简历: 简单明了,重点突出项目经历和解决方案 博客放入简历中,定期维护更新博客 开源项目
过程中: 加班就像借钱,救急不救穷 千万不要挑战面试官,不要反考面试官(素质) 遇到不会的问题,说出知道的就可以
高级 js 面试
高级基础
ES6 常用语法 - Class Module Promise 原型高级应用 - 结合 JQ 和 zepto 源码 异步全面讲解 - 从 JQ 再到 promise 虚拟dom - 存在价值,如何使用,diff算法 vue - MVVM, vue响应式, 模版解析, 渲染 React - 组件化,jsx, vdom, setState hybrid - 基础,和h5对比,上线流程 通讯 - 通讯原理,JS-Bridge封装
ES6 常用语法
题目1: ES6 模块化如何使用,开发环境如何打包
- 模块化的基本语法
export default {
a: 100
}
import utils from './utils'
export function fn1 {}
export const a = {}
import {fn1, a } from './utils'
- 开发环境配置 babel: 将es6转换成es5 webpack: 打包编译,模块化 rollup: 对vue,react等库项目打包
# babel
npm init
npm install babel-core --save-dev
npm install babel-preset-es2015 babel-preset-latest --save-dev # 两个preset
创建.babelrc文件
{
"preset": ["es2015", "latest"], # 最后一个单词是别名
"plugins": []
}
npm install babel-cli --global
babel --version
创建 index.js文件
运行 babel ./index.js 编译
# webpack
npm install webpack webpack-cli babel-loader@7 --save-dev
配置 webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
path: __dirname,
filename: './build/bundle.js'
},
module: {
rules: [{
test: /\.js?$/,
exclude: /(node_modules)/,
loader: 'babel-loader'
}]
}
}
配置 package.json 中的scripts
"scripts": {
"start": "webpack",
}
运行 npm start
# rollup , 暂时放弃,报错解决不了
npm init
npm i rollup rollup-plugin-node-resolve rollup-plugin-babel --save-dev # rollup
npm i @babel/core babel-plugin-external-helpers babel-preset-latest --save-dev # babel
配置 .babelrc
{
"presets": [
[
"latest",
{
"es2015": {
"modules": false
}
}
]
],
"plugins": [
"external-helpers"
]
}
# 配置 rollup.config.js
import babel from 'rollup-plugin-babel'
import resolve from "rollup-plugin-node-resolve"
export default {
entry: 'src/index.js',
format: 'iife', # amd / es6 / iife / umd
plugins: [
resolve(),
babel({
exclude: 'node_modules/**'
})
],
dest: 'build/bundle.js'
}
# 配置 package.json
"scripts": {
"start": "rollup -c rollup.config.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
- js的众多模块化标准 AMD: require.js, CMD:commonjs ES6: import export
题目2: class 与 js 构造函数的区别 new 一个对象,以构造函数为模版创造一个对象。该对象有方法有属性,在构造函数中操作将要创造的新对象就叫this class 是构造函数的语法糖
// JS 构造函数
function Test(x,y) {
this.x = x;
this.y = y
}
Test.prototype.add = function() {
return this.x + this.y
}
const test = new Test(1,2)
console.log(test.add())
// class 基本语法
// typeof Test // function
// Test === Test.prototype.constructor
// test.__proto__ === Test.prototype
class Test extends React.Component {
constructor(props) {
super(props)
this.state = {
data: []
}
}
render() {
return (<div>hello</div>)
}
componentDidMount() {}
}
class Test {
constructor(x,y) {
this.x = x
this.y = y
}
add() {
return this.x + this.y
}
}
const test = new Test(1,2)
console.log(m.add())
// 继承
function Animal() {
this.eat = function() {
console.log('animal eat')
}
}
function Dog() {
this.bark = function() {
console.log('dog bard')
}
}
Dog.prototype = new Animal()
const hashiqi = new Dog()
class Animal {
constructor(name) {
this.name = name
}
eat() {
console.log(this.name, 'eat')
}
}
class Dog extends Animal {
constructor(name) {
super(name) // 指的就是 Animal, 就是 Animal.prototype.constructor, 只要有extents就要把super() 写上
this.name = name
}
say() {
console.log(this.name, 'say')
}
}
const dog = new Dog('哈士奇')
dog.say()
dog.eat()
题目3: promise 的基本使用和原理 promise解决callback hell new Promise 实例,而且要return new promise 时要传入函数,函数有 resolve reject两个函数 成功时执行resolve() 失败时执行reject() then监听结果
// promise方法定义
function loadImg(src) {
return new Promise(function(resolve, reject) {
const img = document.createElement('img')
img.onload = function() {
resolve(img)
}
img.onerror = function() {
reject()
}
img.src = src
})
}
// promise方法使用
const src = "http://xxx.png"
const result = loadImg(src)
result.then(function(img) {
console.log(img.width)
}, function() {
console.log('failed')
})
// 可以分步骤写很多个回调函数
result.then(function(img) {
console.log(img.height)
}, function() {
console.log('failed')
})
题目4: ES6 其他常用功能
let/const 模版变量 解构赋值 块级作用域 函数默认参数 箭头函数(this指向绑定)
const obj = {a: 10, b: 20, c: 30}
const { a, b } = obj
const arr = [1,2,3]
const [x,y,z] = arr // x === arr[0] y === arr[1] z === arr[2]
for(let item in obj) { // var就可以在外部访问到了
console.log(item)
}
// 编译之后为
for(var _item in obj) {
console.log(_item)
}
console.log(item) // undefined
function (a, b = 0) {}
() => {
this // this指向函数体中最近的一层作用域, 而不是window
}
function fn() {
console.log(this) // { a: 100}
arr.map(item => {
console.log(this) // { a: 100 }
})
arr.map(function(item) {
console.log(this) // window
})
}
fn.call({a: 100})
原型高级应用
原型如何实际应用
// 使用
var $p = $('p')
var $div1 = $('#div1')
// css html 是原型方法
$p.css('color', 'red')
$p.html()
$div1.css('color', 'blue')
$div1.html()
// jq和zepto通过$()方法实例化对象,每个实例都可以从原型中访问到事先定义好的各种方法
// zepto(从下往上读)
(function(window) {
var zepto = {}
function Z (dom, selector) {
var i, len = dom ? dom.length : 0
for(i = 0; i<len; i++) {
this[i] = dom[i]
}
this.length = len
this.selector = selector || ''
}
zepto.Z = function(dom, selector) {
// 出现了new关键字
return new Z(dom, selector)
}
zepto.init = function(selector) {
// 弱化了源码,只针对原型
var slice = Array.prototype.slice
const dom = slice.call(document.querySelectorAll(selector))
return zepto.Z(dom, selector)
}
var $ = function(seletor) {
return zepto.init(seletor)
}
window.$ = $
$.fn = {
constructor: zepto.z,
css: function(key, value) {},
html: function(value) {
return 'html: 这是一个字符串'
}
}
Z.prototype = $.fn
})(window)
// jq
(function(window) {
var jQuery = function(selector) {
return new jQuery.fn.init(selector)
}
jQuery.fn = {
css: function() {console.log('css')},
html: function() {return 111}
}
// 定义构造函数
var init = jQuery.fn.init = function(selector) {
var slice = Array.prototype.slice
const dom = slice.call(document.querySelectorAll(selector))
var i, len = dom ? dom.length : 0
for(i = 0; i<len; i++) {
this[i] = dom[i]
}
this.length = len
this.selector = selector || ''
}
init.prototype = jQuery.fn
window.$ = jQuery
})(window)
原型如何体现它的扩展性(jq,zepto 插件机制) 为什么要将原型赋值给 $.fn 或者 jQuery.fn ? 因为要扩展插件
// 一个简单的插件
$.fn.getNodeName = function() {
return this[0].nodeName
}
// 给$.fn增加一个新的属性,增加一个新的功能(一个插件)
// 为什么要加在 $ 上面,而不是别的什么内部变量,因为只有$会暴露在window上
// 将插件扩展统一到$.fn.xxx上面
异步全面讲解
题目1: 什么是单线程和异步有什么关系
单线程:只有一个线程,只能做一件事 原因:避免DOM渲染冲突(解决方案:异步) 浏览器需要渲染DOM, js可以修改DOM结构 js执行的时候,浏览器DOM渲染会停止 两段js也不能同时执行(都修改DOM就冲突了) webworker支持多线程,但是不能访问DOM 异步缺点:没有按照书写顺序执行,回调不容易模块化
题目2: 什么是 event-loop 事件轮询 异步的实现方案:event-loop 同步代码直接执行,异步函数放在 异步队列 中,同步代码执行完,轮询执行异步队列的函数 异步队列何时被放入:立即被放入,指定一段时间被放入,ajax请求成功之后被放入
题目3: 是否用过jQuery的Deferred(延迟) jq1.5之后推出 Deferred deferred: 无法改变js异步和单线程本质,只能从写法上杜绝 callback 这种形式,但是解耦了代码(语法糖),体现了开放封闭原则 promise标准: 任何不符合标准的东西,终将会被用户抛弃(全球的项目经理认证)
- 状态变化:pending, fulfilled, rejected 初始状态pending 状态变化:不可逆 pending --> fulfilled pending --> rejected
- then Promise实例必须实现then这个方法 then()必须接收两个函数作为参数,成功失败的回调函数 then()必须返回一个Promise实例,如果没有显式返回,默认就会返回then前面的那个实例
// jq 1.5之前, 现在也可以这么写,但是1.5之前只支持这么写
var ajax = $.ajax({
url: 'data.json',
success: function() {
console.log(1)
},
error: function() {
console.log(0)
}
})
console.log(ajax) // 返回一个 XHR 对象
// jq 1.5之后
var ajax = $.ajax('data.json')
ajax.done(function() {
console.log(1)
}).fail(function() {
console.log(0)
}).done(function() {
console.log(222)
})
ajax.then(function() {
console.log(1)
}, function() {
console.log(0)
}).then(function() {
console.log(1)
}, function() {
console.log(0)
})
console.log(ajax) // 返回一个 deferred 对象
// deferred 应用1
var wait = function() {
var task = function() {
console.log('执行完成')
// 1
// 2
// 3
}
setTimeout(task, 200)
}
wait()
// 在执行完task之后,还要进行一些复杂的操作
function waitHandle() {
var dtd = $.Deferred()
// 将 Deferred 适量 dtd, 进行一系列封装之后,返回
var wait = function(dtd) {
var task = function() {
console.log('执行完成')
dtd.resolve()
// dtd.reject()
}
setTimeout(task, 2000)
return dtd
}
// 返回值是 dtd
return wait(dtd)
}
var w = waitHandle()
w.then(function() {
console.log('ok1')
}, function() {
console.log('error1')
})
w. then(function() {
console.log('ok2')
}, function() {
console.log('error2')
})
// dtd的API分为两类,用意不同
第一类:dtd.resolve dtd.reject(主动触发)
第二类:dtd.then dtd.done dtd.fail(被动受监听)
这两类要分开,在外部不能主动触发
var w = waitHandle()
w.reject() // 主动执行完之后,后面的then函数将执行失败的回调
w.then(function() {
console.log('ok')
}, function() {
console.log('error')
})
// 解决方法 promise(), 不再返回dtd实例,而是返回promise() 里面只有被动触发的方法
function waitHandle() {
var dtd = $.Deferred()
var wait = function(dtd) {
var task = function() {
console.log('执行完成')
dtd.resolve()
// dtd.reject()
}
setTimeout(task, 2000)
return dtd.promise() // 只有被动触发的方法, 外部不能调用resolve,reject
}
return wait(dtd)
}
题目4: promise 的标准
// 异常捕获
result.then(function() {
// 只处理成功的函数
}).then(function() {
}).catch(function(error) {
// 统一捕获异常
// 语法报错,和reject报错都可以捕获
})
// 多个串联(A请求的结果是B请求的参数)
var resultA = loadImg(src1)
var resultB = loadImg(src2)
resultA.then(function(dataA) {
console.log(dataA)
return resultB
}).then(function(dataB) {
console.log(dataB)
}).catch(function(ex) {
console.error(ex)
})
// promise.all() 全部完成, promise.rece() 只有一个完成
// Promise.all Promise.rece 接收一个promise对象数组,待全部完成之后统一执行success
Promise.all([result1, result2]).then(datas => {
console.log(datas, datas[0], datas[1])
})
Promise.race([result1, result2]).then(data => {
// 最先执行完成的data
console.log(data)
})
题目5: async/await 的使用(和promise的区别、联系) then只是将callback拆分了 async/await 是可以用同步的方法写 await 后面必须跟一个Promise实例 需要 babel-polyfill
const load = async function() {
const result = await loadImg('http://xxx.png')
}
框架原理
虚拟 DOM - 存在价值,如何使用,diff 算法 MVVM vue - MVVM,vue 响应式,模版解析,渲染 组件化 React - 组件化,JSX,vdom, setState
虚拟 DOM(virtual dom)
vdom是一类库 vDom: 用js模拟DOM结构,DOM变化的对比放在js层来做(图灵完备的语言),提高重绘性能 图灵完备的语言:能实现各种逻辑的语言,能做到判断、循环、递归,能实现无限循环无限执行的语言,能实现高复杂逻辑的语言,能实现任何数学算法的语言。 题目1: vdom是什么,为何用会存在vdom? DOM操作是昂贵的,js运行效率高 尽量减少dom操作,不要推到重来
<ul id="list">
<li class="item">item1</li>
<li class="item">item2</li>
</ul>
// 用js模拟
{
tag: 'ul',
attrs: {
id: 'list'
},
children: [{
tag: 'li',
attrs: {
className: 'item', // class js的关键字, 所以用className
children: ['Item 1']
}
}, {
tag: 'li',
attrs: {
className: 'item',
children: ['Item 2']
}
}]
}
// 用jq和vDOM分别实现:将一个对象数组展示成一个表格,当修改数组信息,表格也跟着修改。
<div id="container"></div>
<button id="btn-change">change</button>
const data = [{
name: '张三',
age: 20
}, {
name: '李四',
age: 21
}, {
name: '王五',
age: 22
}]
/*jq实现*/
// 每次改变table的整个都会改变,性能不好
// 渲染函数
function render(data) {
var $container = $('#container')
// 清空容器
$container.html('')
// 拼接table
var $table = $('<table>')
$table.append($('<th><td>name</td><td>age</td></th>'))
data.forEach(item => {
$table.append($(`<tr><td>${item.name}</td><td>${item.age}</td></tr>`))
})
// 渲染到页面
$container.addend($table)
}
$('#btn-change').click(function() {
// 修改某一行数据
data[1].age = 30
data[2].name = '朱六'
// 再重新渲染 re-render
render(data)
})
// 页面加载完立即执行(初次渲染)
render(data)
题目2: vdom 如何使用,核心API是什么? snabbdom: 一个vdom的技术实现库 h('标签名', {...属性...}, [...子元素...]) h('标签名', {...属性...}, '...') patch(container, vnode) patch(vnode, newVnode)
<ul id="list">
<li class="item">item1</li>
<li class="item">item2</li>
</ul>
var vnode = h('ul#list', {}, [
h('li.item', {}, 'Item1'),
h('li.item', {}, 'Item2'),
])
patch(doucment.getElementById('container'), vnode)
var newVnode = h('ul#list', {}, [
h('li.item', {}, 'aaaaa'),
h('li.item', {}, 'bbbbb'),
])
patch(vnode, newVnode) // 找出新的vnode和旧的有什么区别,然后替换
// 应用
// 引入snabbdom文件 有 snabbdom.js snabbdom-class.js snabbdom-props.js snabbdom-style.js snabbdom-eventlist.js h.js 这一堆文件
// 引入之后 window.snabbdom就已经有值了
var snabbdom = window.snabbdom
// 定义path
var patch = snabbdom.init([
snabbdom_class,
snabbdom_props,
snabbdom_style,
snabbdom_eventlisteners
])
// 定义h
var h = snabbdom.h
// 生成 vnode
var vnode = h('ul#list', {}, [
h('li.item', {}, 'Item1'),
h('li.item', {}, 'Item2'),
])
patch(doucment.getElementById('container'), vnode)
document.getElementById('btn-change').addEventListener('click', function() {
// 生成 newVnode
var newVnode = h('ul#list', {}, [
h('li.item', {}, 'Item1'),
h('li.item', {}, 'bbb'),
])
patch(vnode, vnode)
})
/*重做demo*/
var vnode
function render(data) {
var newVnode = h('table', {}, data.map(item => {
var tds = []
var i
for(i in item) {
if(item.hasOwnProperty(i)) {
tds.push(h('td', {}, item[i] + ''))
}
}
return h('tr', {}, tds)
}))
if(vnode) {
// re-render
patch(vnode, newVnode)
} else {
// 初次渲染
patch(container, newVnode)
}
// 存储当前newVode结果
vnode = newVnode
}
// 初次渲染
render(data)
// 更改变化
document.getElementById('btn-change').addEventListener('click', function() {
data[1].age = 30
data[2].name = '朱六'
// re-render
render(data)
})
题目3: 了解 diff 算法(vdom 的核心算法)
- 什么是diff算法 linux命令: diff log1.txt log2.txt; 返回两个文件中哪里不一样 git命令: git diff ./index.js; 修改前后的不同 对比两个vDom的异同
- vdom为何使用diff算法 dom操作要尽量减少,找出必须要更新的,其他不变,找出的过程就要diff算法
- diff算法的实现流程 patch(container, vnode) 递归,vdom --> 真实的dom节点 patch(vnode, newVnode) newVnode --> vnode,递归对比,真实的dom节点替换
// patch(container, vnode)
function createElement(vnode) {
const { tag, attrs, children } = vnode
if(!tag) {
return null
}
// 创建元素
const elem = document.createElement(tag)
// 属性
let attrName
for(attrName in attrs) {
if(attrs.hasOwnProperty(attrName)) {
elem.setAttribute(attrName, attrs[attrName])
}
}
// 子元素
children.forEach(childVnode => {
// 给elem添加子元素
createElement(childVnode)
})
// 返回真实的DOM元素
return elem
}
// patch(vnode, newVnode)
// 根节点不会变,递归比较变化
function updateChildren(vnode, newVnode) {
const children = vnode.children || []
const newChildren = newVnode.children || []
children.forEach((childVnode, index) => {
const newChildVnode = newChildren[index]
if(childVnode.tag === newChildVnode.tag) {
// 一样,就深层次对比,递归
updateChildren(childVnode, newChildVnode)
} else {
// 不一样,就替换, 替换需要真实的dom节点
replaceNode(childVnode, newChildVnode)
}
})
}
function replaceNode(vnode, newVnode) {
// 替换需要真实的dom节点
const elem = vnode.elem
const newElem = createElement(newVnode)
// dom替换
}
MVVM vue
BootCDN 引入jq等框架
题目1: 使用jq和使用框架的区别( vue 或者 react )
数据和视图分离(解耦,开放封闭原则):jq中都混在一起了,var $li = $('<li>${title}</li>'), ul只是一个空壳。
以数据驱动视图,只关心数据变化,DOM操作被封装:只改数据,不用管视图。jq是直接干预了dom,vue是只更改数据。DOM操作被封装。
// jq 实现todo-list
<input type="text" name="" id="txt-title"/>
<button id="btn-submit">submit</button>
<ul id="ul-list"></ul>
const $txtTitle = $('#txt-title')
const $btnSubmit = $('#btn-submit')
const $ulList = $('#ul-list')
$btnSubmit.click(function() {
const title = $txtTitle.val()
if(!title) {
return
}
var $li = $(`<li>${title}</li>`)
$ulList.append($li)
$txtTitle.val('')
})
// vue 实现todo-list
<div id="app">
<input v-model="title"/>
<button v-on:click="add">submit</button>
<ul>
<li v-for="item in list">{{item}}</li>
</ul>
</div>
const vm = new Vue({
el: '#app',
data: {
title: '',
list: []
},
methods: {
add: function() {
this.list.push(this.title)
this.title = ''
}
}
})
题目2: 如何理解 MVVM MVC: model: 数据 view: 视图,界面 controller: 控制器,逻辑处理 用户 --> view --> controller --> model --> view 用户 --> controller --> model --> view
MVVM: view: 视图、模版(视图和模型是分离的) model: 数据,模型,js对象 vm: viewModel, vue实例,链接view、model。view 通过事件监听操作model(v-on 和 methods), model通过数据绑定来操作view MVVM三要素:
- 响应式:vue如何监听到data的每个属性变化? 我们只是 this.title = '' this.list.push(this.title)
- 模版引擎:vue的模版如何被解析,指令如何处理?模版中并不是真正的html
- 渲染:vue的模版如何被渲染成html? 以及渲染过程。
题目3: vue 如何实现响应式 关键是 Object.defineProperty 将data的属性代理到vm上
- 什么是响应式? 修改data属性后,vue立刻监听到 data属性被代理到 vm 上
// data.name, 被代理到vm上了, vm.name 就是data.name
var vm = new Vue({
el: '#app',
data: {
name: 'lyr',
age: 20
}
})
- Object.defineProperty IE9以上
// 将对象属性到获取和设置都变成函数, 对象被访问时可以监听到,被设置到时候可以监听到
var obj = {}
var _name = 'lyr'
Object.defineProperty(obj, 'name', {
get: function() {
console.log('get')
return _name
},
set: function(newVal) {
console.log('set')
_name = newVal
}
})
console.log(obj.name) // 可以监听到
obj.name = '李月茹' // 可以监听到
- 模拟
// 模拟vue怎么监听name,age
// var vm = new Vue({
// el: '#app',
// data: {
// name: 'lyr',
// age: 20
// }
// })
var vm = {}
var data = {
age: 10,
name: 'lyr'
}
var key, value
for(key in data) {
// 命中闭包,新建一个函数,保证key的独立作用域
(function (key) {
// 将data属性代理到vm上, data是实例化的时候传进来到参数
Object.defineProperty(vm, key, {
get: function() {
console.log('get')
return data[key]
},
set: function(newVal) {
console.log('set')
data[key] = newVal
}
})
})(key)
}
题目4: vue 如何解析模版
- 模版是什么? 本质: 字符串 有逻辑: 如 v-if v-for等 与html格式很像, 但是有很大区别(html是静态的,没有逻辑) 最终要转换为html来显示 模版最终必须转化成js代码,因为:有逻辑(v-if v-for)必须使用js才能实现(图灵完备);转换成html渲染页面,必须用js才能实现;因此模版最重要要转换成一个js函数(render函数)
- 模版:字符串,有逻辑,嵌入js变量...
- 模版必须转换成js代码(有逻辑,渲染html,js变量)
- render函数什么样子
- render函数执行返回vnode
- updateComponent
// with的用法
var obj = {
name: 'lyr',
age: 20,
getAddress: function() {
console.log('aaa')
}
}
// 不用with
function fn() {
console.log(obj.name)
console.log(obj.age)
obj.getAddress()
}
fn()
// 使用with, with有解构的作用
function fn() {
with(obj) {
console.log(name)
console.log(age)
getAddress()
}
}
- render函数 模版中所有信息都包含在render函数中 this即vm price 即this.price即vm.price,即data中的price _c即this._c即vm._c
<div id="app">
<p>{{price}}</p>
</div>
// 模版最终转化为
function render() {
with(this) {
// this就是vm这个vue实例,_c === vm._c; _v === vm._v; _s === vm._s
return _c(
'div',
{
attrs: {"id": "app"}
},
[
_c('p', [_v(_s(price))]) // _s 就是toString, _v创建文本节点,_c创建标签
]
)
}
}
// 从哪里可以看到render函数: 源码中搜索code.render, 打印,就可以找到模版的render函数
// 复杂一点儿的例子,render函数是什么样子的
// v-if v-for v-on都是怎么处理的
// vue2.0开始支持预编译,开发环境写模版,经过工具编译打包,生产环境就是js代码,即上面的render函数。
// react 组件化 jsx模版,经过编译生成js代码,jsx语法已经标准化。
with(this) {
return _c(
'div',
{
attrs: {"id": "app"}
},
[
_c('div', [
_c(
'input',
{
directives: [{
name: 'model',
rawName: 'v-model',
value: (title),
expression: 'title'
}],
domProps: {
value: (title)
},
on: {
input: function($event) {
if($event) {
return title = $event.target.value // vm.title = $event.target.value
}
}
}
}
),
_v(""),
_c("button", {
on: {
click: add // vm.add
}
}, [_v('submit')])
]),
_v(""),
_c("div", [
_c('ul', _l((list), function(item) { // vm._l 是个for循环
return _c('li', [_v(_s((item)))])
}))
])
]
)
}
- render函数和vdom vdom核心API: h函数,patch函数 vm._c其实相当于 snabbdom 中的h函数 render函数执行之后返回的是 vnode updateComponent 实现了vdom的patch, 新旧对比 页面首次渲染执行updateComponent data每次修改都执行updateComponent
function updateComponent() {
// vm._render 即上面的render函数,返回 vnode
vm._update(vm._render())
}
vm._update(vnode) {
const prevVnode = vm._vnode
vm._vnode = vnode
if(!prevVnode) {
vm.$el = vm.__patch__(vm.$el, vnode)
} else {
vm.$el = vm.__patch__(prevVnode, vnode)
}
}
题目5: 介绍 vue 的实现流程 第一步: 解析模版成render函数(h 函数)
- 模版打包的时候就解析成render函数了, 创建vnode的过程,添加属性,事件,解析指令等等。
- with的用法
- 模版中的所有信息都被render函数包含
- 模版中用到的data中的属性,都变成了js变量
- 模版中的v-model v-for v-on 都变成了js逻辑
- render 函数返回 vnode 第二步: 响应式开始监听
- Object.defineProperty
- 将data的属性代理到vm上(with) 第三步: 首次渲染,显示页面,且绑定依赖
- 初次渲染,执行updateComponent执行vm.render()
- 执行render函数,会访问到vm.list vm.title
- 会被响应式的get方法监听到,为什么不监听set(data中有很多属性,用到到走get,没用到不走。未走get的属性,set的时候也无需关心,避免不必要到重复渲染)
- 执行updateComponent,会走到vdom的patch方法
- patch将vnode渲染成DOM, 初次渲染完成 第四步: data属性变化,触发rerender
- 修改属性
this.title = 'aaa',被响应式的set监听 - set中执行 updateComponent
- updateComponent 重新执行vm._render()
- 生成vnode和preVnode,通过patch进行对比
- 渲染到html中
组件化 React
react以及组件化的一些核心概念 react的实现流程
// to-do-list
class List extends component {
render() {
const list = this.props.data
return <ul>
{
list.map((item, index) => <li key={index}>{item}</li>)
}
</ul>
}
}
class Input extends component {
state = {
title: ''
}
changeValue(e) {
this.setState({
title: e.target.value
})
}
submit() {
this.props.addTitle(this.state.title)
this.setState({
title: ''
})
}
render() {
const { title } = this.state
return <div>
<input value={title} onChange={this.changeValue}/>
<button onClick={this.submit}>submit</button>
</div>
}
}
class Todo extends component {
state = {
list: ['a', 'b', 'c'],
}
addTitle(title) {
const currentList = this.state.list
this.setState({
list: currentList.concat(title)
})
}
render() {
const { list } = this.state
return <div>
<Input addTitle={this.addTitle}></Input>
<List data={list}></List>
</div>
}
}
题目1: 对组件化的理解
- 封装 把视图,数据,变化逻辑(数据驱动视图变化)封装起来:把我要的数据给我,其他就不用管了
- 复用 props 传递数据: 不同的config展示不同的效果,复用
题目2: jsx 是什么 jsx和vue的模版一样,最终要渲染成html jsx语法无法被浏览器解析 jsx解析成js才能运行 jsx成为一个独立到标准:说明本身功能已经完备,和其他标准兼容,扩展性没有问题
jsx其实是语法糖,开发环境将jsx编写成js代码,jsx的写法降低了学习成本和编码工作量,但是加大了debug工作量
编译jsx为js的插件
插件:npm i --save-dev babel-plugin-transform-react-jsx
配置 .babelrc:{ "plugins": ["transform-react-jsx"]}
执行命令:babel --plugins transform-react-jsx demo.jsx
// jsx
<div>
<img src="avatar.png" className="profile"/>
<h3>{[user.firstName, user.lastName].join(' ')}</h3>
</div>
// jsx会解析成
React.createElement('div', {id: 'div1'}, child1, child2, ...)
React.createElement('div', {id: 'div1'}, [...])
React.createElement('div', null,
React.createElement('img', { src: 'avatar.png', className: 'profile'}),
React.createElement('h3', null, [user.firstName, user.lastName].join(' ')),
)
React.createElement('ul', null, [1,2,3].map((item, index) => {
return React.createElement('li', {key: index}, item)
}))
// 所以,react组件中必须引入React
题目3: jsx 和 vdom 的关系 为何需要vdom: jsx就是模版,最终要渲染成html,数据驱动视图,需要用vdom的方式渲染 React.createElement 和 h 都生成vnode, h函数第一次渲染是一个dom节点,React.createElement还可以是自定义组件 何时 patch:初次渲染 ReactDOM.render + 修改state后的re-render(正好符合vdom的应用场景)
- 初次渲染 -
ReactDOM.render(<App />, container), 会触发patch(container, vnode),container === decument.getElementById('app') - rerender - setState, 会触发
patch(vnode, newVnode)
自定义组件的解析 自定义组件编译,第一个传入的是构造函数。 初始化实例然后执行render
- div直接渲染成
<div>即可,vdom可以做到 - 自定义组件(class),vdom默认不认识
- 因此,Input等自定义组件组件定义的时候必须声明render函数
- 根据props初始化实例,然后执行实例的render函数。render函数返回的还是vnode对象
import Input from './input/index.js'
import List from './List/index.js'
function render() {
render() {
const { list } = this.state
return <div>
<Input addTitle={this.addTitle}></Input>
<List data={list}></List>
</div>
}
}
// 编译为
import Input from './input/index.js'
import List from './List/index.js'
function render() {
return React.createElement('div', null,
React.createElement('Input', {addTitle: this.addTitle.bind(this)} ),
React.createElement('List', {data: this.state.list} ),
)
}
React.createElement('List', {data: this.state.list} )
// 等价于
var list = new List({data: this.state.list}) // 生成实例,返回实例的render函数
var vnode = list.render()
// 每个组件都返回一个render函数,不管有多少层组件,都会一层一层的执行render函数,最终转化成html
题目4: 简述 setState 的过程
setState的异步(为什么需要异步)
- 可能会一次执行多次setState。
- 你无法规定,限制用户如何使用setState
- 没有必要每次setState都重新渲染,考虑性能
- 即便是每次重新渲染,用户也看不到中间效果(js执行的时候,dom渲染是停止的) vue修改属性也是异步 set中执行updateComponent是异步的
setState的过程
// setState的异步
addTitle(title) {
const currentList = this.state.list
console.log(this.state.list) // []
this.setState({ // 异步
list: currentList.concat(title)
})
console.log(this.state.list) // []
}
addTitle(title = 'aaa') {
const currentList = this.state.list
this.setState({
list: currentList.concat(title)
})
this.setState({
list: currentList.concat(title + 1)
})
this.setState({
list: currentList.concat(title + 2)
})
console.log(this.state.list) // [aaa2], 直接执行最后一个,前面两个没有执行
}
// setState的过程
addTitle(title) {
const currentList = this.state.list
this.setState({ // 异步
list: currentList.concat(title)
}, () => {
// 每个组件都extend React.Component, Component父类中定义的方法
// setState中会默认执行这个回调
this.renderComponent()
})
}
// 模拟Component
class Component {
constructor(props) {}
renderComponent() {
const prevVnode = this._vnode
const newVnode = this.render()
patch(prevVnode, newVnode)
this._vnode = newVnode
}
}
题目5: 如何比较 react 和 vue 两者的本质区别:
- vue - 本质是 MVVM 框架,由 MVC 发展而来
- React - 本质是前端组件化框架,由后台组件化发展而来 看模版和组件化的区别:
- vue - 使用模版(最初由angular提出)
- React - 使用JSX(已经标准化)
- 模版语法上 - 更倾向 jsx, JSX只有一个规则,大括号中可以放变量表达式
- 模版分离上 - 更倾向 vue, react中模版和js混合在一起,未分离
- react组件化 - react本身就是组件化,没有组件化就不是react
- vue组件化 - vue也支持组件化,不过是在MVVM上的扩展 两者的共同点:
- 都支持组件化
- 都是数据驱动视图 选型: 国内使用,首推vue。文档易读易学,社区够大 团队水平较高,推荐react。组件化和jsx
app 混合开发
hybrid - 基础,和 H5 的对比,上线流程(前端和 APP 一起开发) 前端客户端通讯:通讯原理,JS-Bridge 封装
hybrid
APP 中都要内嵌很多页面 server 和客户端要了解 对协议和标准要特别重视
使用场景 NA:体验要求极致,变化不频繁(如头条的首页) hybrid: 体验要求高,变化频繁(头条的新闻详情页) h5:体验无要求,不常用(如举报,反馈等)
- hybrid 是什么,为何用 hybrid ? 快速迭代,无需审核
实现
- 前端做好静态页面,将文件交给客户端;
- 客户端将之以文件的形式存进 app 中;
- 客户端在一个 webview 中使用 file 协议加载静态页面
优点
- 快速的迭代更新,无需 app 审核,因为原生的开发可以拿到地理位置,用户信息等高权限的信息,所以要审核
- 体验更流畅,和 NA 基本一致
- 减少开发成本,双端一套代码
webview(小浏览器) ?
- 是 app 的一个组件,app 可以有 webview,也可以没有。
- 用于加载 h5 页面,即一个小型的浏览器内核
file 协议
- file 协议(file://):加载本地的文件是 ,快,hybrid 要用 file 协议。
- http 协议(http://):网络加载,慢
- hybrid 如何更新上线 ?
- 如何更新每个手机上 app 的本地文件:用户每次打开 app,客户端都去 server 下载最新的静态文件(我们,分版本号,压缩文件上传)
- hybrid 和 h5 有何区别 ?
hybrid:
- 体验要求高,变化频繁(头条的新闻详情页)
- 开发成本高,联调,测试,查 bug 都比较麻烦
- 运维成本高
h5:体验无要求,不常用(如举报,反馈等,单次用的)
- js 如何与客户端如何通信 ? js 和客户端通讯的基本形式:调用能力,传递参数,监听回调。(webview ---参数,callback--> 客户端) schema 协议:用于前端和客户端的通讯的约定,每个 app 的 schema 都不同,可以自己定义
前置问题
- 微信的 js-sdk: 前端和客户端的一个中间层,让 html 可以调用手机底层的东西(微信公众号等中使用)
- js 和客户端通讯的基本形式 (webview ---参数,callback--> 客户端)
- schema 协议简介和使用
- schema 使用的封装
- 内置上线:黑客看不到协议
新闻详情页适用 hybrid,前端如何获取新闻内容 ?
- 不能用 ajax 获取。第一跨域,第二速度慢
- 客户端获取新闻内容,然后 js 通讯拿到内容,再渲染
- 客户端可以提前获取
前端客户端通讯
js 基本知识点
js 概观
一种基于对象和事件驱动的客户端脚本语言。
注释:
// , /\*\*/变量:区分大小写。声明变量:var
标识符:变量,函数,属性的名字,或者函数的参数。
- 组成规则:
- 字母、数字、下划线或$构成;
- 不能数字开头
- 不能用关键字和保留字
- 组成规则:
数据类型:
- undefined,null(空指针,准备放对象),boolean,number(NaN),string, object,symbol
- NaN:和任何值都不相等,任何涉及 NaN 的操作(NaN/10)都返回 NaN
操作符:
- typeof 语法:typeof(变量);
- 返回值:string 类型,可能是 string、number、Boolean、object、undefined、function
方法:
- number
- isNaN(n):判断一个参数是否是非数字,返回 true,false
- Number(a):把非数字转为数字
- parseInt(a):把非数字转为数字(转换空字符串返回 NaN,提供两个参数 parseInt("0f0", 16))
- parseFloat(a):把非数字转为数字(第一个点有效,其余无效)
- string
- String():将任何类型转为字符串
- str.toString():将 str 转换为字符串,返回 str 的一个副本,将它赋给变量
- number
类型转换:
- 转为 Boolean
- 0 之外的数字都 true;
- " "之外的字符都为 true;
- null,undefined 都为 false;
表达式和操作符
表达式:将同种类型的数据(常量,变量,函数等)用运算符按一定的规则连起来
操作符:
算数操作符:返回的类型都是 number
- ++a: a = a + 1;
- a++:
逻辑操作符:
- 赋值操作符:= , += , -= , *= , /= , %= ;
- 比较操作符:
- 三元操作符:条件 ?执行代码 1 :执行代码 2(如果条件成立执行 1,否则执行 2)
- && || !
语句
if(boolean) {}
if(表达式) {} else {}
if(表达式) {} else if {} else {}
// let val = prompt('请输入'); // 有返回结果。带确定按钮,点取消,值为null
// new Date().getDay() // 获取星期,返回值number(0-6),周日是0
switch(判断某个变量) {
case value:
document.write('dddd')
break;
case value1:
case value2:
console.log('aaa')
break;
default: console.log('dfskdfsk')
}
for(语句1;语句2;语句3) { 被执行代码 }
// 语句1:循环代码块开始前执行
// 语句2:代码块循环条件
// 语句3:代码块执行后执行
// for嵌套:外层为假时内层不执行;先执行外层再执行内层,直到内层条件为假时再返回外层执行
// for适合已知循环次数的循环体,while适合未知
while(条件) { 被执行代码 }
//至少要执行一次
do { 被执行代码 } while(j < 10)
break:彻底退出循环体
continue:结束本次循环,继续下一次
// 打印所有0-50之间除20之外的5的倍数
for() {
if(n === 20) {
continue // 本次循环不执行
}
console.log(n)
}
函数
- 定义:function 函数名([参数]) { 可执行代码 }
- 调用:函数名([参数])
- 返回值:return
- 参数:
- arguments 在函数内部参数是由一个叫 arguments 的类数组管理的。
- 可用[]来访问每一个参数(0 开始的索引),
- 还有一个 length 属性来确定参数个数
function arrAvg() {
let sum = 0
let i
const len = arguments.length
for (i = 0; i < len; i++) {
sum += arguments[1]
}
return sum / len
}
// 有返回必须有接收
const req = arrAvg(1, 2, 3, 4)
内置对象
Array 创建 const test = new Array(10);如果知道长度就写上 [{name: 1111}, 'dddd']// 字面量,可以保存任意类型的值 读取 ['aaa', 'bbb', 'ccc'][1] // 'bbb' 赋值 test[0] = 1 test[1] = 'ddd' 长度 arr.length // number arr.length = 3 // 长度超过 3 的被删除。通过设置 length 可以从数组的末尾移除项或者向数组中添加新项 arr[99] = 'xxx' // 长度为 100 栈方法 push() // 在数组尾部顺序添加 1 或多个值,返回值为新的长度 number unshift() // 在数组开头添加 1 或多个值,返回值为新的长度 number pop() // 删除数组最后一个元素,返回值为删除的那个元素 shift() // 删除开头的值,返回值为删除的那个元素 转换方法 join() // 将数组转换为字符串,返回值为字符串。无参数默认用逗号隔开。参数为分隔符。 可以用''分开 reverse() // 颠倒数组中元素的顺序 sort() // 对数组里的元素排序,返回值是排序后的数组。 可以接收一个比较函数作为参数 即使数组中每一项都是数值,sort()比较的也是字符串(比较的是字符串) arr.sort((a, b) => a-b) 连接和截取 arr1.concat(arr2, arr3 ……) // 返回值为连接好的新数组 arr.slice(start, [end]) // 从已有的数组中返回选定的元素(start,end 是数组的下标,截取的元素不包含 end) 无 end,则从 start 到结束。 slice 有一个负数,则该处的索引即为:数组长度加上该数
// const a = [1, 3, 4] 实现b数组对a数组的拷贝 b = [].concat(a) // 性能较高 b = a.slice(0)删除、插入和替换
String
Math
Date
DOM 基础
// 查找
// 可以是document也可以是别的DOM元素
const box = document.getElementById('box')
const tags = document.getElementsByTagName('li') // 返回数组
const ulLi = document.getElementById('list1').getElementsByTagName('li')
// 设置样式
ele.style.styleName = styleValue
box.style.color = '#f00'
box.style.fontWeight = 'bold'
// ele: dom元素
// styleName:样式名称,驼峰
// styleValue: 样式值
// 属性
ele.innerHTML // 获取ele元素开始标签和结束标签之间的HTML和文本内容
ele.innerHTML = '' // 设置
ele.className // 获取ele元素的class属性
ele.className = 'cls' // 设置ele元素的class属性 (如果之前有,会替换)
// 操作属性
ele.getAttribute('attribute') // attribute:要获取的html属性,如id,type
// 如果是DOM元素(标签)自带的属性直接用点获取:box.id, box.align。class除外,用box.className
// 如果是你自己设置的用getAttribute("attribute")方法
ele.setAttribute('attribute', value) // 有兼容性
// attribute:要设置的属性名
// value为attribute的值,如果是字符串要加引号
box.setAttribute('isRight', false)
ele.removeAttribute('attribute') // attribute:要删除的属性名称
DOM 事件
事件:文档或浏览器窗口中发生的一些特定的交互瞬间
HTML 事件:
<tag 事件="执行脚本"></tag> // 在HTML元素上绑定事件 // “执行脚本”可以是一个函数的调用,一定要加括号 // 在事件触发的函数中,this是对该DOM对象的引用 // <div onmouseover="fn(this)" onmouseout="out(this, '#00f')"></div> function fn(_this) { _this就是DOM本身 }DOM0 级事件:先获取 DOM 然后在 DOM 对象上绑定事件
ele.事件 = 执行脚本 // 执行脚本可以是一个匿名函数也可是一个函数的调用 // const btn = document.getElementById("btn") // btn.onClick = function() { // 给按钮绑定事件,this是对该DOM元素的引用 if(this.className==="box1") { this.className = "box2" } } // btn.onclick=clickBtn; // 只写函数名,不写括号,因为加括号就会立即执行事件类型:
onload: window.onload=function() {} // unload
onchange:域的内容发生改变时发生(select) // 当菜单里的内容发生变化的时候执行脚本
<selece name="" id="menu"> <option value="#f00" selected></option> <option value="#ff0"></option> </selece> <script> // 选中的option的值 menu.onchange = function() { if (!this.value) return console.log(this.value, menu.options[menu.selectedIndex].value) } </script>onfoucs: 获得焦点时触发(input 等框)
// 获取表单元素的值 input.onblur = function() { console.log(this.value) tip.innerHTML = '<img src="img/right.png">' }onblur:失去焦点时触发(input 等框)
onsubmit: 表单中的确认按钮被点击 不是加在按钮上的而是加在表单上的
onmousedown:鼠标在元素上按下的时候
onmousemove:鼠标指针移动时
onmouseup:在元素上松开按钮的时候
onresize:调整浏览器窗口大小. 经常作用在 window 上,但也可以作用在元素上
window.onresize = function() {}onscroll:拖动滚动条
window.onscroll = function() { // overflow: auto }
键盘事件
- onkeydown: 按下一个键时
- onkeypress: 释放一个键时
- onkeyup: 按键被松开时
- 顺序:down - press - up
- event 对象 keyCode 这个属性:返回键盘事件触发的值的字符代码或键的代码
- event:事件对象,事件的状态,如触发 event 对象的元素,鼠标的位置及状态等
// 字符串也有长度 document.onkeydown = function(enent) { console.log(event.keyCode) const len = text.value.length }
BOM
浏览器对象模型:提供访问浏览器的功能
window:浏览器的一个实例,它既是通过 js 访问浏览器窗口的一个接口,又是 ECMAScript 规定的 global 对象,window 可以省略(window.name === name)。
window 的方法:
alert('XXXXXXX') confirm("XXX") // 返回值是个Boolean值,需要接收 const tmp = window.confirm('提示信息')。tmp 就是true或者false prompt("text, defaultText") // text:要在对话框中显示的纯文本;defaultText:默认的输入文本; // 返回值:点击取消按钮,返回null;点击确认按钮,返回当前显示的文本(默认或者输入??) // 例子:let message = prompt("请输入姓名", "李月茹") console.log(message) open(pageURL,name,parameters) // 打开一个新的浏览器窗口或查找一个已命名的窗口 // pageURL: 子窗口路径 // name: 子窗口句柄(name声明了新窗口的名字,方便通过name对子窗口进行引用) // parameters:窗口参数,各个参数用逗号分隔 // 在一个页面里打开另一个页面(窗口) window.open('newwindow.html', 'newname', "width=400, height=00, left=0, top=0, toolbar = no, menubar = no, scrollbars=no, location=no, status=no") // newwindow.html页面与写代码的页面在同一目录,默认打开一个标签页,默认的 // 可以设置的参数有: width: 窗口宽度, height: 窗口高度, left: 窗口X轴坐标, top: 窗口Y轴坐标, toolbar: 是否显示浏览器工具栏, menubar: 是否显示菜单栏, scrollbars: 是否显示滚动条, location: 是否显示地址字段, status: 是否显示状态栏 close() // 关闭子窗口的。关闭当前窗口,能不能关闭子窗口??? window.close() // 关闭当前窗口 setTimeout(code, millisec) // 超时调用 code: 要调用的函数或要执行的代码 millisec: 在执行代码前需要等待的毫秒数 // 返回值:返回一个ID值,通过它取消超时调用 const timer = setTimeout("alert('aaa')", 4000) setTimeout(function() { console.log("aaa") }, 400) const fn = (a,b) => {} setTimeout(fn, 500) clearTimeout(id_of_settimeout) // 清除超时调用。id_of_settimeout:setTimeout()返回的ID值 clearTimeout(timer) setInterval(code, millisec) // 间歇调用。millisec: 周期性执行或调用code之间的时间间隔 const intervalId = setInterval(() => { console.log(1111) }, 1000) setTimeout(() => { clearInterval(intervalId) }, 10000) timer = null // null 释放内存
navigator
- navigator 对象: 当前浏览器和操作系统的信息
- userAgent 属性:识别浏览器名称,版本,引擎,以及操作系统等信息的内容 // console.log(navigator.userAgent) // msie,chrome,opera,safari
screen
- screen 对象:客户端显示屏幕的信息
- screen.availWidth // 返回可用的屏幕宽度
- screen.availHeight // 返回可用的屏幕高度
- window.innerWidth // 窗口的宽高
- window.innerHeight
history
- history 对象:ie8 以上哈希也会生成一条历史记录
- 方法:
- history.back() // 回到历史记录的上一步,相当于 history.go(-1)
- history.forward() // history.go(1)
location
- location 对象:提供了与当前窗口中加载的文档相关的内容,还提供了一些导航的功能,他既是 window 对象的属性,也是 document 对象的属性
- 属性:
- location.href // 返回当前加载页面的完整的 URL // console.log(location.href)
- location.hash // 返回 URL 中的哈希,#号后的 0 或多个字符,不包含返回空字符串
<!-- console.log(location.hash) 哈希就是锚点链接 回到顶部 --> <div id="top"></div> <div></div> <button></button> btn.onclick=() => { location.hash = '#top' } - location.host // 返回服务器名称和端口号
- location.hostname // 不带端口号的服务器名称
- location.pathname // 返回 URL 中的目录或文件名
- location.port // 返回 URL 中指定的端口号
- location.protocol // 返回页面使用的协议
- location.search // 返回 URL 的查询字符串,这个字符串以问号开头,无为空
- 方法:
- location.href = index.html // 会生成历史记录
- location.replace(url) // 重新定向 URL,不会在历史记录中生成新纪录
- location.replace('index.html')
- location.reload() // 重新加载页面。可能是从缓存中加载的
- location.reload(true) // 重新加载页面。从服务器端重新加载
document
event
轮播图小技巧
图片重叠在一起,默认都不显示,加一个 active 的类表示显示,用 js 改变 active 加在哪张图片上
定位时,如果有 left,则再有 right 时,left 先起作用。若想让 right 起作用,让 left: auto
如果有背景图片,再设 background: #fff; 这时会把图片覆盖,这时要用 background-color: #fff;
正则表达式
有两种字符: 字符+元字符
元字符(特殊字符): * + ? \$ ^ . | \ () {} []
\t: 水平制表符
\v: 垂直制表符
\n: 换行符
\r: 回车符
\0: 空字符
\f: 换页符
\cX: 与 X 对应的控制字符(ctrl + X)
/*
匹配字符串的规则
*/
// 类,[abc]
[abc]: abc中的一个
// 类取反 [^abc]
[^abc]: 不是字符abc的内容
// 范围类[a-z]
[a-z]: 从a到z的任意字符
[a-zA-Z]: a-z,A-Z
[0-9-]: 从0-9和字符-
// 预定义类
. : 除了回车换行之外的所有字符 [^/r/n]
\d: 数字字符[0-9]
\D: 非数字字符[^0-9]
\s: 空白符[\t\n\x0B\f\r]
\S: 非空白符[^\t\n\x0B\f\r]
\w: 单词字符(字母数字下划线)[a-zA-Z_0-0]
\W: 非单词字符[^a-zA-Z_0-0]
// 边界
^: 开头
$: 结尾
\b: 单词边界
\B: 非单词边界
// 量词, {}
?: 出现0次或者一次,最多一次
+: 出现1次或者多次,最少一次
*: 出现0次或者多次,任意次
{n}: 出现n次
{n,m}: 出现n到m次
{n,}: 至少出现n次
// 贪婪模式: 尽可能多的匹配
'12345678'.replace(/\d{3-6}/g, 'X') // X78, 会匹配最大的次数
// 非贪婪模式:一旦匹配成功就不再继续,只要在量词后面加上?即可
'12345678'.replace(/\d{3-6}?/g, 'X') // XX78, 一旦匹配成功就不再继续 123->X 456->X
'123456789'.match(/\d{3-5}?/g)// ["123", '456', '789']
// 分组(): 使量词作用于分组,而不是一个字符; 或 |作用于分组;反向引用 $1可以拿到组匹配到的内容; 忽略分组,在分组内加上?:即可
'a1b2c3d4'.replace(/([a-z]\d){3}/g, "X") // Xd4, 匹配一个字母加一个数字连续出现3次的情况
'2015-02-23'.replace(/(\d{4})-(\d{2})-(\d{2})/g, '$1$2$3')
(?:Byron).(ok)
// 前瞻(?=)(?!), 断言, js不支持后顾
正则表达式是从文本 头->尾,文本尾部方向成为"前"
匹配到一个人叫张三的时候,还得判断他爸爸是不是叫张二
断言要匹配,但是不会被替换
exp(?=assert)
exp(?!assert)
\w(?=\d) //匹配是不是文字, 且该文字后面是数字
// 正则对象的属性,方法
// 属性
global: 是否全文搜索,默认false
ignore case: 是否大小写敏感,默认false
multiline: 多行搜索,默认false
lastIndex: 这一轮匹配完了,下一轮的开始位置。在非全局情况下lastindex不生效
sourch: 正则表达式
const reg = /\w/gim;
reg.global // true
reg.ignoreCase // true
reg.multiline // true
reg.lastIndex // true
reg.sourch // "\w"
// 方法
RegExp.prototype.test(str)
reg.test('dkfsdkfsd') // true
reg.test('$') // false
RegExp.prototype.exec(str)// 没有匹配到返回null,否则返回一个结果数组
// 字符串对象的正则方法
String.prototype.search(reg) // 忽略全局搜索,index或者-1
'dfdsfsd'.search('d')
String.prototype.match(reg)// 数组
String.prototype.split(reg) // 数组
String.prototype.replace(reg, '替换成谁') // 数组, '替换成谁'可以是function(匹配结果,group1 ...groupx ,index,原字符串)
/*
IDE 中规则复杂的字符串查找替换
*/
// 匹配 单词is,\b是单词边界
\bis\b
// http://aaa/bbb/ccc.jpg 去掉所有jpg文件的协议头。 .+任意字符至少一次, 小括号是分组, 用$1可以获取匹配到的内容
http:(\/\/.+\.jps)
$1
// 日期替换: \d数字,{4}重复次数,[-/]或者, ^开头,$结尾, ()分组
^(\d{4})[/-](\d{2})[/-](\d{2})$
$1-$2-$3
/*
js 正则处理字符串
js中通过内置对象RegExp支持正则表达式
有两种方法实例化RegExp对象
字面量: const reg = /\bis\b/g;
构造函数: const reg = new RegExp('\\bis\\b', 'g')
修饰符: g全文搜索, i忽略大小写, m多行搜索(有换行符默认第二行开头也不会匹配,加上m会被匹配)
字符串: 'my name is lyr'.replace(reg, "要替换成的字符串");
*/
js事件循环,宏微任务
异步执行顺序的差异: 要明白这个问题需要去了解js的事件循环模型 (js的执行栈,作用域链,变量提升,js的单线程原因) 关键字:marcotask microtask queue 执行上下文context 堆heap 栈stack 作用域
宏任务: setTimeout,setTimeInterval, setTimeout在0ms后将callback加入到宏任务的queue中 微任务: promise.then中的回调, promise的回调放在微任务的queue中
先去微任务的queue,再去执行宏任务队列中的callback。
js事件轮询event loop
一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线 一个进程的内存空间是共享的,每个线程都可用这些共享内存。但进程之间是独立的。 多进程: 在同一个时间里,同一个计算机系统中允许两个或两个以上的进程处于运行状态。 多线程:程序中包含多个执行流。一个程序中可以同时运行多个不同的线程来执行不同的任务。 以Chrome浏览器中为例,当你打开一个 Tab 页时,其实就是创建了一个进程,一个进程中可以有多个线程,如渲染线程、JS 引擎线程、HTTP 请求线程等等。 当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。
浏览器主要由4个进程组成
- browser主进程: 就是浏览器的主进程,一般只有一个,用于管理,协调,调配,前进后退一些大的功能。负责各个页面的管理,创建和销毁其他进程。将Renderer进程得到的内存中的Bitmap,绘制到用户界面上。网络资源的管理,下载等。
- GPU渲染进程: 最多一个,主要用于渲染一些3d动画
- 第三方插件进程: 每种类型的插件对应一个进程,仅当使用该插件时才创建
- 浏览器渲染进程: 这个对于前端来说比较重要,每个tab都是拥有一个进程,每个tab互不干扰。页面渲染,脚本执行,事件处理等,在浏览器中打开一个网页相当于新起了一个进程(进程内有自己的多线程)。
浏览器渲染进程(浏览器内核,也被称为渲染引擎) 多线程,5个线程 对于异步,如果有回调函数,才会异步执行。 GUI渲染线程与JS引擎线程是互斥的
- GUI线程: 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时该线程就会执行。GUI线程的js线程会互斥,也就是js线程跑的时候,GUI线程就不会跑,反之亦然
- 网络请求线程: 主要处理一些http异步请求,接受到请求之后会把回调函数交给事件队列线程,然后再给js线程去执行代码
- 定时器线程: setTimeOut和setInterVal,主线程依次执行代码时遇到定时器,会将定时器交给该线程处理,当计数完毕后,事件触发线程会将计数完毕后的事件加入到任务队列的尾部,等待JS引擎线程执行。
- js解析线程: 解析js的脚本程序, 也主要处理异步回调。每个tab都拥有独立的js解析线程
- 事件触发线程: 主要就是存放一些,异步的回调事件,当达成某个条件后,就会触发回调函数,推入事件触发线程,事件触发线程会根据js解析线程是否空闲去推到js解析线程上解析代码。
宏任务(macrotask): setTimeout、setInterval、script(整体代码块)、 I/O 操作、UI 渲染等, 宏任务是放到事件触发队列里面的。 微任务(microtask): Promise,process.nextTick, MutationObserver(html5新特性)等, 是放到微任务队列里面的,由js线程掌管。微任务在es6里面被规范为jobs queue
js在执行宏任务前先会把微任务执行完清空,执行完一个宏任务去清空一次微任务 流程如下:
- js解析线程先执行完栈内的代码
- 在执行事件触发线程里面的宏任务之前,先会看看微任务队列里面是否有微任务,有就会先一步调用所有的微任务
- 调用完微任务再触发一个宏任务
- 执行完一个宏任务会再去找微任务
这也是为什么setTimeOut和setInterVal有时会不准确,如果js解析线程还没解析完栈内的代码,就不能执行定时器的回调了,即使是定时器的时间到了,也没办法
执行过程
- 执行一个宏任务并将之出队(主程序也是一个宏任务)
- 执行一对微任务并将之出队
- 执行渲染操作,更新界面
- 处理worker相关的任务 当某个宏任务执行完后,会查看是否有微任务队列。 如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。 栈空后,再次读取微任务队列里的任务,依次类推。
总结就是:每一次宏任务执行完之后就会执行自己的微任务队列
// 例1:
setTimeout(function() {
console.log(1)
}, 0);
new Promise(function(a, b) {
console.log(2);
for(var i = 0; i < 10; i++) {
i == 9 && a();
}
console.log(3);
}).then(function() {
console.log(4)
});
console.log(5)
// 结果: 2,3,5,4,1
// promise的定义是同步的,then是异步的
// 例2:
console.log(1);
setTimeout(function() {
console.log(2);
}, 0);
Promise.resolve().then(function() {
console.log(3);
}).then(function() {
console.log(4);
});
console.log(5);
// 结果: 1,5,3,4,2
// 例3:
Promise.resolve().then(()=>{ // promise1
console.log('promise1')
setTimeout(()=>{ // setTimeout1
console.log('setTimeout1')
},0)
})
setTimeout(()=>{ // setTimeout2
console.log('setTimeout2')
Promise.resolve().then(()=>{ // promise2
console.log('promise2')
})
},0)
// 结果:promise1,setTimeout2,promise2,setTimeout1
分析: 第一轮:执行宏任务(主函数) --> 清空微任务队列 执行同步:主函数执行完毕,没有同步代码,生成一个微任务promise1, 一个宏任务setTimeout2。 // 微: [promise1]; 宏: [setTimeout2] 清空微任务队列:执行微任务promise1: 打印promise1并且生成一个宏任务setTimeout1。 // 微: []; 宏: [setTimeout2, setTimeout1] 第二轮:执行下一个宏任务(setTimeout2)--> 清空微任务队列 执行同步: 执行setTimeout2:打印setTimeout2并且生成一个微任务promise2 // 微: [promise2]; 宏: [setTimeout1] 清空微任务队列: 执行promise2:打印promise2 // 微: []; 宏: [setTimeout1] 第三轮:执行下一个宏任务(setTimeout1)--> 清空微任务队列 执行同步:执行setTimeout1:打印setTimeout1 // 微: []; 宏: [] 清空微任务队列: 无任务
浏览器的基本工作原理
核心问题:在地址栏输入 google.com 直到您在浏览器屏幕上看到 Google 首页的整个过程中都发生了些什么 WebKit 是一种开放源代码 rendering engine, 也叫内核。Safari 和 Chrome 浏览器使用的都是 WebKit。
- 什么是引擎? 如果把游戏中所有的对象看作状态机,游戏引擎就是改变状态的规则,它定义了对象(状态机)在什么情况下应该转变为另一种状态并且完成这个状态的转换。 汽车,开始是静止状态。引擎,定义了当打火的时候汽车发动,当踩油门的时候,引擎会让汽车进行加速。 引擎是改变状态的动力,它定义了状态如何改变并且将这种改变实现。
从状态的角度讲,引擎是为了实现状态的转换。而状态的转换,需要定义规则。即在什么情况下作出什么反应。 从功能的角度讲,引擎是封装了代码/函数库,通过调用代码/函数库的接口来实现特定的功能。 从抽象的角度讲,引擎简化了我们看问题的视角。使得我们通过引擎就可以对程序的功能状态进行改变,从而实现丰富的内容和功能。
- BFC 块格式化上下文(Block Formatting Context,BFC) 是块盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。
下列方式会创建块格式化上下文: