• 【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,就不得不说到 Thymeleafjspfreemarker 等后端模板化语言,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 服务器,从而前端可以单独启动。
     
     
  • 相关阅读:
    小程序 视频
    b161: NOIP2007 4.Hanoi双塔问题
    命名规则、.gitignore、freopen()
    c++学习记录(九)
    c++学习笔记(八)
    2020面向对象程序设计寒假作业2
    c++学习记录(七)
    c++学习记录(六)
    c+学习记录(五)
    c++学习记录(四)
  • 原文地址:https://www.cnblogs.com/easy-tech/p/14472058.html
Copyright © 2020-2023  润新知