JavaScript

基于 koa 的微服务

使用 JavaScript 构建微服务时候似乎没有什么多余的选择,毕竟 grpc 都还没有合适的解决方案。所以还是采用 HTTP + JSON 的方式啦。 这里我选择了 koa 这个框架,可以很好地使用 Promise 和 async/await 的语法,再也不用写一大堆回调了,写起来还能轻松一些。

koa 作为一个微框架,简直是把 node 生态圈 isArray 都要写个库这种做法发挥到极致了。 默认情况下,koa 连 POST 过来的 JSON 都不给解析出来。所以几乎任何操作都需要安装对应的库,下面总结一下必备的一些库。

解析 JSON

使用 koa-bodyparser: https://github.com/koajs/bodyparser

解析命令行参数

使用 Command Line Args: https://github.com/75lb/command-line-args/wiki/Typical-usage-example

参考文献

  1. https://koajs.com/#request

Python 和 JavaScript 语法对比

Python 和 JavaScript 语法对比

命名

  1. 注意使用驼峰变量名, 不要使用下划线变量名

字符串

格式化

JavaScript:

`hello ${name}`

Python:

fhello {name}

文件

打开文件:

JavaScript:

const fs = require(fs).promises;  // 使用 async/await 版本的 fs 模块

  await fs.writeFile(filename, data);  // 写入文件

  // 文件是否存在
  try {
      await fs.stat(filename)
      exitst = true
  } catch (e) {
      exists = false
  }

Python:

with open(filename, w) as f:
      f.write(data)

  os.path.exists(filename)  # 文件是否存在

数组

切片:

JavaScript:

const arr1 = arr2.slice(3, 5);

Python:

arr1 = arr2[3:5]

前端框架 Vue 学习笔记

之前在网页里小范围的用了vue, 感觉用起来非常爽, 现在打算做自己的笔记应用. 这次打算做成SPA的形式, 前端全部用vue来写. 需要node的一些东西, 记录下学习过程. 迈向全栈,哈哈 ^_^

安装

首先, 全局安装vue:

npm install --global @vue/cli

data 属性

只有在创建时提供的 data 属性才是响应式的,在创建之后在添加新元素就不管用了。

当然还可以使用 app.$watch 方法显式创建一些监听器

// $watch 是一个实例方法
vm.$watch("a", function (newValue, oldValue) {
  // 这个回调将在 `vm.a` 改变后调用
})

methods 和 computed

methods 定义了一些可以调用的方法,他的值也可以用来插值。但是最好使用 computed,因为 computed 是有缓存的。

computed 属性也可以设置 setter,所以实际上,computed 属性相当于对现有属性的一种映射和变化。

v-bind 和 v-model

  • v-bind 用于单项绑定:在 HTML 属性中使用 v-bind 绑定, 标签中使用 {{ }}。只能使用表达式,而不能使用语句。
  • v-model 用于双向绑定:在 input 这类用户可以输入的组件中,需要双向绑定,使用 v-model.

v-bind 类似的指令还有 v-once 和 v-html

v-model 实际上等价于

<input v-model="searchText">
<input
  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value"
>

要想在自定义输入组件中支持 v-model 的话,就需要使用 v-bind 和 v-on 两个方法了,而不能直接使用 v-model。

v-if 和 v-for

这两个就和所有模板系统中的 if 和 for 一样。vue 中提供的额外方便之处是,可以使用 template 标签,这样就不会多一个标签了

vue 是懒渲染的,因此会尽可能地复用组件,可以使用 key 来区分

v-if 是真正的条件式渲染,v-show 则只是在切换 display 属性

vue 没有代理数组的赋值方法,所以需要使用 app.$set 方法

@

如果只是指定一个事件处理函数的话,那么参数就是 event。如果自己指定了参数的话,可以使用 $event 来代表 event

还可以使用 .prevent 和 .stop 等修饰符

component

vue 中最终要的概念就是组件了。使用组件来模块式得构建应用。需要通过 props 属性来定义组件中的属性

<div id="app-7">
  <ol>
    <!--
      现在我们为每个 todo-item 提供 todo 对象
      todo 对象是变量,即其内容可以是动态的。
      我们也需要为每个组件提供一个“key”,稍后再
      作详细解释。
    -->
    <todo-item
      v-for="item in groceryList"
      v-bind:todo="item"
      v-bind:key="item.id">
    </todo-item>
  </ol>
</div>

Vue.component("todo-item", {
  // todo-item 组件现在接受一个
  // "prop",类似于一个自定义特性。
  // 这个 prop 名为 todo。
  props: ["todo"],
  template: "<li>{{ todo.text }}</li>"
})

props 是一个数组,用来声明组建的属性。然后通过属性来传递。

组件还可以通过 $emit 来发送事件,这些事件可以被所有的组件监听到,就像普通的 DOM 事件一样。

声明周期函数

在固定的周期,vue 会调用的一些函数 created, mounted 等。需要注意的是,不要使用胖箭头函数。

new Vue({
  data: {
    a: 1
  },
  created: function () {
    // &#x60;this&#x60; 指向 vm 实例
    console.log("a is: " + this.a)
  }
})
// => "a is: 1"

创建vue应用

因为我们会直接通过*.vue文件来编写vue的组件, 因此需要使用webpack打包编译. 另外我们需要使用官方的vue-router来

vue init webpack notelet

这条命令基于 webpack 这个模板创建了notelet这个应用, 也就是我们的笔记应用.

打开 src/router/index.js 可以看到 vue 创建的router的代码, 其中@src目录的缩写.

import Vue from "vue"
  import Router from "vue-router"
import Hello from "@/components/Hello"

Vue.use(Router)

export default new Router({
  routes: [
    {
    ¦ path: "/",
    ¦ name: "Hello",
    ¦ component: Hello
    }
  ]
})

然后打开 src/main.js, 可以看到在里面使用了 App 来作为我们的跟组件

// The Vue build version to load with the &#x60;import&#x60; command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from "vue"
import App from "./App"
import router from "./router"

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: "#app",
  router,
  template: "<App/>",
  components: { App }
})

执行 npm run dev, 然后打开 http://localhost:8080/ 就可以看到我们的vue应用了. 注意, 在写这篇文章的时候 node 8.x 下似乎有bug, 导致 app.js 加载不出来, 安装 6.x 就好了. 该死的node.

打开 App.vue, 也就是我们的根组件, 可以看到下面的内容

<template>
  <div id="app">
  ¦ <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: "app"
}
</script>

<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

注意其中的 router-view 便签, 意思就是路由的内容都在 router-view 中显示.

添加一个新的组件和路由

接下来我们添加一个”关于”页面. 打开src/router/index.js, 改成下面这样:

export default new Router({
  routes: [
  ¦ {
  ¦ ¦ path: "/",
  ¦ ¦ name: "Hello",
  ¦ ¦ component: Hello
  ¦ },
  ¦ {
  ¦ ¦ path: "/about",
  ¦ ¦ name: "About",
  ¦ ¦ component: About
  ¦ }
  ]
})

然后添加 src/components/About.vue 文件

<template>
  <div class="hello">
  ¦ <h1>About Notelet</h1>
  ¦ <p>This is a simple note app</p>
  </div>
</template>

<script>
export default {
  name: "About",
  data () {
  ¦ return {
  ¦ ¦ msg: "Hello Vue"
  ¦ }
  }
}
</script>

然后更改 App.vue 文件

<template>
  <div id="app">
  ¦ <router-link :to="{name: "Hello"}">Home</router-link>
  ¦ <router-link to="/about">About</router-link>
  ¦ <router-view></router-view>
  </div>
</template>

注意, 我们使用about指向了 About 这个组件, 而使用 hello 指向了 Hello 这个组件, 注意其中还动态传递了参数.

[1] https://scotch.io/tutorials/getting-started-with-vue-router

html Node vs Element

  • Node vs Element

HTML Document consists of different node, element is one type of node.

  • NodeList vs HTMLCollection

NodeList is a collection of node, HTMLCollection is a collection of element.

  • HTMLCollection vs NodeList vs Array

HTMLCollection 和 NodeList 都是动态的,会随着 DOM 的变化而变化

Array 是静态的数据结构

浏览器用于替换 ajax/xhr 的 fetch api

fetch 是近年来浏览器实现的一个用于取代 xhr 的 API,相比于 xhr 来说更加简单易用安全且强大。主要区别有:

  • fetch 基于 promise,可以使用 await 直接调用;
  • fetch 对于 400 和 500 错误依然会正常返回而不会报错。只有在网络错误的时候才会抛出异常;
  • fetch 默认只会携带同一个域名下的 cookies,也就是 same-origin,默认更安全;
  • fetch 不接受跨域的 Set-Cookie。

基本用法

const response = await fetch("http://example.com/movies.json");
const myJson = await response.json(); // text() 返回纯文本
console.log(JSON.stringify(myJson));
  • response.ok 是否请求返回了 2XX 代码
  • response.status 返回的状态码,比如 200, 404 等
  • await response.blob() 返回二进制文件
  • await response.text() 返回文本文件
  • await response.json() 返回解析的 json

增加选项

try {
  // Default options are marked with *
  const response = await fetch(url, {
    method: "POST", // *GET, POST, PUT, DELETE, etc.
    mode: "cors", // no-cors, *cors, same-origin
    cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
    credentials: "same-origin", // include, *same-origin, omit
    headers: {
      "Content-Type": "application/json"
      // "Content-Type": "application/x-www-form-urlencoded",
    },
    redirect: "follow", // manual, *follow, error
    referrer: "no-referrer", // no-referrer, *client
    body: JSON.stringify(data) // body data type must match "Content-Type" header
  });
  console.log(await response.json());
} catch (error) {
  console.error(error);
}

携带 cookies

fetch 的 credentials 有三个选项:

  • omit 不携带任何 cookies
  • include 携带所有 cookies
  • same-origin 只有向当前网站的同源域名发送请求时才携带 cookies

其中 same-origin 是默认选项。

使用自定义 headers 和 request

const headers = new Headers();
headers.append("Content-Type", "text/plain");
headers.append("Content-Length", content.length.toString());
headers.append("X-Custom-Header", "ProcessThisImmediately");

// 另一种方法是直接使用字典
const headers = new Headers({
  "Content-Type": "text/plain",
})

const init = {
  method: "POST",
  headers: headers,
  mode: "cors",
  cache: "default",
}

const request = new Request("flowers.jpg", init);
const rsp = await fetch(request);

参考

  1. https://developer.mozilla.org/en-US/docs/Web/API/FetchAPI/UsingFetch
  2. https://stackoverflow.com/questions/34558264/fetch-api-with-cookie
  3. https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials

JavaScript snippets

get parameter from url

function getParameterByName(name, url) {
    if (!url) url = window.location.href;
    name = name.replace(/[\[\]]/g, "\\$&");
    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
        results = regex.exec(url);
    if (!results) return null;
    if (!results[2]) return '';
    return decodeURIComponent(results[2].replace(/\+/g, " "));
}

thinking in vue

basic usage

The created vue instance will proxy its data member

<div id="app">
  <ol>
    <li v-for="company in companies">
      <a v-bind:href="company.link">{{ company.text }}</a>  // bind 更新参数
            <button v-on:click="reverseText">逆转消息</button>
    </li>
  </ol>
</div>

let vm = new Vue({
    el: '#app',
        data: {
            companies: [
                    {text: 'Google', link: 'http://google.com'},
                        {text: 'fackbook', link: 'http://facebook.com'},
                        {text: 'apple', link: 'http://apple.com'}
                ]
        },
        methods: {
            reverseText: function() {
            // 注意 this 绑定到了触发这个事件的元素内部
                    this.company.text = this.company.text.split('').reverse.join('');
                }
        }   
});

vm.companies.push({text: 'Amazon', link: 'http://amazon.com'});
// computed 属性可以绑定一个虚拟的属性到几个不同的属性上, 有点类似python的@property
// ...
computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
// ...

# 适合用来做自动保存文档等工作
<script>
var watchExampleVM = new Vue({
  el: '#watch-example',
  data: {
    question: '',
    answer: 'I cannot give you an answer until you ask a question!'
  },
  watch: {
    // 如果 question 发生改变,这个函数就会运行
    question: function (newQuestion) {
      this.answer = 'Waiting for you to stop typing...'
      this.getAnswer()
    }
  },
  methods: {
    // _.debounce 是一个通过 lodash 限制操作频率的函数。
    // 在这个例子中,我们希望限制访问 yesno.wtf/api 的频率
    // ajax 请求直到用户输入完毕才会发出
    // 学习更多关于 _.debounce function (and its cousin
    // _.throttle),参考:https://lodash.com/docs#debounce
    getAnswer: _.debounce(
      function () {
        if (this.question.indexOf('?') === -1) {
          this.answer = 'Questions usually contain a question mark. ;-)'
          return
        }
        this.answer = 'Thinking...'
        var vm = this
        axios.get('https://yesno.wtf/api')
          .then(function (response) {
            vm.answer = _.capitalize(response.data.answer)
          })
          .catch(function (error) {
            vm.answer = 'Error! Could not reach the API. ' + error
          })
      },
      // 这是我们为用户停止输入等待的毫秒数
      500
    )
  }
})
</script>

flow control

v-if

v-else 元素必须紧跟在 v-if 或者 v-else-if 元素的后面——否则它将不会被识别。v-else-if 也是.

可以使用template来包装多个元素:

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>

v-for

v-for的基本语法如前所述, 另外还可以采用可选参数 key. <li v-for="(item, index) in items">.

v-for还可以遍历对象, <li v-for="value in obj">

v-for还可以直接遍历range, <li v-for="n in 10">

事件

在vue中, 绑定的事件如果需要参数, 可以使用

<button v-on:click="warn('Form cannot be submitted yet.', $event)">
  Submit
</button>

的形式, 其中 $event 指的是原声事件.

修饰符

vue中的事件绑定函数可以使用一些修饰符来指定一些附加的效果. 常用的有 .prevent, .stop, .self, .once 等.

像这样: <a v-on:click.stop="doThis"></a>

对于键盘时间, 还可以使用修饰符来指定键值: <input v-on:keyup.enter="submit">

mustache vs v-bind

mustache can only be used in textContent of an element, v-bind is used for attribute.

<a v-bind:href="url">{{ link_text }}</a>

shortcut

<!-- 完整语法 -->
<a v-bind:href="url"></a>
<!-- 缩写 -->
<a :href="url"></a>
<!-- 完整语法 -->
<a v-on:click="doSomething"></a>
<!-- 缩写 -->
<a @click="doSomething"></a>

v-model 做双向绑定

use v-model to double bind data between input and js.

<div id="app-6">
  <p>{{ message }}</p>
  <input v-model="message">
</div>

var app6 = new Vue({
  el: '#app-6',
  data: {
    message: 'Hello Vue!'
  }
})

值得注意的是, v-model 本质上只是一个语法糖.
<input v-model="something"> is just a syntax sugar to <input v-bind:value="something" v-on:input="something = $event.target.value">

text area

需要注意的是, textarea 时间上相当于一个input组件, 不能在testarea内部使用 {{value}} 的语法, 而应该使用 v-model

<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>

修饰符

就像事件一样, 也可以指定一些修饰符给 v-model, 常用的有 .trim

vue-component

vue 的component中三个重要的概念: props, events, slots.

其中props向下传递, 用于parent组件向child组件传递值. child组件对于props的访问只能是只读的. 在child组件中使用v-bind:var="var"来访问定义的props. 注意在组件中不能更改props, 如果需要更改他, 请把他赋值给其他变量, 或者使用 computed 属性.

如果把模板直接放到dom中会有一些标签渲染不出来, 建议放到

// 在vue中注册一个组件, 大多数传递给vue实例的参数都可以使用, 除了 data 必须是一个函数

Vue.component('todo-item', {
  // todo-item 组件现在接受一个"prop",类似于一个自定义属性. 这个属性名为 todo。
  props: ['todo'],
  template: '<li>{{ todo.text }}</li>',
  data: function() {},
})

<div id="app-7">
  <ol>
    <!-- 现在我们为每个todo-item提供待办项对象    -->
    <!-- 待办项对象是变量,即其内容可以是动态的 -->
    <todo-item v-for="item in groceryList" v-bind:todo="item"></todo-item>
  </ol>
</div>

Vue.component('todo-item', {
  props: ['todo'],
  template: '<li>{{ todo.text }}</li>'
})
var app7 = new Vue({
  el: '#app-7',
  data: {
    groceryList: [
      { text: '蔬菜' },
      { text: '奶酪' },
      { text: '随便其他什么人吃的东西' }
    ]
  }
})

事件

parent组件可以监听子组件的事件, 从而实现通信:

这个例子中, 子组件通过$emit()函数发送increment事件. parent组件通过监听子组件的increment事件, 从而获得子组件的消息.

<div id="counter-event-example">
  <p>{{ total }}</p>
  <button-counter v-on:increment="incrementTotal"></button-counter>
  <button-counter v-on:increment="incrementTotal"></button-counter>
</div>
Vue.component('button-counter', {
  template: '<button v-on:click="incrementCounter">{{ counter }}</button>',
  data: function () {
    return {
      counter: 0
    }
  },
  methods: {
    incrementCounter: function () {
      this.counter += 1
      this.$emit('increment')
    }
  },
})
new Vue({
  el: '#counter-event-example',
  data: {
    total: 0
  },
  methods: {
    incrementTotal: function () {
      this.total += 1
    }
  }
})

slots

slots vs props: slots 用于显示一大片的包含html代码的替换块, 而props 用于显示值, 有点类似 v-bind 和 {{}} 的区别.

Props 允许外部环境传递数据给组件
Events 允许从外部环境在组件内触发副作用
Slots 允许外部环境将额外的内容组合在组件中。

vue router

basic: 将组件(components)映射到路由(routes),然后告诉 vue-router 在哪里渲染它们。

<div id="app">
  <p>
    <router-link to="/user/foo">/user/foo</router-link>
    <router-link to="/user/bar">/user/bar</router-link>
  </p>
  <router-view></router-view>
</div>

<script>
const User = {
  template: `<div>User {{ $route.params.id }}</div>`
}

const router = new VueRouter({
  mode: 'history',  // 这样才能使用 html5 的history api
  routes: [
    { path: '/user/:id', component: User }
  ]
})

const app = new Vue({ router }).$mount('#app')
</script>

vue lifecycle

using in chrome extension

chrome does not allow eval and new Function() in extensions, vue relies on it. you need to use CSP version of vue or relax the restriction by chrome.

See also:

  • https://developer.chrome.com/extensions/contentSecurityPolicy#relaxing-eval
  • https://stackoverflow.com/questions/34615503/vue-js-in-chrome-extension
  • https://vuejs.org/v2/guide/installation.html#CSP-environments

JavaScript Selection and Range

basics

window.getSelection and document.getSelection all returns the Selection object, the selection object is almost useless.

window.getSelection.getRangeAt(0) returns a Range object. for history reasons, there is only one range in each selection.

rangeAncestor = range.commonAncestorContainer; commonAncestorContainer is the common ancestor of the range elements.