Vue学习笔记 - 基础篇

先是把官方手册一字不落过一遍,有例子要动手写,写的时候不要复制代码,而是把例子看几遍,记住思想,然后自己写代码实现。有的语法糖记不住的可以看看。然后例子不是说跑通就好了,多多变通下。然后进行多个维度思考。

Hello

1
2
3
4
5
6
7
8
9
10
<div id="demo">{{message}}</div>

<script>
var app = new Vue({
el: "#demo",
data: {
message: "Hello Vue"
}
});
</script>

思考:这里可以看到Html部分的”“的书写方式,这肯定是模板语法了,可以猜测到message是个变量,注意双花括号的书写方式,然后思考下假如在元素的属性上写一个变量改怎么写,比如 class = “active”, 这里active是一个变量,而如果用常规字符串符号包裹,这种书写肯定是错误的,改怎么写呢?这里得看接下来的Vue指令部分了。
然后Js部分可以看到实例化了一个Vue然后赋给app(可以试着打印app,看有哪些属性),注意Vue实例中的属性“el”“data”这些,猜测下作用。然后想想肯定还有其他更多属性的。
这里data里面的message肯定就是对应模板中的那个message了,Vue手册说这里是做响应式的数据绑定的,改变了这里的message,模板中也会相应改变,而且data中的每一个属性都会注册到上层的app变量中,也就是app.message == app.data.message,然后我们试着在console面板中改变message的值,看看变化。但是如果我们在后面在声明一个data,比如app.v = 2,这里的v就不是响应式的了,我们可以用Vue.set(app.a, 属性名,值),方式赋值,这时v就是响应式的了。

包括指令和For循环的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div class="demo">
<ul>
<li v-for="item in items" v-bind:item="item">
name:{{item.name}}, age {{item.age}}
</li>
</ul>
</div>

<script>
var app = new Vue({
el: ".demo",
data: {
items: [
{name: "Rootrl", age: 26},
{name: "Mancy", age: 1},
{name: "Katol", age: 26}
]
}
});
</script>

思考:这里首先可以看到解开了我们上个实例的疑惑了,在dom属性下的变量绑定方式是用v-bind:属性名 = “变量”这种形式,这就是所谓的Vue指令了,然后看看for的书写方式,很平常,但要记住。在手册上可以了解到遍历对象时获取值、key、索引的方式是v-for=”(value, key, index) in items”,这个以后肯定会用到。

组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<ul class="demo">
<family v-for="(item, key, index) in items" :item="item" :key="index"></family>
</ul>

<script>
// 注意:组件是Vue全局变量提供的,且需提前定义(在app实例前)
Vue.component("family", {
props: ['item'], // 传值
template: "<li>name: {{item.name}}, age: {{item.age}}</li>"
});

var app = new Vue({
el: ".demo",
data: {
items: [
{name: "Rootrl", age: 26},
{name: "Mancy", age: 1},
{name: "Katol", age: 26}
]
}
});
</script>

思考:首先看dom部分,组件相当于自定义的一个html5元素(所以注意兼容性,其实是废话==’),然后用到了我们上个实例提到的获取索引等书写方法,还有这里的key属性是必须写的,为啥?(首先我忘了写过,console会有报错)。
再看看Js部分,注意看是怎么声明一个组件的,这里用到了暴露在外面的Vue全局变量提供的component方法,注意看书写方式,这里的props作用很大,用来传值以及父子组件通信。接下来还是那个带data的常规实例化,注意组件定义得在app实例化前面(因为我开始放后面还是有报错==)

事件

1
2
3
4
5
6
7
8
9
<div class="demo" v-on:click="++counter">点击我, 次数{{counter}}</div>
<script>
var app = new Vue({
el: ".demo",
data: {
counter: 1
}
});
</script>

思考:也是用到了v指令,只用记住一个书写方式就行了,其他形式到时候手册查,还有这里后面的值可对变量用一些简单单表达式,复杂的就不行了,那么一个事件所要做的事通常有很多,该咋办呢?很简单,我们在第一个例子中说到除了el, data这些属性外,Vue中可能还有其他的,这里便用到了,叫method。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div class="demo" v-on:click="handler('hi', $event)">点我 {{message}}</div>
<script>
var app = new Vue({
el: ".demo",
data: {
message: "hello"
},
methods: {
handler: function(data, event) {
this.message = data;
alert(this.message);
}
}
});
</script>

思考:这里vue指令绑定了click事件,点击会触发handler方法,handler方法里,首先这个event是javascript的原生dom事件(方法默认接受的就是event,我这里故意放到第二个,然后注意把原生event传过来的书写方式),这里改变message是直接操作this.message,this在这里是指Vue实例。说到这里的this,官方手册在开头有说到不要在回调或属性中用箭头函数,箭头函数中父子上下文是绑定在一起的,到时候就不一定是预期的this了。

v-model 双向数据绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
<div class="demo">
{{message}} <br>
<input type="text" v-model="message">
</div>

<script>
var app = new Vue({
el: ".demo",
data: {
message: "hello"
}
});
</script>

这个例子就是Two-way-data-binding 双向数据绑定,双向数据绑定就是属性在model层变化了UI层也要变化,反过来UI层面元素的变动也要反馈到Model层。Vue底层是通过Object.defineProperty()来劫持各个属性的setter,getter来达到这个效果的(defineProperty要IE9以上才支持)。回到dom层面,这里只是input,再注意下check,select等的写法。

深入组件

组件可以提高代码复用性,Vue官网提到一点“组件也可以是原生HTML元素的形式,以is特性扩展”(什么意思?这个先记着)。

使用组件

在实例前使用Vue.component(tagName, options)注册组件。

1
2
3
 Vue.component('my-component',  {
// 选项
});

在实例的模块中可以使用形式调用。

DOM模板解析注意事项

当使用DOM作为模板时,将会受到Html的限制,因为浏览器只有在解析标准化HTML后才能获取模板内容,比如ul, ol, table,select限制了能被他包括的元素。

1
2
3
<table>
<my-row>...</my-row>
</table>

比如这里的my-row被认为是无效的,在渲染的时候会导致错误。这里就要使用开头提到的那个is了:

1
2
3
<table>
<tr is="my-row"></tr>
</table>

data 必须是函数

组件中的data必须是函数,这个要注意下。

组合组件

在Vue中,父子组件的关系可以总结为props dwon, events up,父组件通过props向下传递数据给子组件,子组件通过events给父组件发送消息。

组件实例中的作用域是孤立的,这意味着不能在子组件中直接引用父组件的数据,要让子组件使用父组件的数据,我们需要通过子组件的props选项:

1
2
3
4
5
6
7
Vue.component('child', {
props: ['message'],
template: '<span>{{message}}</span>'
})

// 使用时直接传递值
<child message="hello"></child>

自定义事件

父组件可以通过props传递数据给子组件,但是子组件怎么跟父组件通信呢?这时候Vue的自定义事件系统就可以派得上用场了。

Vue实例事件接口:

  • 使用$on(eventName) 监听事件
  • 使用$emit(eventName)出发事件

注意vue的事件系统和浏览器的eventTarge api不同。$on和$emit不是addEventListener 和 dispatchEvent的别名。

父组件可以在使用子组件的地方使用v-on来监听子组件触发的事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<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>

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

在本例中,子组件已经和他外部完全解耦了。他所做的只是报告自己的内部事件,至于父组件是否关系则与他无关。

分析:这里确是解耦了,子组件只负责做自己的事,并提供一个increment 事件给父组件。父组件监听子组件触发的increment事件,然后触发实例提供的incrementTotal方法。这里只是子组件$emit触发,父组件$on监听,其他的各做自己的事。

修饰符和原生事件

类似v-on:click.native=”doSomething”后面的这个.native就是Vue的修饰符,比如这里通过.native在某个组件上监听一个原生事件。

非父子组件通信

两个非父子组件通信可以使用一个空的Vue实例作为中央事件总线:

1
2
3
4
5
6
7
8
9
var bus = new Vue();

// 触发组件A中的事件
bus.$emit('id-selected', 1);

// 在组件B创建的钩子中监听事件
bus.$on('id-selected', function(id){
// ...
});

杂项

Vue组件的API来自三部分,Props,Events和slots

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

Slots插槽这部分没深入,具体结合业务场景使用。

子组件索引

有时候需要在Javacript中直接访问子组件时,需要使用ref为子组件指定一个索引Id:

1
2
3
4
5
6
7
8
9
<div id="parent">
<user-profile ref="profile"></user-profile>
</div>

<script>
var parent = new Vue({ el: '#parent' })
// 访问子组件
var child = parent.$refs.profile
</script>

异步组件

需与webpack结合,后面再深入

总结

这样Vue文档基础部分就走马观花过了一遍了,其中某些特性这里跳过了,具体的得结合具体使用场景再理解。
其中组件、组件间通信这个是重点,得好好理解,其次是Vue指令这些语法糖等。
总的来讲就是通过实例化Vue来声明绑定一些属性,这些属性都有各自作用,比如el绑定模板中的Vue实例作用元素,data提供数据,method提供方法等。然后暴露在全局的Vue也提供了许多方法以供使用,比如component。

接下来会深入Vue部分,比如使用构建工具webpack、路由、状态管理等等。

Share Comments

PHP OPcodes实战:PHP类名和方法同名引起的问题

简述

我刚开始是想用Php Opcodes来解决问题的,但是并没有派上用场,不过过程很详细的记录了,当是一个Opcodes使用教程吧。

缘起

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

abstract class Base
{
abstract public function child();
}

class Child extends Base
{
public function child()
{
echo "child";
}
}

$class = new Child();

这里结果直接显示 child(注意是直接打印出”child”字符,我这里并没有调用child这个方法,Why?)

我试着写两个抽象方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

abstract class Base
{
abstract public function foo();
abstract public function bar();
}

class Child extends Base
{
public function foo()
{
echo "\n foo \n";
}

public function bar()
{
echo "\n bar \n";
}
}

$class = new Child();

结果正常,什么也没显示。那为什么有一个抽象方法的时候会直接显示呢?

考虑到我这里换代码了,于是删除一个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

abstract class Base
{
abstract public function foo();
}

class Child extends Base
{
public function foo()
{
echo "\n foo \n";
}
}

$class = new Child();

结果也正常,那再回头看第一个,方法。结构没什么不同,不过方法的名称和类的名称好像同名,但是这个原因吗?

深入

于是想到php的Opcode可以查看内部执行情况

具体见鸟哥文章:http://www.laruence.com/2008/06/18/221.html

PHP官方有个叫VLD的扩展可以查看opcode

安装:

1
2
3
4
5
wget http://pecl.php.net/get/vld-0.14.0.tgz
tar zxvf vld-0.14.0.tgz
cd vld-0.14.0
phpize # 编译php扩展,根据系统信息生成对应的configure文件
./configure -with-php-config=/usr/local/php/bin/php-config --enable-vld #

配置编译vld的php-config路径

1
make && make install

在 php.ini 中激活vld

1
extension=vld.so

执行php -m | grep ‘vld’ 看是否安装成功

使用说明:

1
2
3
4
-dvld.active=1; # 使用vld扩展
-dvld.execute=0 # 不执行该文件 默认为1
-dvld.verbosity=3# 显示信息 0-3 个等级 3最高
php -dvld.active=1 -dvld.execute=0 foo.php # 查看 foo.php 的opcode

回到问题:
查看之前那个类的opcode

此处输入图片的描述

读懂cpcode前提
这其中,ASSIGN、ECHO、RETURN 这些 opcode TOKEN具体意义可参考官方文档:http://php.net/manual/en/internals2.opcodes.list.php

VLD说明:

  • Branch analysis from position 这条信息多在分析数组时使用。
  • Return found 是否返回,这个基本上有都有。
  • filename 分析的文件名
  • function name 函数名,针对每个函数VLD都会生成一段如上的独立的信息,这里显示当前函数的名称
  • number of ops 生成的操作数
  • compiled vars 编译期间的变量,这些变量是在PHP5后添加的,它是一个缓存优化。这样的变量在PHP源码中以IS_CV标记。
  • op list 生成的中间代码的变量列表

好吧,最后通过opcode没有解决这个这个问题,因为两者的opcode是一样。。

后来弄懂了,这个问题简单讲就是一个兼容问题,在老的php版本中(5以前),如果类的方法和类同名的话,这个方法相当于一个构造函数。

php官方手册中也有写:

此处输入图片的描述

Share Comments

Docker实践 - 入门篇

简介

Docker 是使用 Go 语言 进行开发实现的,基于 Linux 内核的 cgroup,namespace,以及 AUFS 类的 Union FS 等技术,对进程进行封装隔离,属于 操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。

下面的图片比较了 Docker 和传统虚拟化方式的不同之处。传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。

虚拟机

虚拟机

Docker

Docker

基本概念

Docker三大概念:镜像、容器、仓库

镜像
Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
镜像还有一个重要概念就是分层储存,镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。

容器
镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。也因为这种隔离的特性,很多人初学 Docker 时常常会把容器和虚拟机搞混。

前面讲过镜像使用的是分层存储,容器也是如此。每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为容器存储层。

容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。

按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用 数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。

数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器可以随意删除、重新 run,数据却不会丢失。

仓库
镜像构建完成后,可以很容易的在当前宿主上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。
仓库分为共有仓库和私有仓库

这些概念都是搜索来的,由于要控制时间和篇幅故没有好好整理(本篇博客的关注点也不在这),大家最好先自行搜索理解Docker的基本概念,这有利于后期实践。

安装

Docker官网提供两个版本即Docker CE(社区免费版)和 Docker EE(企业版),我们这里使用Docker CE
由于Docker CE要求Linux内核必须3.10以上,所以这里选用Centos7.2版本(7.0满足内核最低要求),不过用Centos跑Docker生产环境不推荐使用(好像是储存驱动和部分功能不稳定,具体可以自己了解)。

作为实验,我这里是用Vagrant启动一个Centos虚拟机来跑Docker

首先安装环境依赖

1
sudo yum install -y yum-utils device-mapper-persistent-data lvm2

为了加快速度,这里使用国内阿里云源

1
2
3
sudo yum-config-manager \
--add-repo \
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

安装Docker CE

1
2
sudo yum makecache fast
sudo yum install docker-ce

启动docker

1
2
sudo systemctl enable docker
sudo systemctl start docker

创建Docker用户组,并把当前用户加入管理组

1
2
sudo groupadd docker
sudo usermod -aG docker $USER

为了提高以后镜像下载速度,大家可以使用镜像加速器(这里不讲了,具体百度,很简单,就是替换docekrhub仓库地址)

Docker使用:

获取镜像

docker pull [选项] [Docker Registry地址]<仓库名>:<标签>

这里如果Registry地址不写,默认就是Dockerhub
比如我们下载一个Centos6.6的镜像:

1
docker pull centos:6.6

下载好后可以查看下载的镜像

1
docker images

运行镜像

1
docker run --name centos -it --rm centos:6.6 /bin/bash
  • -i 和 -t 是使用交互命令 还有一个用得多的就是-p 端口映射,比如把容器80端口映射到本机81端口: -p 81:80

  • –rm 是退出后删除容器

  • –name 是给容器命名

这里会启动一个bash交互窗口,操作和在Centos中一样,但这一切只是一个进程,所以启动速度非常快

我们这里是用Docker run 来运行一个镜像

Docker run时的标准操作:

  • 检查本地是否存在指定镜像, 不存在就从公有库进行下载
  • 利用镜像创建并启动一个容器
  • 分配一个文件系统,并在只读的镜像层外面挂载一层只读写层
  • 从宿主机配置的网桥接口中桥接一个虚拟接口到容器中
  • 从地址池配置一个ip地址给容器
  • 执行用户指定的应用程序
  • 执行完毕后容器被终止

操作完成后我们可以使用exit或者Ctrl+d退出容器

平时如果我们要在后台运行一个容器可以使用-d参数

1
docker run -itd centos:6.6 /bin/bash

这样容器就会在后台运行

这时候可以使用docker ps查看目前正在运行的容器,以及容器信息(包括容器ID)

如果要进入容器

1
docker attach <容器ID>

要终止一个容器可以使用

1
docker stop <容器ID>

使用docker ps -a可以查看所有容器,包括终止的

假如再次启动一个容器:

1
docker start <容器ID>

删除一个容器:

1
docker rm <容器ID>

docker rm 不会删除正在运行的容器

删除所有容器:

1
docker rm $(docker ps -a -q)

下面是我自己想的(使用awk和xargs):

1
docker rm $(docker ps -a | awk '{if (NR > 1) print $1}'|xargs)

导入和导出容器

导出

1
docker export 容器ID > ubuntu.tar

导入

1
cat unbuntu.rar | docker import - test/ubuntu:v1.0

也可以从一个网络地址导入

1
docker import http://dfsdf.taz example/imagesrepo

由于容器非常轻量,所以一般都是随时删除和创建容器的

容器的核心为所执行的应用程序,所需要的资源都是应用程序运行所必须的。除此之外,没有任何其他资源,可以在容器内用ps 或 top查看

构建镜像:

镜像一般是基于一个基础镜像构建,比如我们在centos6.6镜像上构建lanp环境
一般会用Dockerfile定制镜像

这里做个小例子,比如一般我们运行一个nginx容器,默认访问会是官方的欢迎界面,这里我们构建一个镜像,让它默认运行起来就是一个hello docker的界面

Dockerfile

mkdir创建一个测试的dir,然后进入创建一个Dockerfile文件,内容如下

1
2
3
From nginx # 基于nginx基础镜像

RUN echo "<h1>Hello, Docker!</h1>" > /usr/share/nginx/html/index.html # 重写index.html

先说说Dockerfile的格式
# 后面的为注释
\ 是换行 (写多行shell命令实用)

再来说说Dockerfile的指令,这里我们使用了两条指令

FROM
指定基础镜像,这个是必备也必须是第一条指令

RUN
运行命令,也是最常用的命令
注意:
每个RUN命令都是不同的容器中,不是同一个执行环境
每一个RUN命令都是启动一个容器,执行命令,然后提交存储层文件变更。
比如你这样操作:

1
2
RUN cd /app
RUN echo "hello" > world.txt

这里/app/world.txt会找不到文件

还有许多其他指令这里就不深入了:
copy 复制指令,注意源文件是在上下文环境下面
add 跟高级的复制,不建议使用
cmd容器启动命令
docker不是虚拟机,docker是进程,在启动的时候需要指定运行参数,
CMD [“sh”, “-c”, “service nginx start”]

ENTRYPOINT入口点
docker 命令行可以接受参数

ENV 设置环境变量

ARG构建参数

VOLUME定义匿名卷

EXPOSE声明端口

WORKRIR 指定工作目录

USER指定当前用户

HEALTHCHECK 健康检查

ONBUILD 当前镜像为基础镜像去构建下一个镜像时会被执行

构建镜像

好了下面我们可以在当前目录(Dockerfile所在目录)构建镜像

1
docker build -t nginx:v2 .  #注意此处的点为上下文目录,不能为绝对地址(具体自己可以去延伸)

然后 docker images就可以看到我们的镜像了

可以把容器跑起来看看

1
Docker run -it -p 80:80 nginx:v2

好了,由于篇幅原因,就到这里了,都是调研时事后整理的,所以比较琐碎,命令执行结果也没有截图(嫌Markdown中贴图麻烦,还要放到图床中,说到图床,以后图片我会直接贴到github了,具体操作是在Hexo Source目录下建立images目录,图片放到这目录然后提交到github,然后在网页中获取地址,这样就可以贴图了,而且图片资源可以和博客共存亡了,嘿嘿,总觉得图片放到其他地方不安全,以后某天图片会莫名访问不了)

接下来会深入调研,Docker的资源管理(因为Docker容器的储存层会和容器生命周期一起消亡,官方不提倡直接在容器中存储数据,应该跳过储存层,直接读写宿主机的存储机制,比如使用数据卷Volume实现),还有Docker的集群管理、容器监控,以及大名鼎鼎的Kubernetes

Share Comments