2020-web前端-vue-Vue进阶

Vue进阶

  • 能够实现项目的打包
  • 能够说出开发移动App的几种方式
  • 能够使用HBuliderX把移动端网站打包成移动App
  • 能够说出defineProperty的作用
  • 能够说出服务端渲染和客户端渲染的区别
  • 理解Nuxt的作用
  • 能够理解asyncData的作用

Day01

01-阶段说明和内容介绍

内容介绍

  1. 打包app
  2. MVVM实现原理
    1. Object.defineProperty()方法的使用
    2. 发布订阅模式
    3. 实现MVVM框架
  3. SSR服务端渲染

阶段说明

  1. 本阶段MVVM和SSR部分的内容难度大、抽象、难理解

  2. 本阶段不要求掌握编码,但需要用自己的语言将内容进行描述

  3. 本阶段要求对Vue基本使用非常熟练

    1. data->声明组件自己的简单的响应式的数据

    2. computed->声明组件自己的复杂(数据b依赖了数据a,此时数据b是计算属性)响应式的数据

    3. props->声明值来源于外部(通常是父组件)的响应式的数据

    4. watch->监听数据变化-在变化时要做的事儿是异步||开销大

      1. 异步:定时器/ajax/事件

      2. 开销大:循环||递归(自己玩自己)

      3. 语法 watch:

        // 凡是可以使用Vue的实例this.出来的东西都可以watch监测变化
        watch : {
                   
            msg(){
                   }, this.msg
            computedMsg(){
                   },  this.computed
            '$route'(){
                   }, this.$route
             '$store'(){
                   } this.$store
        }
        
  4. ****本阶段的内容MVVM部分在面试环节Vue部分属于重点项、必问点,常见的面试问题:

    • Vue 数据绑定的原理?
    • MVVM 数据绑定的原理?
    • Vue 双向数据绑定的原理?
    • Vue 数据响应式原理?
    • 数据响应式原理?
    • 数据驱动视图的原理?

02-移动App开发的几种方式

  1. 原生App
  2. WebApp
  3. HybridApp
  4. 分支:跨平台开发
  5. 其他类型(小程序/快应用等等)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3hYDAAwc-1589879881943)(assets/Mobile-App-Development.png)]

03-移动App开发-NativeApp介绍和特点

定义:传统的原生App开发模式,有iOS和Android两大系统,需要各自语言开发各自App。

优点:性能和体验都是最好的

缺点:开发和发布成本高

应用技术:Swift,OC,Java

常识: 安卓/iOS/Web 基本都使用Mac进行开发

04-移动App开发-WebApp介绍和特点

M站演示

定义: 移动端的网站,常被称为H5应用,说白了就是特定运行在移动端浏览器上的网站应用。一般泛指 **SPA(Single Page Application)模式开发出的网站,与MPA(**Multi-page Application)对应。

优点开发和发布成本最低

  1. 开发成本低,可以跨平台,调试方便,开发速度最快

    web app一般只需要一个前端人员开发出一套代码,然后即可应用于各大主流浏览器(特殊情况可以代码进行下兼容),没有新的学习成本,而且可以直接在浏览器中调试

  2. 维护成本低

    同上,如果代码合理,只需要一名前端就可以维护多个web app

  3. 更新最为快速

    由于web app资源是直接部署在服务器端的,所以只需要替换服务器端的文件,用户访问是就已经更新了(当然需要解决一些缓存问题)

  4. 无需安装App,不会占用手机内存

    通过浏览器即可访问,无需安装,用户就会比较愿意去用

缺点性能和体验不能讲是最差的,但也受到浏览器处理能力的限制

  1. 性能低,用户体验差

    由于是直接通过的浏览器访问,所以无法使用原生的API,操作体验不好

  2. 依赖于网络,页面访问速度慢,耗费流量

    Web App每次访问都需要去服务端加载资源访问,所以必须依赖于网络,而且网速慢时访问速度很不理想,特别是在移动端,如果网站优化不好会无故消耗大量流量

  3. 功能受限,大量功能无法实现

    只能使用Html5的一些02-特殊api,无法调用原生API,所以很多功能存在无法实现情况

  4. 临时性入口,用户留存率低

    这既是它的优点,也是缺点,优点是无需安装,缺点是用完后有时候很难再找到,或者说很难专门为某个web app留存一个入口,导致用户很难再次使用

应用技术ReactJS,AugularJS,VueJS等等

05-移动App开发-HybridApp介绍和特点

定义:混合模式移动应用,介于Web App、Native App这两者之间的App开发技术,兼具“Native App良好交互体验的优势”和“Web App跨平台开发的优势” ,原生客户端的壳WebView,其实里面是HTML5的网页

  • 把网页打包成移动 App
  • 使你的 Web 程序可以访问手机原生能力

优点:开发和发布都比较方便,效率介于Native App、Web App之间

  1. 开发成本较低,可以跨平台,调试方便

    Hybrid模式下,由原生提供统一的API给JS调用,实际的主要逻辑有Html和JS来完成,而由于最终是放在webview中显示的,所以只需要写一套代码即可,达到跨平台效果,另外也可以直接在浏览器中调试,很为方便

    最重要的是只需要一个前端人员稍微学习下JS api的调用即可,无需两个独立的原生人员

    一般Hybrid中的跨平台最少可以跨三个平台:Android App,iOS App,普通webkit浏览器

  2. 维护成本低,功能可复用

    同上,如果代码合理,只需要一名前端就可以维护多个app,而且很多功能还可以互相复用

  3. 更新较为自由

    虽然没有web app更新那么快速,但是Hybrid中也可以通过原生提供api,进行资源主动下载,达到只更新资源文件,不更新apk(ipa)的效果

  4. 针对新手友好,学习成本较低

    这种开发模式下,只需要前端人员关注一些原生提供的API,具体的实现无需关心,没有新的学习内容,只需要前端人员即可开发

  5. 功能更加完善,性能和体验要比起web app好太多

    因为可以调用原生api,所以很多功能只要原生提供出就可以实现,另外性能也比较接近原生了

  6. 部分性能要求的页面可用原生实现

    这应该是Hybrid模式的最多一个好处了,因为这种模式是原生混合web,所以我们完全可以将交互强,性能要求高的页面用原生写,然后一些其它页面用JS写,嵌入webview中,达到最佳体验

缺点:学习范围较广,需要原生配合

  1. 相比原生,性能仍然有较大损耗

    这种模式受限于webview的性能桎梏,相比原生而言有不少损耗,体验无法和原生相比

  2. 不适用于交互性较强的app

    这种模式的主要应用是:一些新闻阅读类,信息展示类的app;但是不适用于一些交互较强或者性能要求较高的app(比如动画较多就不适合)

应用技术:Cordova、APPCan、 DCloud 、API Cloud

四种方式对比

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mEns2X7B-1589879881945)(assets/zl9vgosvxs.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dtF3bDHb-1589879881947)(assets/4t0yf9dnpw.png)]

js模块化单独回顾~~~~~

06-移动App开发-跨平台开发介绍和特点

特点:使用类似于 Web 技术的方式来开发 Native App。

定义: Facebook发现Hybrid App存在很多缺陷和不足,于是发起开源的一套新的App开发方案RN。使用JSX语言写原生界面,js通过JSBridge调用原生API渲染UI交互通信。

优点:效率体验接近Native App,发布和开发成本低于Native App

  1. 虽然说开发成本大于Hybrid模式,但是小于原生模式,大部分代码可复用

    相比于原生模式,这种模式是统一用JS写代码,所以往往只需要一名成员投入学习,即可完成跨平台app的开发,而且后续代码封装的好,很多功能可复用

  2. 性能体验高于Hybrid,不逊色与原生

    这种模式和Hybrid不一样,Hybrid中的view层实际上还是dom,但是这种模式的view层是虚拟dom,所以性能要高于Hybrid,距离原生差距不大

    这种模式可以认为是用JS写原生,即页面用JS写,然后原生通过Bridge技术分析JS,将JS内容单独渲染成原生Android和iOS,所以也就是为什么性能不逊色原生

  3. 开发人员单一技术栈,一次学习,跨平台开发

    这种模式是统一由JS编写,有着独特的语法,所以只需要学习一次,即可同时开发Android和iOS

  4. 社区繁荣,遇到问题容易解决

    这应该是React Native的很大一个优势,不像Hybrid模式和原生模式一样各自为营,这种模式是Facebook统一发起的,所以有一个统一的社区,里面有大量资源和活跃的人员,对开发者很友好

缺点: 学习有一定成本,且文档较少,免不了踩坑

  1. 虽然可以部分跨平台,但并不是Hybrid中的一次编写,两次运行那种,而是不同平台代码有所区别

    这种模式实际上还是JS来写原生,所以Android和iOS中的原生代码会有所区别,如果需要跨平台,对开发人员有一定要求

    当然了,如果发展了有一定时间,组件库够丰富了,那么其实影响也就不大了,甚至会比Hybrid更快

  2. 开发人员学习有一定成本

    虽然社区已经比较成熟了,但是一个新的普通前端学习起来还是有一定学习成本的,无法像Hybrid模式一样平滑

  3. 学习成本大,对开发人员技术要求比较高

  4. 不懂原生开发很难驾驭好

  5. 说是使用 Web 技术进行开发,还是多少得学点儿原生 App 开发,才能处理好跨平台。

  6. 前期投入比较大,后劲很足。

应用技术 :

React Native(主流)

  • 公司:Facebook
  • 技术栈:React
  • 基于React开发App的框架RN

其他技术:

Weex(使用不多)

  • 公司:Apache 开源基金会
  • Vue.js 技术栈
  • 基于Vue开发App的框架WEEX

Flutter(未来趋势)

  • 公司:Google
  • 它提供了官方的原生 UI 组件
  • 比 RN、Weex 之类的体验更好
  • 开发语言:Dart(和 JavaScript 很像)
  • 商业应用:闲鱼

行业常识:

  1. 前端工程师 Web
  2. FE客户端开发工程师
    1. 苹果开发工程师
    2. 安卓开发工程师
    3. Web开发工程师

07-移动App开发-其他类型App

小程序

  • 微信小程序
  • 百度小程序
  • 头条小程序
  • 支付宝小程序
  • 。。。

统一开发平台

  • taro(京东)->坑多 趋势很好
  • uni-app

微网页

  • 微信公众号
  • 百度直达号
  • 。。。

快应用(不温不火,iPhone 不参与很难搞起来)

  • 各大手机厂商联合制定推出的一种方式,类似于小程序
  • 使用 Web 技术进行开发, 而且提供了在 Web 中访问手机硬件等底层交互的 API
  • 属于混合 App 的一种方式

PWA(网站离线访问技术,没有 iPhone 不参与)

  • 它可以让网站拥有一个类似于 App 的入口
  • 提供了网站的离线应用访问
  • Google 在推动
  • 手机端目前只能在 安卓手机的 Chrome 浏览器运行

08-移动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

如何选择开发模式

目前有多种开发模式,那么我们平时开发时如何选择用哪种模式呢?如下

选择纯Native App模式的情况

  • 性能要求极高,体验要求极好,不追求开发效率

    一般属于吹毛求疵的那种级别了,因为正常来说如果要求不是特别高,会有Hybrid

选择Web App模式的情况

  • 不追求用户体验和性能,对离线访问没要求

    正常来说,如果追求性能和体验,都不会选用web app

  • 没有额外功能,只有一些信息展示

    因为web有限制,很多功能都无法实现,所以有额外功能就只能弃用这种方案了

选择Hybrid App模式的情况

  • 大部分情况下的App都推荐采用这种模式

    这种模式可以用原生来实现要求高的界面,对于一些比较通用型,展示型的页面完全可以用web来实现,达到跨平台效果,提升效率

    当然了,一般好一点的Hybrid方案,都会把资源放在本地的,可以减少网络流量消耗

选择React Native App模式的情况

  • 追求性能,体验,同时追求开发效率,而且有一定的技术资本,舍得前期投入

    React Native这种模式学习成本较高,所以需要前期投入不少时间才能达到较好水平,但是有了一定水准后,开发起来它的优势就体现出来了,性能不逊色原生,而且开发速度也很快

选择其它方案

  • 小程序(目前移动 App 中开发难度最低的,体验也是仅次于原生+跨平台NativeApp)
  • 接活: 整包(8K+) || 按页面算(500静态||2K) => 另外的薪酬计算方式: 按时薪

08-补-如何分辨一个 App 是原生做的还是 Web 做的

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)]

09-DCloud-HTML5+介绍-HBuliderX安装和创建项目

使用HBuliderX创建项目并且编码运行测试(网页测试/模拟器测试/真机测试)

下载安装HBuilderX

IDE:敲代码的软件.比如VSCode/WebStrom/HBuliderX

下载地址:https://www.dcloud.io/hbuilderx.html

创建项目

  	1. 新建项目
      	2. 5+runtime类型
      	3. 生成项目目录
      	4. 编码
      	5. 测试
           1. 运行->选择chrome
           2. 运行->选择安卓模拟器(夜神)

开发神器

  1. 软件名 everything+wox
  2. 作用:全局搜索软件或者文件
  3. 关键字: windows everything wox配置
  4. 原因: 提高开发效率 (没鼠标)

10-DCloud-HBuliderX模拟器测试和API测试

1. 在浏览器上测试效果
2. 在模拟器上测试
3. 在真机上测试
- 用手机助手连接手机(**安装驱动**)
- 开启手机的**开发者选项**
- **启用 USB 调试**
- 使用数据线连接手机
- 在 HBuilder 中找到:运行 -> 运行到手机或模拟器 -> 你的设备
- [HBuilder/HBuilderX真机运行、手机运行、真机联调常见问题](http://ask.dcloud.net.cn/article/97)

访问 HTML5 + API

  • HTML5+ API Reference

11-DCloud-HBuliderX打包demo

打包发布

  • 配置 manifest.json 文件
  • 在 HBuilder 中找到:发行 -> 原生 App(云打包)
  • 等待一段时间,得到打包结果安装包,然后安装到手机上测试
  • 最后根据需要发布到对应的手机应用商店

配置 manifest

  • Manifest.json文档说明 manifest配置

打包

  • 离线打包
  • 云打包

12-DCloud-HBuliderX打包黑马头条

通过HBuliderX将黑马头条项目打包成app,生成apk文件下载安装测试

  • 测试之前的网站效果
    1. npm run build
    2. http-server- o
  • 打包app
    1. 复制manifest.json到public中
    2. 修改配置
    3. 发布(发行)
    4. 测试

13-MVVM-介绍和演示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6R8iwFcY-1589879881951)(./assets/mvvm-3510948.png)]

  • Model(M):普通的 JavaScript 对象,例如 Vue 实例中的 data
    • 普通数据
  • View(V):视图
    • HTML DOM 模板 #app
  • ViewModel(VM):Vue实例
    • 负责数据和视图的更新
    • 它是 Model数据 和 View 视图通信的一个桥梁
  • 简单一句话:数据驱动视图
<!-- 视图 -->
<template>
  <div>{
     {
      message }}</div>
</template>

<!-- ViewModel -->
把普通的 JavaScript 对象和视图 DOM 之间建立了一种映射关系:
- 数据的改变影响视图
- 视图(表单元素)的改变影响数据

<script>
// Model 普通数据对象
export default {
     
  data () {
     
    return {
     
      message: 'Hello World'
    }
  }
}
</script>

<style>

</style>

研究原理的套路

  1. 玩明白 vue.js
  2. 分析特点 重点vm实例
  3. 假设没有vue.js
  4. 效果一样,证明原理搞懂

14-MVVM-响应式变化原理-介绍

Vue文档说明

Vue的MVVM的实现原理包含以下几部分

  1. Object.defineProperty()方法->MDN文档- > 为属性设置set和get方法->在数据变化时更新视图
  2. js发布订阅模式: 在数据变化时->通知其他位置更新视图

Day02

01-Object.defineProperty()-基本使用

  • 作用: **Object.defineProperty()** 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
  • 说明 Object.defineProperty(obj, prop, descriptor)
    • 形参1: 被操作的对象
    • 形参2:新属性或者现有属性
    • 形参3:属性描述符
    • 返回值:返回被操作的对象
  • 注意:value和get/set不能同时进行设置
   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方法

02-Object.defineProperty()-模拟vm对象

目的: 利用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);

03-Object.defineProperty()-可枚举

目的: 利用属性描述符中的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);
        }

04-Object.defineProperty()-可配置和严格模式

目的:利用属性描述符中的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)

05-Object.defineProperty()-可写性

目的:利用属性描述符中的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)无法同时设置

06-Object.defineProperty()-总结

  1. 作用1: 为对象增加属性
  2. 作用2:一旦设置了访问器->
    1. 取值->get
    2. 该值->set

结果: 1. 模拟vm的结构啦 好开心 2. 修改数据时,执行set->更新视图

07-发布订阅模式的介绍

设计模式: 前人总结的经验,用于开发

介绍:Vue的MVVM原理实现中,当数据变化时,要通知多个位置更新视图,所以我们可以使用设计模式->发布订阅模式来实现->当事情A发生时通知多个人进行事件B->在busevent.js中就是利用了这一模式

组成:

  • 监听/注册一个自定义事件 bus.$on(‘事件类型’,处理函数)
  • 发布事件 bus.$emit(‘事件类型’,参数)

代码

   // 注册事件(订阅消息)
        // 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() {
     

        }

08-发布订阅模式的实现-$on

// 使用构造函数进行封装
        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)
        }

09-发布订阅模式的实现-$emit

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')

10-发布订阅模式的实现-$emit-参数

 EventEmitter.prototype.$emit = function(eventType, ...rest) {
     
            if (this.subs[eventType]) {
     
                this.subs[eventType].forEach((handler) => {
     
                    handler(...rest)
                })
            }
        }

这里用到了ES6语法剩余参数…rest

11-发布订阅模式的实现-$emit-this问题

目的:我们希望$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->实例对象

12-发布订阅模式的实现-总结

// 注册事件(订阅消息)
// 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

13-MVVM实现-DOM复习

  1. DOM是什么?->文档对象模型 document
  2. DOM的作用?->可以通过对象去操作页面元素
  3. document对象里面的每个内容都是节点
  4. 节点类型
    1. 注释节点
    2. 文本节点
    3. 元素节点
子节点 : childNodes 伪数组
节点类型: nodeType === 1 3 8
属性集合 attributes -> 指令  v-xxoo
节点内容: innerText || textContent  ->  {
     {
     }}

找到对应的DOM,更新DOM

14-MVVM实现-分析Vue构造函数-准备结构

  1. 新建mvvm/vue.js->构造函数
  2. 新建测试文件mvvm/mvvm.html->实例化Vue

el的值:string || Element

15-MVVM实现-代理数据proxyData

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
      }
    })
  })
}

16-MVVM实现-数据劫持Observer

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>

17-MVVM实现-编译模板Compiler-设计结构

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) {
     }

18-MVVM实现-编译模板Compiler-辅助方法

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-')
}

Day03

01-MVVM实现-编译模板Compiler-处理文本节点

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>

提示: 实际开发时正则不需要记 但是要能看懂

02-MVVM实现-编译模板Compiler-处理元素节点

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]
      }
    }
  })
}

03-MVVM实现-数据驱动视图

数据变化时,视图的多个位置都需要变化->发布订阅模式EventEmitter.js

  1. observer.js->数据变化时->触发事件$emit
  2. compiler.js->更新视图时->注册事件$on

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]
        })
      }
    }
  })
}

04-MVVM实现-视图变化更新数据

处理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]
        })
      }
    }
  })
}

05-CSR演示和特点

黑马头条项目演示

Client Side Render (客户端渲染 CSR):页面初始加载的 HTML 文档中无核心内容,需要下载执行 js 文件,由浏览器动态生成页面,并通过 JS 进行页面交互事件与状态管理

  • 优点:适合前后端分离开发,方便维护,单页应用中几乎都是客户端渲染
  • 缺点:首次加载慢,不利于 SEO

拼接数据的操作在客户端完成 审查元素看不到数据

提示: 其实我们学过服务端渲染.比如node的项目/案例-> 大量代码在服务器,最后竟然有页面

06-SSR渲染的演示和特点

Server Side Render (服务端渲染 SSR):服务器直接生成 HTML 文档返回给浏览器,但页面交互能力有限。适合于任何后端语言:PHP、Java、Python、Go 等。

  • 优点:响应速度快(首屏渲染速度快),有利于 SEO
  • 缺点:前后端代码混合在一起,难以开发和维护,不适合进行前后端分离开发

拼接数据的操作在服务端完成 审查元素可以看到数据

07-SPA演示和特点

SPA(单页面应用程序)

  • 好处:页面导航不用刷新整个页面,**体验好,**有利于前后端分离开发
  • 缺点:不利于 SEO(因为单页面应用中都是使用客户端渲染的方式),还有首次响应慢(第1次要加载大量的公共资源)

SPA是效果,接近于原生应用

SPA面向用户->体验好->解决用户的痛点:用户是最没耐心/吝啬/想体验好的服务的人-

08-Vue的SSR介绍

Vue的SSR文档

  1. 利于ToC(面向客户)项目的SEO
  2. 提高首页渲染速度

Vue的SSR代码既包含客户端部分,又包含服务端部分

isomorphic web apps(同构应用):基于react、vue框架,客户端渲染和服务器端渲染的结合,在服务器端执行一次,用于实现服务器端渲染(首屏直出),在客户端再执行一次,用于接管页面交互,核心解决SEO和首屏渲染慢的问题。

  • 单页面 + 服务端渲染

09-Vue的SSR演示

Vue的SSR文档

  1. 安装vue vue-server-renderer
  2. 渲染一个Vue实例
  3. 服务端渲染

以上所有操作和编码都是在服务端代码

问题: 目前服务端的代码既有客户端的又有服务端的,此时可以用第三方框架实现Nuxt

10-Nuxt介绍

说明

  1. Nuxt不是Vue官方提供的
  2. Nuxt是基于Vue的服务端渲染的框架
  3. Nuxt.js 预设了利用 Vue.js 开发服务端渲染的应用所需要的各种配置。

作用

基于 Vue、Webpack 和 Babel Nuxt.js 集成了以下组件/框架,用于开发完整而强大的 Web 应用:

Babel作用: 编译各种各样的js->转换浏览器可以认识的js(其中各种各样的js包含ES6/ES7/ES8/ES9/typescript/jsx等)

  1. Vue 2
  2. Vue-Router
  3. Vuex (当配置了 Vuex 状态树配置项 时才会引入)
  4. Vue 服务器端渲染 (排除使用 mode: 'spa')
  5. Vue-Meta

11-Nuxt创建项目

  1. mkdir <项目名>

  2. $ cd <项目名>

  3. npm init

    {
           
      "name": "my-app",
      "scripts": {
           
          // npm的功能:自定义指令(封装指令用的)
          // "start" -> npm start
        "dev": "nuxt"
      }
    }
    
  4. npm install --save nuxt
    
  5. mkdir pages
    
    
    
  6. 创建第一个页面->pages/index.vue

    
    
  7. npm run dev
    
    1. 自动生成路由配置
    2. 生成.nuxt文件夹(编译客户端和服务端代码的结果)
    3. 自动修改代码重新编译
  8. 新建组件pages/list.vue->自动生成路由/自动重启

12-Nuxt路由

  1. 按照路由的语法规定处理动态路由
    1. 新建pages/user/_id.vue
    2. 测试 /user/100
  2. 处理路由嵌套
    1. 新建组件pages/user.vue->父组件 设置

父组件名字user 子组件所在的文件夹同名user

13-Nuxt验证服务端渲染和单页应用

  1. 验证是否是服务端渲染的
    1. 审查元素->看是否可以看到页面内容
    2. 编写log->看服务端控制台和客户端控制台->二者都执行

14-Nuxt的asyncData

  1. 作用: 为组件data提供数据
  2. 特点
    1. this->没意义
    2. 顺序:在组件创建之前beforeCreate执行
    3. 服务端(自定执行)和客户端(路由更新 )都会执行
  3. 场景: 获取首屏数据 axios请求

文档

测试接口

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>

15-Nuxt的服务端和客户端发送请求的区别

axio支持客户端和服务器(node)环境

  1. 客户端: 请求 <-> 接口服务器
  2. 服务器: 客户端<->中间件(asyncData中axios代码)<->接口服务器

难点: 中间件二次处理的数据操作(接口数据不便于直接使用)

16-Nuxt的生命周期钩子函数

需要注意的是,在任何 Vue 组件的生命周期内, 只有 beforeCreatecreated 这两个方法会在 客户端和服务端被调用。其他生命周期函数仅在客户端被调用。

前后端都调用:

  • beforeCreate
  • created

在服务端渲染期间不被调用:

  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • activated
  • deactivated
  • beforeDestroy
  • destroyed
 beforeCreate() {
     
    console.log('beforeCreate------')
    console.log(this.$isServer)
  },

17-Nuxt脚手架创建项目

  1. 使用Nuxt脚手架创建项目->文档
  2. 执行项目
  3. 熟悉目录结构

pages是需要关注的

18-案例-准备素材-介绍

案例: TODOlist->表格增删改查

[email protected] 12345678

  • 在线示例:
    • https://demo.realworld.io/
  • 接口文档:
    • https://github.com/gothinkster/realworld/tree/master/api
  • 页面模板:
    • https://github.com/gothinkster/realworld-starter-kit/blob/master/FRONTEND_INSTRUCTIONS.md
  • 所需资源
    • https://github.com/gothinkster/realworld-starter-kit

19-案例-页面布局

  1. 在页面引入样式资源->资源链接

    1. 视图-模板
    2. 新建app.html
    3. 引入样式文件
    
    <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>
    
    
  2. 有公共头部和尾部->使用Nuxt提供的视图布局

    1. Nuxt-视图-布局
    2. 默认layouts/default.vue
    <template>
    头部->来源于RealWorld素材    
      <nuxt/>
    尾部->来源于RealWorld素材    
    template>
    
  3. 修改了根目录的文件,需要重启,测试

20-案例-静态页面

  1. 准备首页index.vue

    
    
    
    
    
    
    
  2. 准备登陆页login.vue

    
    
    
    
    
    
    
  3. 准备文章详情页articles/_slug.vue

    
    
    
    
    
    
    

自动生成路由,可以通过手动修改标识进行测试

21-案例-axios模块

使用Nuxt提供的axios模块进行请求->文档

  1. 安装
  2. 配置

22-案例-文章列表

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 };
  }
// 遍历数据->测试

你可能感兴趣的:(vue进阶,vue.js)