Web development technologies have evolved at an incredible clip over the past few years.
Introducing RealWorld.
It's a great solution for learning how other frameworks work.
内容介绍
阶段说明
本阶段MVVM和SSR部分的内容难度大、抽象、难理解
本阶段不要求掌握编码,但需要用自己的语言将内容进行描述
本阶段要求对Vue基本使用非常熟练
data->声明组件自己的简单的响应式的数据
computed->声明组件自己的复杂(数据b依赖了数据a,此时数据b是计算属性)的响应式的数据
props->声明值来源于外部(通常是父组件)的响应式的数据
watch->监听数据变化-在变化时要做的事儿是异步||开销大
异步:定时器/ajax/事件
开销大:循环||递归(自己玩自己)
语法 watch:
// 凡是可以使用Vue的实例this.出来的东西都可以watch监测变化
watch : {
msg(){
}, this.msg
computedMsg(){
}, this.computed
'$route'(){
}, this.$route
'$store'(){
} this.$store
}
****本阶段的内容MVVM部分在面试环节Vue部分属于重点项、必问点,常见的面试问题:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3hYDAAwc-1589879881943)(assets/Mobile-App-Development.png)]
定义:传统的原生App开发模式,有iOS和Android两大系统,需要各自语言开发各自App。
优点:性能和体验都是最好的
缺点:开发和发布成本高
应用技术:Swift,OC,Java
常识: 安卓/iOS/Web 基本都使用Mac进行开发
M站演示
定义: 移动端的网站,常被称为H5应用,说白了就是特定运行在移动端浏览器上的网站应用。一般泛指 **SPA(Single Page Application)模式开发出的网站,与MPA(**Multi-page Application)对应。
优点:开发和发布成本最低
开发成本低,可以跨平台,调试方便,开发速度最快
web app一般只需要一个前端人员开发出一套代码,然后即可应用于各大主流浏览器(特殊情况可以代码进行下兼容),没有新的学习成本,而且可以直接在浏览器中调试
维护成本低
同上,如果代码合理,只需要一名前端就可以维护多个web app
更新最为快速
由于web app资源是直接部署在服务器端的,所以只需要替换服务器端的文件,用户访问是就已经更新了(当然需要解决一些缓存问题)
无需安装App,不会占用手机内存
通过浏览器即可访问,无需安装,用户就会比较愿意去用
缺点:性能和体验不能讲是最差的,但也受到浏览器处理能力的限制
性能低,用户体验差
由于是直接通过的浏览器访问,所以无法使用原生的API,操作体验不好
依赖于网络,页面访问速度慢,耗费流量
Web App每次访问都需要去服务端加载资源访问,所以必须依赖于网络,而且网速慢时访问速度很不理想,特别是在移动端,如果网站优化不好会无故消耗大量流量
功能受限,大量功能无法实现
只能使用Html5的一些02-特殊api,无法调用原生API,所以很多功能存在无法实现情况
临时性入口,用户留存率低
这既是它的优点,也是缺点,优点是无需安装,缺点是用完后有时候很难再找到,或者说很难专门为某个web app留存一个入口,导致用户很难再次使用
应用技术:ReactJS,AugularJS,VueJS等等
定义:混合模式移动应用,介于Web App、Native App这两者之间的App开发技术,兼具“Native App良好交互体验的优势”和“Web App跨平台开发的优势” ,原生客户端的壳WebView,其实里面是HTML5的网页
优点:开发和发布都比较方便,效率介于Native App、Web App之间
开发成本较低,可以跨平台,调试方便
Hybrid模式下,由原生提供统一的API给JS调用,实际的主要逻辑有Html和JS来完成,而由于最终是放在webview中显示的,所以只需要写一套代码即可,达到跨平台效果,另外也可以直接在浏览器中调试,很为方便
最重要的是只需要一个前端人员稍微学习下JS api的调用即可,无需两个独立的原生人员
一般Hybrid中的跨平台最少可以跨三个平台:Android App,iOS App,普通webkit浏览器
维护成本低,功能可复用
同上,如果代码合理,只需要一名前端就可以维护多个app,而且很多功能还可以互相复用
更新较为自由
虽然没有web app更新那么快速,但是Hybrid中也可以通过原生提供api,进行资源主动下载,达到只更新资源文件,不更新apk(ipa)的效果
针对新手友好,学习成本较低
这种开发模式下,只需要前端人员关注一些原生提供的API,具体的实现无需关心,没有新的学习内容,只需要前端人员即可开发
功能更加完善,性能和体验要比起web app好太多
因为可以调用原生api,所以很多功能只要原生提供出就可以实现,另外性能也比较接近原生了
部分性能要求的页面可用原生实现
这应该是Hybrid模式的最多一个好处了,因为这种模式是原生混合web,所以我们完全可以将交互强,性能要求高的页面用原生写,然后一些其它页面用JS写,嵌入webview中,达到最佳体验
缺点:学习范围较广,需要原生配合
相比原生,性能仍然有较大损耗
这种模式受限于webview的性能桎梏,相比原生而言有不少损耗,体验无法和原生相比
不适用于交互性较强的app
这种模式的主要应用是:一些新闻阅读类,信息展示类的app;但是不适用于一些交互较强或者性能要求较高的app(比如动画较多就不适合)
应用技术:Cordova、APPCan、 DCloud 、API Cloud
四种方式对比
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mEns2X7B-1589879881945)(assets/zl9vgosvxs.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dtF3bDHb-1589879881947)(assets/4t0yf9dnpw.png)]
js模块化单独回顾~~~~~
特点:使用类似于 Web 技术的方式来开发 Native App。
定义: Facebook发现Hybrid App存在很多缺陷和不足,于是发起开源的一套新的App开发方案RN。使用JSX语言写原生界面,js通过JSBridge调用原生API渲染UI交互通信。
优点:效率体验接近Native App,发布和开发成本低于Native App
虽然说开发成本大于Hybrid模式,但是小于原生模式,大部分代码可复用
相比于原生模式,这种模式是统一用JS写代码,所以往往只需要一名成员投入学习,即可完成跨平台app的开发,而且后续代码封装的好,很多功能可复用
性能体验高于Hybrid,不逊色与原生
这种模式和Hybrid不一样,Hybrid中的view层实际上还是dom,但是这种模式的view层是虚拟dom,所以性能要高于Hybrid,距离原生差距不大
这种模式可以认为是用JS写原生,即页面用JS写,然后原生通过Bridge技术分析JS,将JS内容单独渲染成原生Android和iOS,所以也就是为什么性能不逊色原生
开发人员单一技术栈,一次学习,跨平台开发
这种模式是统一由JS编写,有着独特的语法,所以只需要学习一次,即可同时开发Android和iOS
社区繁荣,遇到问题容易解决
这应该是React Native的很大一个优势,不像Hybrid模式和原生模式一样各自为营,这种模式是Facebook统一发起的,所以有一个统一的社区,里面有大量资源和活跃的人员,对开发者很友好
缺点: 学习有一定成本,且文档较少,免不了踩坑
虽然可以部分跨平台,但并不是Hybrid中的一次编写,两次运行那种,而是不同平台代码有所区别
这种模式实际上还是JS来写原生,所以Android和iOS中的原生代码会有所区别,如果需要跨平台,对开发人员有一定要求
当然了,如果发展了有一定时间,组件库够丰富了,那么其实影响也就不大了,甚至会比Hybrid更快
开发人员学习有一定成本
虽然社区已经比较成熟了,但是一个新的普通前端学习起来还是有一定学习成本的,无法像Hybrid模式一样平滑
学习成本大,对开发人员技术要求比较高
不懂原生开发很难驾驭好
说是使用 Web 技术进行开发,还是多少得学点儿原生 App 开发,才能处理好跨平台。
前期投入比较大,后劲很足。
应用技术 :
其他技术:
行业常识:
Native App | Web App | Hybrid App | React Native App | |
---|---|---|---|---|
原生功能体验 | 优秀 | 差 | 良好 | 接近优秀 |
渲染性能 | 非常快 | 慢 | 接近快 | 快 |
是否支持设备底层访问 | 支持 | 不支持 | 支持 | 支持 |
网络要求 | 支持离线 | 依赖网络 | 支持离线(资源存本地情况) | 支持离线 |
更新复杂度 | 高(几乎总是通过应用商店更新) | 低(服务器端直接更新) | 较低(可以进行资源包更新) | 较低(可以进行资源包更新) |
编程语言 | Android(Java),iOS(OC/Swift) | js+html+css3 | js+html+css3 | 主要使用JS编写,语法规则JSX |
社区资源 | 丰富(Android,iOS单独学习) | 丰富(大量前端资源) | 有局限(不同的Hybrid相互独立) | 丰富(统一的活跃社区) |
上手难度 | 难(不同平台需要单独学习) | 简单(写一次,支持不同平台访问) | 简单(写一次,运行任何平台) | 挺等(学习一次,写任何平台) |
开发周期 | 长 | 短 | 较短 | 中等 |
开发成本 | 昂贵 | 便宜 | 较为便宜 | 中等 |
跨平台**** | 不跨平台**** | 所有H5浏览器 | Android,iOS,h5浏览器**** | Android,iOS |
APP发布 | App Store | Web服务器 | App Store | App Store |
目前有多种开发模式,那么我们平时开发时如何选择用哪种模式呢?如下
性能要求极高,体验要求极好,不追求开发效率
一般属于吹毛求疵的那种级别了,因为正常来说如果要求不是特别高,会有Hybrid
不追求用户体验和性能,对离线访问没要求
正常来说,如果追求性能和体验,都不会选用web app
没有额外功能,只有一些信息展示
因为web有限制,很多功能都无法实现,所以有额外功能就只能弃用这种方案了
大部分情况下的App都推荐采用这种模式
这种模式可以用原生来实现要求高的界面,对于一些比较通用型,展示型的页面完全可以用web来实现,达到跨平台效果,提升效率
当然了,一般好一点的Hybrid方案,都会把资源放在本地的,可以减少网络流量消耗
追求性能,体验,同时追求开发效率,而且有一定的技术资本,舍得前期投入
React Native这种模式学习成本较高,所以需要前期投入不少时间才能达到较好水平,但是有了一定水准后,开发起来它的优势就体现出来了,性能不逊色原生,而且开发速度也很快
1、看断网情况
通过断开网络,刷新页面,观察内容缓存情况来有个大致的判断,可以正常显示的就是原生写的,显示404或者错误页面的就是html页面。
3、看复制文章的提示,需要通过对比才能得出结果。
比如文章资讯页面可以长按页面试试,如果出现文字选择,粘贴功能的是H5页面,否则是native原生的页面。
有些原生APP开放了复制粘贴功能或者关闭了,而H5的CSS屏蔽了复制选择功能等情况,需要通过对目标测试APP进行对比才可知。
在支付宝APP、蚂蚁聚宝是可以判断的。
4、看加载的方式
如果在打开新页面导航栏下面有一条加载线的话,这个页面就是H5页面,如果没有就是原生的。
5、看app顶部,导航栏是否会有关闭的操作
如果APP顶部导航栏当中出现了关闭的按钮或者关闭的图标,那么当前的页面是H5页面,原生的不会出现(除非设计开发者特意设计),美团、大众点评的APP、微信APP当加载H5过多的时候,左上角会出现关闭两个字。
6、判断页面下拉刷新的时候(前提是要有下拉刷新的功能)
如果页面没有明显刷新现象的是原生的,如果有明显刷新现象(比如闪一下)的是H5页面(Ios和Android)。比如淘宝的众筹页面。
7、下拉页面的时候显示网址提供方的一定是H5页面。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f3zCdnlx-1589879881948)(/assets/wKiom1kKyzWDcmeRAARsWiUM4mA579.png-wh_500x0-wm_3-wmp_4-s_2902533966.png)]
8、利用系统开发人员工具
找到手机的设置,开发者选项,显示布局边界,选择开启后再去查看APP整体布局边界,这样所有应用控件布局就会一目了然。
如果是native APP那么每个按钮、文字、图片都是红色的线显示这个控件的布局情况。如下图的微信:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JgspHUeP-1589879881949)(/assets/wKioL1kKzGzTEtNCAAXhWJHYlgI333.png-wh_500x0-wm_3-wmp_4-s_3312311617.png)]
如果是web APP那么应该就是一个webview去加载网页,webview作为一个控件,只有一个边界框,即只有屏幕边才有红色线,如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CS2nEto9-1589879881950)(/assets/wKioL1kKzXDhS9IMAAb9O0qy84c885.png-wh_500x0-wm_3-wmp_4-s_1494819383.png)]
混合APP 则是native 与 webview 混排的界面,如下图红色线框是各控件的绘制边界,中间那一大块布局丰富的界面没有显示出很多边界红线,就是网页实现的。如下图的京东:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ybr9EPcN-1589879881951)(/assets/wKioL1kKzjWjSa77AA10F9eHjLU763.png-wh_500x0-wm_3-wmp_4-s_2780626697.png)]
使用HBuliderX创建项目并且编码运行测试(网页测试/模拟器测试/真机测试)
下载安装HBuilderX
IDE:敲代码的软件.比如VSCode/WebStrom/HBuliderX
下载地址:https://www.dcloud.io/hbuilderx.html
创建项目
1. 新建项目
2. 5+runtime类型
3. 生成项目目录
4. 编码
5. 测试
1. 运行->选择chrome
2. 运行->选择安卓模拟器(夜神)
开发神器
1. 在浏览器上测试效果
2. 在模拟器上测试
3. 在真机上测试
- 用手机助手连接手机(**安装驱动**)
- 开启手机的**开发者选项**
- **启用 USB 调试**
- 使用数据线连接手机
- 在 HBuilder 中找到:运行 -> 运行到手机或模拟器 -> 你的设备
- [HBuilder/HBuilderX真机运行、手机运行、真机联调常见问题](http://ask.dcloud.net.cn/article/97)
访问 HTML5 + API
通过HBuliderX将黑马头条项目打包成app,生成apk文件下载安装测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6R8iwFcY-1589879881951)(./assets/mvvm-3510948.png)]
<!-- 视图 -->
<template>
<div>{
{
message }}</div>
</template>
<!-- ViewModel -->
把普通的 JavaScript 对象和视图 DOM 之间建立了一种映射关系:
- 数据的改变影响视图
- 视图(表单元素)的改变影响数据
<script>
// Model 普通数据对象
export default {
data () {
return {
message: 'Hello World'
}
}
}
</script>
<style>
</style>
研究原理的套路
Vue文档说明
Vue的MVVM的实现原理包含以下几部分
**Object.defineProperty()**
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。 const per = {
name: 'zs'
}
var _age = 1000
const per1 = Object.defineProperty(per, 'age', {
// value: 200,
set(age) {
_age = age
},
get() {
return _age
}
})
per1.age = 2000
console.log(per1.age);
当访问对象的属性时,会调用属性的get方法,当为对象的属性设置值时,会调用属性的set方法
目的: 利用Object.defineProperty()模拟Vue中的Vue实例化对象的效果
核心:把data的数据挂到vm对象上,直接访问vm.数据名字可以调用对应的set/get方法
const data = {
msg: 'abc'
}
const vm = {
}
Object.defineProperty(vm, 'msg', {
get() {
console.log('get');
return data.msg
},
set(value) {
console.log('set');
data.msg = value
}
})
console.log(vm);
目的: 利用属性描述符中的enumerable选项设置属性是否可以被遍历访问
代码
const data = {
msg: 'abc'
}
const vm = {
}
Object.defineProperty(vm, 'msg', {
enumerable: true,
get() {
console.log('get');
return data.msg
},
set(value) {
console.log('set');
data.msg = value
}
})
// console.log(vm);
for (const key in vm) {
console.log(key);
}
目的:利用属性描述符中的enumerable选项设置属性是否可以被delete删除
代码
'use strict';
const data = {
msg: 'abc'
}
const vm = {
}
Object.defineProperty(vm, 'msg', {
configurable: true,
enumerable: true,
get() {
console.log('get');
return data.msg
},
set(value) {
console.log('set');
data.msg = value
}
})
delete vm.msg
// console.log(vm);
for (const key in vm) {
console.log(key);
}
js可以开启严格模式’use strict’ [MDN文档](https://developer.mozilla.org/zh-CN/docs/Web/JavaS cript/Reference/Strict_mode)
目的:利用属性描述符中的writable 选项设置属性是否可写,可以被修改
代码
'use strict';
const data = {
msg: 'abc'
}
const vm = {
}
Object.defineProperty(vm, 'msg', {
configurable: true,
enumerable: true,
value: data.msg,
writable: false
})
vm.msg = '新值'
console.log(vm);
writable和访问器(set/get)无法同时设置
结果: 1. 模拟vm的结构啦 好开心 2. 修改数据时,执行set->更新视图
设计模式: 前人总结的经验,用于开发
介绍:Vue的MVVM原理实现中,当数据变化时,要通知多个位置更新视图,所以我们可以使用设计模式->发布订阅模式来实现->当事情A发生时通知多个人进行事件B->在busevent.js中就是利用了这一模式
组成:
代码
// 注册事件(订阅消息)
// bus.on('click',fn1)
// bus.on('mouseover',fn2)
// bus.on('xxoo',fn3)
// 把多个事件类型和事件处理函数进行保存
// 触发事件(发布消息)
// bus.$emit('click',1)
// bus.$emit('mouseover',2)
// bus.$emit('xxoo',3)
// 把多个事件类型进行取出,并且执行事件处理函数
// 使用构造函数进行封装
function EventEmitter() {
}
EventEmitter.prototype.$on = function() {
}
EventEmitter.prototype.$emit = function() {
}
// 使用构造函数进行封装
function EventEmitter() {
// {'click':[fn1,fn2,...],'mouseover':[fn1,fn2,...]}
this.subs = {
}
}
// 注册事件
// click , fn
EventEmitter.prototype.$on = function(eventType, handler) {
// // subs ---> {click:[fn1]}
// if (this.subs[eventType]) {
// this.subs[eventType].push(handler)
// } else {
// // sub2 ---> {}
// this.subs[eventType] = []
// this.subs[eventType].push(handler)
// }
this.subs[eventType] = this.subs[eventType] || []
this.subs[eventType].push(handler)
}
EventEmitter.prototype.$emit = function(eventType) {
if (this.subs[eventType]) {
this.subs[eventType].forEach((handler) => {
handler()
})
}
}
// 测试
const em = new EventEmitter()
em.$on('a', function() {
console.log('---a');
})
em.$on('b', function() {
console.log('---b');
})
em.$on('c', function() {
console.log('---c');
})
em.$emit('a')
em.$emit('b')
em.$emit('c')
EventEmitter.prototype.$emit = function(eventType, ...rest) {
if (this.subs[eventType]) {
this.subs[eventType].forEach((handler) => {
handler(...rest)
})
}
}
这里用到了ES6语法剩余参数…rest
目的:我们希望$on事件触发时的this是em实例化对象
EventEmitter.prototype.$emit = function(eventType, ...rest) {
if (this.subs[eventType]) {
this.subs[eventType].forEach((handler) => {
// handler(...rest)
handler.call(this, ...rest)
})
}
}
这里用到了call方法修改函数内部的this指向
this指向玩法->单讲
// this指向->
// 规则
// 1. 如果函数调用时,前面没东西->独立调用: this->window
// 2. obj.fn() this->obj
// 3. bind/call/apply eg:fn.call(obj) this->obj
// 4. new Fn() this->实例对象
// 注册事件(订阅消息)
// bus.on('click',fn1)
// bus.on('mouseover',fn2)
// bus.on('xxoo',fn3)
// 把多个事件类型和事件处理函数进行保存
// 触发事件(发布消息)
// bus.$emit('click',1)
// bus.$emit('mouseover',2)
// bus.$emit('xxoo',3)
// 把多个事件类型进行取出,并且执行事件处理函数
// 使用构造函数进行封装
function EventEmitter() {
// {'click':[fn1,fn2,...],'mouseover':[fn1,fn2,...]}
this.subs = {
}
}
// 注册事件
// click , fn
EventEmitter.prototype.$on = function(eventType, handler) {
this.subs[eventType] = this.subs[eventType] || []
this.subs[eventType].push(handler)
}
EventEmitter.prototype.$emit = function(eventType, ...rest) {
if (this.subs[eventType]) {
this.subs[eventType].forEach((handler) => {
handler.call(this, ...rest)
})
}
}
const em = new EventEmitter()
em.$on('a', function(...rest) {
console.log('---a', rest);
})
em.$on('b', function() {
console.log('---b');
})
em.$on('c', function() {
console.log('---c', this);
})
// em.$emit('a', 100, 1, 2, 3, 4)
// em.$emit('b', 200)
em.$emit('c', 300)
利用发布订阅模式可以实现当事件触发时会通知到很多人去做事情,Vue中做的事情是更新DOM
子节点 : childNodes 伪数组
节点类型: nodeType === 1 3 8
属性集合 attributes -> 指令 v-xxoo
节点内容: innerText || textContent -> {
{
}}
找到对应的DOM,更新DOM
el的值:string || Element
mvvm/vue.js
// Vue构造函数
// 初始化数据
// 1. 注入:data中的属性,设置为Vue实例的属性,并且设置成getter/setter
// 2. 数据劫持:$data中的属性设置为getter/setter(响应式数据),当数据变化时,要发送通知,更新视图
// 3. 编译模板:解析模板中的{
{}}和v-指令
function Vue(options) {
this.$options = options
this.$data = options.data || {
}
// 判断options.el的类型
// 如果字符串(选择器'#app'),要获取对应的DOM元素
const el = options.el
this.$el = typeof el === 'string' ? document.querySelector(el) : el
this.$proxyData()
}
// 1. 注入:data中的属性,设置为Vue的属性,并且设置成getter/setter
// 1.1 遍历data中的属性
// 1.2 把遍历到的属性挂载到Vue实例上,并且设置getter/setter
Vue.prototype.$proxyData = function() {
Object.keys(this.$data).forEach(key => {
Object.defineProperty(this, key, {
enumerable: true,
configurable: false,
getter() {
return this.$data[key]
},
setter(value) {
// 相同的值,不需要赋值
if (this.$data[key] === value) return
this.$data[key] = value
}
})
})
}
mvvm/observer.js
// 数据劫持:$data中的属性设置为getter/setter(响应式数据),当数据变化时,要发送通知,更新视图
// 1. 遍历data中的属性->walk方法
// 2. 为属性设置getter和setter->defineReactive方法
function Observer(data) {
this.walk(data)
}
Observer.prototype.walk = function(data) {
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
})
}
Observer.prototype.defineReactive = function(data, key, value) {
Object.defineProperty(data, key, {
configurable: false,
enumerable: true,
get() {
return value
},
set(newValue) {
if (value === newValue) return
value = newValue
// 当数据变化时,更新视图
}
})
}
测试
index.html
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Documenttitle>
<script src="./mvvm/observer.js">script>
<script src="./mvvm/vue.js">script>
head>
<body>
<div id="app">div>
<script>
// 实例化Vue对象
const vm = new Vue({
// 指定Vue管理的视图
el: '#app',
data: {
msg: 'abc',
age: 10
}
})
console.log(vm)
script>
body>
html>
mvvm/compiler.js
// 3. 编译模板:解析模板中的{
{}}和v-指令
// 找到$el->就找到了模板中的{
{}}和指令
// 找到数据$data
// 所以, 直接找vm
function Compiler(vm) {
this.$vm = vm
// 编译模板
}
// 编译模板
Compiler.prototype.compile = function(el) {
}
// 处理文本节点
Compiler.prototype.compileTextNode = function(node) {
}
// 处理元素节点
Compiler.prototype.compileElementNode = function(node) {
}
// 判断当前节点是否是文本节点
Compiler.prototype.isTextNode = function(node) {
}
// 判断当前节点是否是元素节点
Compiler.prototype.isElementNode = function(node) {
}
// 判断属性名称是否是指令
Compiler.prototype.isDirective = function(attrName) {
}
mvvm/compiler.js
// 3. 编译模板:解析模板中的{
{}}和v-指令
// 找到$el->就找到了模板中的{
{}}和指令
// 找到数据$data
// 所以, 直接找vm
function Compiler(vm) {
this.$vm = vm
// 编译模板
this.compile(vm.$el)
}
// 编译模板
// 1.1 遍历节点
// 1.2 判断节点类型
Compiler.prototype.compile = function(el) {
// 找到el下所有子节点childNodes
Array.from(el.childNodes).forEach(node => {
if (this.isTextNode(node)) {
this.compileTextNode(node)
}
if (this.isElementNode(node)) {
this.compileElementNode(node)
}
})
}
// 处理文本节点
Compiler.prototype.compileTextNode = function(node) {
}
// 处理元素节点
Compiler.prototype.compileElementNode = function(node) {
}
// 判断当前节点是否是文本节点
Compiler.prototype.isTextNode = function(node) {
return node.nodeType === 3
}
// 判断当前节点是否是元素节点
Compiler.prototype.isElementNode = function(node) {
return node.nodeType === 1
}
// 判断属性名称是否是指令
Compiler.prototype.isDirective = function(attrName) {
return attrName.startsWith('v-')
}
mvvm/compiler.js
// 编译模板
// 1.1 遍历节点
// 1.2 判断节点类型
Compiler.prototype.compile = function(el) {
// 找到el下所有子节点childNodes
Array.from(el.childNodes).forEach(node => {
if (this.isTextNode(node)) {
this.compileTextNode(node)
}
if (this.isElementNode(node)) {
this.compileElementNode(node)
this.compile(node)
}
})
}
// 处理文本节点
// 1. 获取文本节点的内容
// 2. 正则判断
// 3. 获取属性名
// 4. 给文本节点重新赋值为data中的key属性
Compiler.prototype.compileTextNode = function(node) {
const text = node.textContent
const reg = /\{\{(.+)\}\}/
if (reg.test(text)) {
const key = RegExp.$1.trim()
node.textContent = text.replace(reg, this.vm.$data[key])
}
}
测试
<script src="./mvvm/compiler.js">script>
<script src="./mvvm/observer.js">script>
<script src="./mvvm/vue.js">script>
<div id="app">
<p>{
{msg}}p>
<p>{
{age}}p>
div>
提示: 实际开发时正则不需要记 但是要能看懂
mvvm/compiler.js
// 处理元素节点
Compiler.prototype.compileElementNode = function(node) {
Array.from(node.attributes).forEach(attr => {
const name = attr.name
if (this.isDirective(attr.name)) {
const value = attr.value
if (name === 'v-text') {
node.textContent = this.$vm[value]
}
if (name === 'v-model') {
node.value = this.$vm[value]
}
}
})
}
数据变化时,视图的多个位置都需要变化->发布订阅模式EventEmitter.js
observer.js
->触发事件
Observer.prototype.defineReactive = function(data, key, value) {
Object.defineProperty(data, key, {
configurable: false,
enumerable: true,
get() {
return value
},
set(newValue) {
if (value === newValue) return
value = newValue
// 当某个数据变化时,发送通知更新视图 触发事件$emit()
em.$emit(key)
}
})
}
compiler.js
-> 注册事件
Compiler.prototype.compileTextNode = function(node) {
const text = node.textContent
const reg = /\{\{(.+)\}\}/
if (reg.test(text)) {
const key = RegExp.$1.trim()
node.textContent = text.replace(reg, this.vm.$data[key])
// 注册事件
em.$on(key, () => {
node.textContent = this.vm[key]
})
}
}
Compiler.prototype.compileElementNode = function(node) {
Array.from(node.attributes).forEach(attr => {
const name = attr.name
if (this.isDirective(name)) {
const value = attr.value
if (name === 'v-model') {
node.value = this.vm.$data[value]
// 注册事件
em.$on(value, () => {
node.value = this.vm[value]
})
}
if (name === 'v-text') {
node.textContent = this.vm.$data[value]
// 注册事件
em.$on(value, () => {
node.textContent = this.vm[value]
})
}
}
})
}
处理v-model的视图变化更新数据
Compiler.prototype.compileElementNode = function(node) {
Array.from(node.attributes).forEach(attr => {
const name = attr.name
if (this.isDirective(name)) {
const value = attr.value
if (name === 'v-model') {
node.value = this.vm.$data[value]
// 注册事件
em.$on(value, () => {
node.value = this.vm[value]
})
// 视图更新,更改数据
node.oninput = () => {
this.vm[value] = node.value
}
}
if (name === 'v-text') {
node.textContent = this.vm.$data[value]
// 注册事件
em.$on(value, () => {
node.textContent = this.vm[value]
})
}
}
})
}
黑马头条项目演示
Client Side Render (客户端渲染 CSR):页面初始加载的 HTML 文档中无核心内容,需要下载执行 js 文件,由浏览器动态生成页面,并通过 JS 进行页面交互事件与状态管理
拼接数据的操作在客户端完成 审查元素看不到数据
提示: 其实我们学过服务端渲染.比如node的项目/案例-> 大量代码在服务器,最后竟然有页面
Server Side Render (服务端渲染 SSR):服务器直接生成 HTML 文档返回给浏览器,但页面交互能力有限。适合于任何后端语言:PHP、Java、Python、Go 等。
拼接数据的操作在服务端完成 审查元素可以看到数据
SPA(单页面应用程序)
SPA是效果,接近于原生应用
SPA面向用户->体验好->解决用户的痛点:用户是最没耐心/吝啬/想体验好的服务的人-
Vue的SSR文档
Vue的SSR代码既包含客户端部分,又包含服务端部分
isomorphic web apps(同构应用):基于react、vue框架,客户端渲染和服务器端渲染的结合,在服务器端执行一次,用于实现服务器端渲染(首屏直出),在客户端再执行一次,用于接管页面交互,核心解决SEO和首屏渲染慢的问题。
Vue的SSR文档
以上所有操作和编码都是在服务端代码
问题: 目前服务端的代码既有客户端的又有服务端的,此时可以用第三方框架实现Nuxt
说明
作用
基于 Vue、Webpack 和 Babel Nuxt.js 集成了以下组件/框架,用于开发完整而强大的 Web 应用:
Babel作用: 编译各种各样的js->转换浏览器可以认识的js(其中各种各样的js包含ES6/ES7/ES8/ES9/typescript/jsx等)
mode: 'spa'
)mkdir <项目名>
$ cd <项目名>
npm init
{
"name": "my-app",
"scripts": {
// npm的功能:自定义指令(封装指令用的)
// "start" -> npm start
"dev": "nuxt"
}
}
npm install --save nuxt
mkdir pages
创建第一个页面->pages/index.vue
:
Hello world!
npm run dev
新建组件pages/list.vue->自动生成路由/自动重启
父组件名字user 子组件所在的文件夹同名user
文档
测试接口
index.vue
<template>
<div id="index">
<h1>Index</h1>
<p>{
{
msg }}</p>
<nuxt-link to="/user/one">链接1</nuxt-link>
<nuxt-link to="/user/200">链接2</nuxt-link>
<ul>
<li v-for="(item, index) in userList" :key="index">
{
{
item.title }}
</li>
</ul>
</div>
</template>
<script>
import axios from 'axios'
// console.log('test----')
export default {
// 1. 组件创建之前被调用(beforeCreate之前)->服务端执行
// 2. 路由跳转的时候会执行->客户端执行
// 3. asyncData返回的数据 会被融合到组件的data中
// 4. asyncData的this不是组件的实例 而是undefined
async asyncData() {
console.log(this)
console.log('asyncData-----')
const {
data } = await axios.get(
`https://jsonplaceholder.typicode.com/todos`
)
return {
userList: data
}
},
data() {
return {
msg: 'abc'
}
}
}
</script>
<style></style>
axio支持客户端和服务器(node)环境
难点: 中间件二次处理的数据操作(接口数据不便于直接使用)
需要注意的是,在任何 Vue 组件的生命周期内, 只有 beforeCreate
和 created
这两个方法会在 客户端和服务端被调用。其他生命周期函数仅在客户端被调用。
前后端都调用:
在服务端渲染期间不被调用:
beforeCreate() {
console.log('beforeCreate------')
console.log(this.$isServer)
},
pages是需要关注的
案例: TODOlist->表格增删改查
[email protected] 12345678
在页面引入样式资源->资源链接
<html {
{
HTML_ATTRS }}>
<head {
{
HEAD_ATTRS }}>
<link href="//code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="//demo.productionready.io/main.css"> {
{ HEAD }}
head>
<body {
{
BODY_ATTRS }}>
{
{ APP }}
body>
html>
有公共头部和尾部->使用Nuxt提供的视图布局
<template>
头部->来源于RealWorld素材
<nuxt/>
尾部->来源于RealWorld素材
template>
修改了根目录的文件,需要重启,测试
准备首页index.vue
准备登陆页login.vue
Sign up
准备文章详情页articles/_slug.vue
Web development technologies have evolved at an incredible clip over the past few years.
Introducing RealWorld.
It's a great solution for learning how other frameworks work.
With supporting text below as a natural lead-in to additional content.
With supporting text below as a natural lead-in to additional content.
自动生成路由,可以通过手动修改标识进行测试
使用Nuxt提供的axios模块进行请求->文档
nuxt.config.js
export default {
mode: "universal",
/*
** Headers of the page
*/
head: {
title: process.env.npm_package_name || "",
meta: [
{
charset: "utf-8" },
{
name: "viewport", content: "width=device-width, initial-scale=1" },
{
hid: "description",
name: "description",
content: process.env.npm_package_description || ""
}
],
link: [{
rel: "icon", type: "image/x-icon", href: "/favicon.ico" }]
},
/*
** Customize the progress-bar color
*/
loading: {
color: "#fff" },
/*
** Global CSS
*/
css: [],
/*
** Plugins to load before mounting the App
*/
plugins: [],
/*
** Nuxt.js dev-modules
*/
buildModules: [],
/*
** Nuxt.js modules
*/
modules: ["@nuxtjs/axios"],
axios: {
// proxyHeaders: false
baseURL: `https://conduit.productionready.io/api`
},
/*
** Build configuration
*/
build: {
/*
** You can extend webpack config here
*/
extend(config, ctx) {
}
}
};
index.vue
async asyncData({
$axios }) {
const {
articles } = await $axios.$get("/articles");
console.log(articles);
return {
articles };
}
// 遍历数据->测试