【JavaScript】一个后端兼职Vue.js前端的开发回顾、总结

前言: 即将不碰前端了, 仅以此文献给将来的自己, 为那时省些力气,总的而言是挺有意思的学习之旅,不算一无所获。
 

一. 开始之前

1.历史: 如同Java 发展至今日, 拥有了自己成熟的包管理系统, IDE, 调试开发环境, 开源库 等等完备的生态系统一样,  js历经几十年的发展, 也不是那个脆弱的, 依靠浏览器活着的语言.
尤其是新的 fs 模块也引入了对本地文件IO的处理,使其更加地“本地化”
 
2.JavaScript 与 Java
JavaScript 真的一无可取吗? 不是的. 作为一门天生异步的语言, 现如今各大js引擎的优化已经让它做到了不慢, 综合速度不落下风 : 
想象你要写个小工具, 没工夫去画流程图, 做详尽的设计, 需要快速的调试, 尽快实现想法. 
well, js 的优势体现的淋漓尽致, 你不需要一个 IDE, 也不需要配环境变量, 调试很多东西, 还要找一台足够快的电脑避免影响效率. 统统不需要 , 你只需要找个浏览器, F12, 或者cmd -> node  把用nodepad++ 写的代码直接贴进去即可。
又或是, 你在写正则, 想试试某个规则能否匹配到自己想要的东西;
又或是, 你想知道算法A和算法B哪个更快, 等等.
可能jshell靠着“抄袭”Scala shell 追上来了一点点, 但体验仍旧输了一截。 
 
试图理解二者的区别,以及Js为何在效率上更胜一筹,还是从几个日常的例子入手。
这里是一段Java想要使用异步-回调模式需要写的代码:
FutureTask<Object> t=new FutureTask<>(new Callable() {
            @Override
            public Object call() throws Exception { // can throw exceptions
                System.out.println("executing ...");
                return null;
            }
        });
//        service.submit(t);// runs in executor
//        t.run();// runs in current thread.
new Thread(t).start();
t.get();// if not running, block
然后你还要注意: FutureTask的默认run()是在本线程, 自己一时犯蠢忘掉又是麻烦.
而对比之下, 这是js的
function executeTask0(id){
    new Promise(()=>{
        exeFor2sec();
    }).then(console.log('task ' + id + ' complete'))
}
另外一点是, 它对函数式的支持允许了更灵活的实现, 因而不需要复杂的设计去实现想法.
 
或是你想要判断回调在不在 ?
if(callback) 即可
function somefuc(callback){
    if(callback){
        callback()
    }
}

 

假如是Java7 , 你还需要使用匿名对象去实现回调. Java8 ? 语法糖罢了, 不但需要思维转换, 还要记住被匿名的对象有几个参数要传
同样以异步回调为例:
Future<?> submit = service.submit(()->{
   ... 
});
看上去足够简单了,实际上你仍要记住,submit传入的是Runnable,然后runnable 的run()不需要任何入参。
 
或是你想要个对象 var a ={} 即可, 也不需要声明Class,Nothing.
 
一些常用函数Js也给出了更便捷的处理方式
如:判断是不是数值 ?
if(Number(x)){
    console.log('x is a number')
}
 
而Java则需要
boolean isNum = true ;
try{ 
    Integer.valueOf(x);
    Double.valueOf(x);
}catch(NumberFormatException e){ // 还要考虑不能捕获全体, 否则会漏掉一些情况
    isNum= false;
}
System.out.println(" x is a number")
同时,JS还支持许多好玩的特性
如:柯里化
柯里化来源应该是函数式编程的学术定义,甚至不需要怎么理解,只要记住一个大前提,js函数是可以作为返回值的
于是我们有:
(x=>x+2)(2) == 4  // true xqcl 
那么上述 x+2 作为新函数返回后:
(y=>(x=>x+y)(2))(2) == 4 
其中,内层 x + y 在经过第一次运算后,成为 2+y,整体作为函数返回给外部函数去运算,于是有 2 + 2 = 4
 
如:解构赋值
结构赋值允许我们在给对象赋值时无需依赖诸如反射等手段,直接将值赋值给目标对象。如:
var a, b, rest;
[a, b] = [10, 20];
console.log(a); // 10
console.log(b); // 20
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(a); // 10
console.log(b); // 20
console.log(rest); // [30, 40, 50]
({ a, b } = { a: 10, b: 20 });
console.log(a); // 10
console.log(b); // 20
如:融入语言的异步模型,
Javascript 提出了一个模型,也即事件循环,这种设计常见于游戏引擎,当然也存在于操作系统中,只不过不会这么简单。通过事件循环,js 得以把大部分操作交由一个队列处理,而主线程会持续不断地询问这个队列有没有任务,从而屏蔽掉线程地概念,没有了线程,就没有了并发问题。
对于这个队列,Nodejs如是说到:
 
当调用 setTimeout() 时,浏览器或 Node.js 会启动定时器。 当定时器到期时(在此示例中会立即到期,因为将超时值设为 0),则回调函数会被放入“消息队列”中。
在消息队列中,用户触发的事件(如单击或键盘事件、或获取响应)也会在此排队,然后代码才有机会对其作出反应。 类似 onLoad 这样的 DOM 事件也如此。
事件循环会赋予调用堆栈优先级,它首先处理在调用堆栈中找到的所有东西,一旦其中没有任何东西,便开始处理消息队列中的东西。
 
然后随着Es6的提出,工作队列被引入,另一种特殊API被引入(Promise),通过使用 promise 并为其准备回调函数,可以将任务异步提交,并立即执行。
 
new Promise(()=>{
        exec();
    }).then(console.log('task ' + id + ' complete'))
 
提到这点不得不提一句 await 关键字,上文提到,2处异步事件不在同一处队列中运行,自然也不对应着同一条线程,但如果,如果,真的要等待 ,就只能通过 await 关键字,通过声明一个函数是 async 的,你可以等待特定的await 函数执行结束。
而且神奇的是,promise中的setTimeout() 放入的函数,也将被等待。
 
async function executeTask(id){
// await can only be used inside a async task
// it will await till set Timeout Complete
  await new Promise(resolve => {
    setTimeout(() => {
            resolve('resolved');
        }, 2000);
    })
    console.log('task ' + id + ' complete')
}

 

 
具体见这几篇文章,详细地说明了上述的问题,并且阐述了一些技巧
 

二. 开发环境搭建

  1. package.json
    以配置文件引入, 现代js离不开包管理, 彼时的js仍是html的附属脚本, 严重依赖全局变量, 每一个外部自定义函数, 都需要不厌其烦地引入, 比如vue官网的快速开始, 
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
但以这种方式, 难免有部分引入的函数没有被使用到。
在网络上, 它吃掉了不必要的带宽 (尽管有 xx-min.js的方式去尽量减少带宽使用, chrome 等现代浏览器为了优化这一部分也做出了本地缓存的努力;
而对于网页本身, 则耗费了不必要的加载时间, 于是, 类似webpack等打包技术应运而生. 
 
随着js的逐渐发展, 谷歌将v8 引擎单独独立出来, js也从浏览器中“打破了第四面墙”, 来到外面的世界, 成了独立的顶级项目 Node.js , nodejs 可能早生几年, 就没python什么事了.
随着node 一同而来的, 还有npm包管理系统.
node.js 的安装包自带npm包管理工具,只要不刻意勾选掉,按照默认安装就可以了。
 
npm 依靠package.json来查找需要的依赖。
默认情况下,
如果当前文件夹存在 package.json ,npm install 将安装依赖到当前文件夹下的node_modules文件夹中(没有的话会自动创建)。
如果当前文件夹下不存在package.json,则将安装到当前用户文件夹下 (linux: ~/) 
大多数时候我们并不需要手动写下面这个文件, 只需要运行npm install package_name  即可, 着实有 apt/yum 的感觉了.
{
  "name": "test",
  "version": "4.5.0",
  "private": true,
  "scripts": {
    "dev-online": "vue-cli-service serve --open --host 0.0.0.0 --port 9000",
    "dev": "vue-cli-service serve --open",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "eslint": "eslint --fix --ext .js,.vue src"
  },
  "dependencies": {
    "axios": "^0.19.0",
    "core-js": "^2.6.5",
    "cropperjs": "^1.5.4",
    "echarts": "^4.7.0",
    "element-ui": "^2.13.1",
    "jsencrypt": "^3.0.0-rc.1",
    "marked": "^1.2.7",
    "view-design": "^4.0.2",
    "vue": "^2.6.10",
    "vue-router": "^3.1.3",
    "vuex": "^3.0.1"
  },
  "devDependencies": { 
  }
}
如, 为你的前端项目引入vue的简单方式:  $ npm install vue # 最新稳定版
  1. lanuch.json
    来到第二步, vscode, 作为后现代IDE, vscode吸取了之前各家的教训, 奠定了以插件为本\ 统一入口, 简化UI 的总体逻辑
任何时候, 忘了功能在哪, Ctrl + Shift + P
缺了功能, 去插件商店看看即可。
它需要注意的地方不多, 总体而言只需要如下面的运行配置即可. 
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "Launch Program",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}\\AsyncFunctions.js"
        }
    ]
}
  1. babel
    前端代码“不需要编译”,这是常识,然而也带来了一些缺点,比如编译期检查,如Java等,现代IDE如 eclipse或idea通过即时编译提供报错信息,辅助开发者发现代码中的错误,
而Js IDE则基本无法提供这点,即使是vscode也是如此。Babel的出现缓解了这个问题,
实际上,vue-cli 的build 可以通过babel提供兼顾所有最新的 ES2015+ 语言特性,通过下列命令提供
vue-cli-service build --modern
  1. eslint
    它有自作聪明的点,比如它极其不待见 ==,同时不允许无空格 ,但大部分是可取的,常用来检查 js 中不符合规范的写法。同时 elint 也提供了 --fix等一键式命令辅助代码规范
 

三. 主角: Vue

Vue, 将前端重复性劳动简化为组件, 同时保证了足够简单. 
提到Vue,就不得不说到 Thymeleaf\jsp\freemarker 等后端模板化语言,freemarker严格意义上是xml 模板语言,并不限于html。
相比 Thymeleaf 等模板语言靠服务器端生成html, 也可以弄成组件化, 但仅针对html而言, 不能(不建议)额外绑定js 函数\ 事件 等, 从用户角度还是不如vue 这种前端组件化. 
JSP 过于容易将业务和代码及页面效果强耦合的“特点”, 也是如今不被推荐的最大原因。
 
1.组件\内置组件\slot
Vue由组件组成,每个组件都由2部分组成。
一是HTML 
二是vue对象
var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } })
 
1.1 引用组件$refs
在我们引入组建后,$refs 使得父组件可以直接访问子组件的方法、字段等(尽管不建议这么干)
this.$refs.select0.setQuery(null)
  • 组件绑定:
   双向绑定:v-model
常用于表单输入,因为此时较有可能出现这样的需求:输入某字段时显示预览效果。
   单向绑定:v-bind 常以语法糖的形式出现 :value="some"
       引入:事件 $emit()
  • 生命周期
同安卓每个activity的生命周期一样,想介入vue的渲染过程,可以使用 vue 提供的生命周期回调。
如created(最常用)
created: function () { 
// `this` 指向 vm 实例 console.log('a is: ' + this.a) 
}
具体生命周期如下,回调逻辑同上。
  •     监听函数:用来针对某些值更新需要触发额外操作
watch: {   
    id: function (new_id, old_id) {
        updateOptions(new_id);    
    }
}
  •     生命周期函数无法解决的问题:页面初始化时,单个子组件渲染完成后自动加载
 
1.2 内置组件
常见于各大UI框架,如elemet-UI, iview等。
 
1.3 slot 
slot可以看作是简易便捷的渲染函数,通过 <template/> 元素定义的slot可以在随后使用,
 <template #header>
    <h1>Here might be a page title</h1>
  </template>
vue将自动替换下文中的 <slot></slot>
<header>
    <slot name="header"></slot>
  </header>

 

1.4 组件化带来的思路变化:
由于vue组件为王, 组件是最小单元,这样避免了一部分问题,如全局变量污染问题,但是从现在开始,组件间的互操作就不那么容易了。
比如两个下拉框,前一个选中后,后一个根据前一个值筛选下拉框内容。
如果是原生js,或者jQuery,思路会是这样子:
第一个下拉框值选中后,更新第二个下拉框的内容(<option>)
但如果切换成vue则要转变思维,
页面本身是一个组件,而两个下拉框是2个子组件,于是涉及到2个问题,子组件间通信,和父子组件通信。
子组件A需要把值更新到父组件的某个参数,
function onChange(id){
    this.$emit('update:id', id)
}
同时另一个子组件B绑定内部值到那个参数,并在内部监听该参数。
watch: {   
    id: function (new_id, old_id) { 
        updateOptions(new_id);    
    }
}
于是我们有:
tricks:  两个控件绑定同一个值,触发彼此刷新
<choose-a :value.sync="formData.id" /> <!-- tricks 两个控件绑定同一个值,触发彼此刷新,此处为id-->
<choose-b :innerId="formData.id" />

 

 
2.”响应式“
vue框架负责对dom元素建立watcher监听,所以对其内部实现的推测是:解析模板,然后根据v-if 等标签,解析完成后,根据解析结果(抽象语法树?)建立创建对各dom元素的监听。
至于虚拟dom其实不需要深入了解,只需要知道是异步更新就够了。
内容详见:
 
3.路由
     意义: 减少跳转\iframe嵌套等复杂化业务的东西. 
常见用法
this.$router.push('/weekText')
4.Vuex
    如前文所说,vue在引入了模块化之后,解决了全局变量的问题,但是此时,如果仍需要“old fashion”,你就需要vuex 了。通过将vuex 注册为一个组件,再在别处引用,vuex可以作为前端全局变量的所在地,具体实现细节不需要太关心。
new Vuex.Store({
  modules: {
    app,
    user,
    data
  }
})

 

5.Es6模块
    使用Babel后可以放心的使用es6 语法作为vue的组件注册语法。
6.Vue-cli 
目前使用的不多,主要是vue-cli-sevice 开启node.js 服务器,从而前端可以单独启动。
 
 
posted @ 2021-03-02 23:22    阅读(565)  评论(0编辑  收藏  举报