Webpack v4 使用 Webpack低版本编译后的文件

webpackJsonp

The JSONP function used by webpack for async loading of chunks.

Webpack 使用JSONP按需加载 chunks,这个函数的名字默认为 webpackJsonp

在之前版本的 webpack 中,webpackJsonp 是一个函数:

问题

在一个项目中如果同时需要同时运行 Webpack v4Webpack v1-3 编译后的模块,会导致命名冲突。

webpack v1

1
2
3
4
5
6
7
8
/******/(function(modules) { // webpackBootstrap
/******/ // install a JSONP callback for chunk loading
/******/ var parentJsonpFunction = window["webpackJsonp"];
/******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {
/******/ ...
/******/ };
/******/ ...
/******/ })

但在 webpack v4 中变成了一个数组

1
(window.webpackJsonp=window.webpackJsonp||[]).push([[16],{150:function(e,t,u){....

如果在同一网页上使用多个 webpack不同版本编译后的文件,就会产生冲突,导致异步 chunks 无法加载。

解决方案

正确的做法是使用 output.jsonpFunction 方法修改 JSONP 的默认名,解决冲突。

output.jsonpFunction

string

Only used when target is web, which uses JSONP for loading on-demand chunks.

A JSONP function name used to asynchronously load chunks or join multiple initial chunks (CommonsChunkPlugin, AggressiveSplittingPlugin).

This needs to be changed if multiple webpack runtimes (from different compilation) are used on the same webpage.

If using the output.library option, the library name is automatically appended.

评论和共享

Docker Compose 模板文件

简介

默认的配置文件名为 docker-compose.yml,格式为 YAML 格式。

1
2
3
4
5
6
7
version: "3"

services:
webapp:
image: examples/web
posts: "80:80"
volumes: "/data"

每个服务都必须通过 image 指定镜像或者 build 指令(需要 Dockerfile)等来自动构建生成镜像,如果使用 build 指令,在 Dockerfile 中的选项会自动获取,无需再次设置。

参数

build

指定 Dockerfile 的路径(绝对路径或相对路径)

可以使用 context 指令指定位置,dockerfile 指令指定 Dockerfile 文件名, arg 指令指定构建时的变量。

eg:

1
2
3
4
5
6
7
8
version: "3"
services:
webapp:
bulid:
context: ./div
dockerfile: Dockerfile-alternate
args:
buildno: 1

cap_add, cap_drop

指定容器的内核能力

eg: 拥有所有能力:

1
2
cap_add:
- ALL

eg: 去掉 NET_ADMIN 能力

1
2
cap_drop:
- NET_ADMIN

command

覆盖容器启动的命令

1
command: echo "hello world"

configs

cgroup_parent

指定父 cgroup 组,继承该组的资源限制。

container_name

指定容器名称,默认使用 项目名称_服务名称_序号格式。

deploy

devices

指定设备映射关系

1
2
devices:
- "/dev/ttyUSB1:/dev/ttyUSB0"

depends_on

解决容器依赖,启动先后问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: '3'

services:
web:
build:
depends_on:
- db
- redis

redis:
image: redis

db:
image: mongo

dns

自定义 DNS 服务器,可以是一个值,也可以是一个列表。

1
2
3
4
5
dns: 8.8.8.8

dns:
- 8.8.8.8
- 114.114.114.114

配置 DNS 搜索域。

1
2
3
4
5
dns_search: example.com

dns_search:
- domain1.example.com
- domain2.example.com

tmpfs

挂载一个 tmpfs 文件系统到容器。

env_file

从文件中获取环境变量

环境变量格式如下:

1
PROG_ENV=development

environment

设置环境变量,可以使用数据或者字典两种格式,只给定变量名会自动获取主机上的变量值,避免数据泄漏。

1
2
3
4
5
6
7
environment:
RACK_ENV: development
SESSION_SECRET:

environment:
- RACK_ENV=development
- SESSION_SECRET

如果变量名称用到 true | false, yes | no 等表达布尔含义的值,最好放到引号里。

expose

暴露端口,但不映射到宿主机,仅可以指定内部端口为参数

不建议使用: 链接 docker-compose 外部容器,甚至非 Compose 管理的外部容器

extra_hosts

指定额外的 host 映射

1
2
3
extra_hosts:
- "googledns:8.8.8.8"
- "dockerhub:52.1.157.61"

healthcheck

检查容器是否正常

1
2
3
4
5
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 1m30s
timeout: 10s
retries: 3

image

指定镜像名称或ID,如果本地不存在会尝试拉取这个镜像

labels

为容器添加元数据

logging

配置日志选项

1
2
3
4
logging:
driver: syslog
options:
syslog-address: "tcp://192.168.0.42:123"

network_mode

设置网络模式

networks

配置容器链接的网络

1
2
3
4
5
6
7
8
9
10
version: "3"
services:

some-service:
networks:
- some-network
- other-network
network:
some-network:
other-network:

pid

和主机进程共享命名空间

ports

暴露端口信息,宿主机端口:容器端口,或仅指定容器端口(宿主机随机分配)

secrets

储存敏感数据,例如 mysql 密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: "3"
services:

mysql:
image: mysql
secrets:
- db_root_password
- my_other_secret

secrets:
my_secret:
file: ./my_secret.txt
my_other_secret:
external: true

sysctls

配置容器内核参数

ulimits

指定容器大小限制

nproc: 进程数
nofile:文件句柄数(软限制和硬限制)

1
2
3
4
5
ulimits:
nproc: 65535
nofile:
soft: 20000
hard: 40000

volumes

数据卷所挂载路径设置

读取变量

1
2
3
4
5
version: "3"
services:

db:
image: "mongo:${MONGO_VERSION}"

MONGO_VERSION=3.2 docker-compose up, 会启动一个 mongo:3.2 镜像容器
MONGO_VERSION=2.8 docker-compose up, 会启动一个 mongo:2.8 镜像容器

评论和共享

Docker Compose 使用

定义和运行多个 Docker 容器的应用,它允许用户通过一个单独的 docker-compose.yml 模板文件(YAML格式)来定义一组相关联的应用容器为一个项目(project)。

命令

1
docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...]

构建

1
docker-compose build [options] [--build-arg key=val...] [SERVICE...]

验证配置文件

验证 Compose 文件格式是否正确,正确则显示配置文件,错误显示原因

1
docker-compose config [options]

自动构建并启动服务

1
docker-compose up [options] [--scale SERVICE=NUM...] [SERVICE...]

自动尝试完成构建镜像,创建服务,启动服务,并关联服务相关容器。

链接的服务都将会被自动启动,除非已经处于运行状态。

默认 docker-compose up 启动容器在前台,Ctrl-c 会导致所有容器停止。

如果服务已经存在,docker-compose up 将会尝试停止容器,然后重新创建(保持使用 volumes-from 挂载的卷)。

docker-compose up -d 后台启动并运行所有容器。

docker-compose up --no-recreate 启动处于停止状态的容器,忽略已经运行的。

docker-compose up --no-deps -d SERVICE 只重启部署服务

停止

停止 up 命令所启动的容器,并移除网络

1
docker-compose down [options]

进入

进入指定容器

1
docker-compose exec [options] [-e KEY=VAL...] SERVICE COMMAND [ARGS...]

列出 Compose 包含的镜像

1
docker-compose images [options] [SERVICE...]

强制停止服务容器

1
docker-compose kill [options] [SERVICE...]

查看服务器的输出

1
docker-compose logs [options] [SERVICE...]

暂停一个服务

1
docker-compose pause [SERVICE...]

恢复一个暂停的服务

1
docker-compose unpause [SERVICE...]

查看某个容器的端口

1
docker-compose port [options] SERVICE PRIVATE_PORT

列出项目中的所有容器

1
docker-compose ps [options] [SERVICE...]

拉取项目所需镜像

1
docker-compose pull [options] [SERVICE...]

推送服务依赖的镜像到 Docker 镜像仓库

1
docker-compose push [options] [SERVICE...]

重启项目中的服务

1
docker-compose restart [options] [SERVICE...]

删除所有停止状态的服务容器

1
docker-compose rm [options] [SERVICE...]

在指定服务上执行一个命令

1
docker-compose run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] [-l KEY=VALUE...] SERVICE [COMMAND] [ARGS...]

设置指定服务器运行的容器个数

1
docker-compose scale [options] [SERVICE=NUM...]

eg: 启动三个容器运行 web 服务,启动 2 个容器运行 db 服务

1
docker-compose scale web=3 db=2

当指定数目多于该服务实际运行容器,将创建新容器;反之将停止容器。

启动已经存在的服务容器

1
docker-compose start [SERVICE...]

停止已经运行的容器

1
docker-compose stop [options] [SERVICE...]

查看各个服务容器内运行的进程

1
docker-compose top [SERVICE...]

评论和共享

使用 Dockerfile 定制镜像

Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层, 因此每一条指令的内容,就是描述该层应当如何构建。

重要概念

构建上下文

docker 的构建命令:

1
docker build [OPTIONS] PATH | URL | -

最后的参数是指上下文路径,而不是 Dockerfile 所在路径,docker 在构建的时候,会将上下文路径下的所有内容打包,然后上传给 Docker 引擎,Docker 就有权限访问上下内的文件了。

如果需要访问的文件路径超出上下文的范围,Docker 引擎就无法获得这些文件的位置,应该将这些文件复制到上下文目录中。更不应该把 Dockerfile 放到硬盘根目录去构建。如果目录中有些不希望在构建中传递给 Docker 的文件,可以放在 .dockerignore 文件夹中。

指令

FROM

指定基础镜像,必须是第一条

RUN

用于执行命令行命令的,有两种格式:

  • shell 格式:RUN <COMMAND>

  • exec 格式:RUN ["<COMMAND>", "<args1>", "<args2>", ...]

每一个 RUN 命令都会创建一层镜像,如果每一个命令都使用一个 RUN 命令,那么整个项目变得臃肿,易出错。正确写法应该如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM debian:jessie
RUN buildDeps='gcc libc6-dev make' \
&& apt-get update \
&& apt-get install -y $buildDeps \
...
# 清理为了编译构建需要的软件
&& rm -rf /var/lib/apt/lists/* \
# 清理下载文件
&& rm redis.tar.gz \
# 清理展开文件
&& rm -r /usr/src/redis \
# 清理 apt 缓存
&& apt-get purge -y --auto-remove $buildDeps

每一层构建后要清理无关文件。可以使用 docker diff 命令查看文件变化。

COPY

1
2
COPY <源路径>...<目标路径>
COPY ["<源路径>",... "<目标路径>"]

源路径可以是多个,甚至通配符,通配符要满足 Go 的 filepath.Match 规则,例如:

1
2
COPY hom* /mydir/
COPY hom?.txt /mydir/

COPY 命令会保留源文件的各种源数据属性

ADD

COPY 命令的基础上添加了自动解压的功能,推荐优先使用 COPY 命令

CMD

用于指定默认的容器主进程的启动命令,运行的时候可以指定新的命令来代替镜像设置中的这个默认命令

1
2
3
4
# shell
COPY <COMMAND>
# exec
COPY ["<COMMAND>", "<args1>", "<args2>", ...]

推荐使用 exec 格式,shell格式实际的命令会被包装为 sh -c 的参数形式执行。

1
CMD echo $HOME

解释为:

1
CMD ["sh", "-c", "echo $HOME"]

ENTRYPOINT

ENV

配置环境变量:

1
2
3
4
ENV VERSION=1.0 DEBUG=on \
NAME="Happy Feet"

RUN rm "node-v$VERSION-linux-x64.tar.xz"

ARG

和 ENV 一样,都是设置环境变量,不同的是 ARG 所设置的环境变量,在将来运行时是不会存在这些环境变量的。但不要使用 ARG 保存密码,因为 docker history 可以看到所有值。

VOLUME 定义匿名卷

1
2
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>

为了保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,为了防止运行时用户忘记挂载卷,可以先用 VOLUME 命令指定某些目录为匿名卷,防止向容器存储层写大量数据。

1
VOLUME /data

运行时:

1
docker run -d -v mydata:/data xxx

EXPOSE

声明端口

1
EXPOSE <端口1> [<端口2>]

EXPOSE 仅仅声明容器打算使用的端口,并不会自动在宿主进行端口映射。

-p 是将容器的对应端口服务公开给外界访问。

WORKDIR

指定工作目录,以后各层的当前目录就被改为指定目录。

错误示例

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

新手容易的错误:因为构建分层存储,所以第一层 RUN cd /app 的执行仅仅是当前进程的工作目录变更,一个内存上的变化而已,其结果不会造成任何文件变更。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化。

USER

指定当前用户,和 WORKDIR 类似。

HEALTHCHECK

告诉 Docker 应该如何判断容器的状态是否正常,防止程序进入死循环,无法通过退出判断。

只可出现一次,多写只会生效最后一次。

ONBUILD

ONBUILD 是一个特殊的指令,后面跟其他指令,这些指令在当前镜像构建时不会执行,只有以当前镜像为基础的镜像,去构建下一级镜像的时候才会被执行。

评论和共享

Docker 基本命令

Docker 基本命令

镜像

搜索

1
docker search [OPTIONS] TERM

获取

1
docker pull [OPTIONS] NAME[:TAG|@DIGEST]

运行

1
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

常用OPTIONS:
|option|描述|
|—|—|
|-i -t|两个参数,-i 交互式操作,-t终端|
|-p|指定端口|
|-d|在后台运行容器并打印容器ID|
|--name|为容器指定名称|
|--rm|退出容器后将其删除|

挂载数据卷:

1
docker run -d -P --name web --mount source=my-vol,target=/webapp webapp python app.py

挂载主机目录:

1
docker run -d -P --name web --mount type=bind,source=/src/webapp,target=/opt/webapp,readonly webapp python app.py

列出已下载镜像

1
docker image ls [OPTIONS] [REPOSITORY[:TAG]]

删除本地镜像

1
docker image rm [OPTIONS] IMAGE [IMAGE...]

列出虚悬镜像

由于同名镜像更新产生

1
docker image ls -f dangling=true

删除虚悬镜像

1
docker image prune

占用磁盘空间

1
docker system df [OPTIONS]

查看历史

1
docker history [OPTIONS] IMAGE

推送镜像

1
docker push [OPTIONS] NAME[:TAG]

容器

启动已终止容器

1
docker container start [OPTIONS] CONTAINER [CONTAINER...]

查看日志

1
docker container logs [OPTIONS] CONTAINER

终止容器

1
docker container stop [OPTIONS] CONTAINER [CONTAINER...]

重启容器

1
docker container restart [OPTIONS] CONTAINER [CONTAINER...]

删除容器

1
docker container rm [OPTIONS] CONTAINER [CONTAINER...]

清理所有终止状态容器

1
docker container prune [OPTIONS]

进入容器

exec 命令 (推荐)

从这个 stdin 中 exit 不会导致容器停止

1
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]

常用OPTIONS:
|option|描述|
|—|—|
|-i -t|两个参数,-i 交互式操作,-t终端|
|-w|容器的工作目录|
|-e|设置 ENV|

attach 命令

注意: 如果从这个 stdin 中 exit,会导致容器停止

1
docker attach [OPTIONS] CONTAINER

查看存储层变化

1
docker diff CONTAINER

提交变更

1
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

eg:

1
docker commit --author "mail@shianqi.com" --message "Change index.html" webserver nginx:v2

常用于学习,还可以在被入侵后保存现场。不要使用 docker commit 定制镜像,应该用 Dockerfile

容器和本机文件传输

docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-

导入导出

导出容器快照

1
docker export [OPTIONS] CONTAINER

eg:

1
docker export 329a2e380 > ubuntu.tar

导入容器

docker import

导入容器快照

1
docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]

eg: 导入容器快照

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

eg: 指定url或目录导入

1
docker import http://shianqi.com/exampleimage.tgz shianqi/imagerepo

docker load

导入镜像存储文件

1
docker load [OPTIONS]

区别: 导入容器快照将丢弃所有的历史记录和元数据信息,导入镜像存储文件将保存完整记录,体积也要大。从容器快照文件中导入可以重新指定标签等信息。

数据卷

创建

1
docker volume create [OPTIONS] [VOLUME]

查看

查看所有:

1
docker volume ls [OPTIONS]

查看指定:

1
docker volume inspect [OPTIONS] VOLUME [VOLUME...]

删除

1
docker volume rm [OPTIONS] VOLUME [VOLUME...]

清理无用数据卷

1
docker volume prune [OPTIONS]

容器互联

推荐使用自定义 Docker 网络来链接多个容器,而不是使用 --link 参数

新建网络

1
docker network create [OPTIONS] NETWORK

eg:

1
docker network create -d bridge my-net

评论和共享

Docker

Docker

传统虚拟机是虚拟出一套硬件后,在其上运行一套完整的操作系统,再之上运行所需的应用进程。而容器内的应用进程直接运行于宿主机的内核,容器没有自己的内核,也没有进行硬件虚拟。因此容器比传统虚拟机更方便。

传统虚拟机:

传统虚拟机

容器:

容器

特性 容器 虚拟机
启动 秒级 分钟级
硬盘使用 一般为MB 一般为GB
性能 接近原生 弱于
系统支持量 单机支持上千个容器 一般几十个

镜像

Docker 采用分层存储的架构,构建时会一层一层构建,前一层是后一层的的基础。每一层构建完就不会发生改变,后一层上的任何改变只发生在自己这一层。在构建镜像的时候要额外小心,每一层尽量只包含需要的东西,任何额外的东西应该在该层构建结束前清理掉

容器

镜像容器的关系,就像对象,镜像是静态的定义,容器是镜像运行时的实体。

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

数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器删除或者重新运行之后,数据却不会丢失。

评论和共享

Trampoline 解决 Blowing the stack

用过 JavaScript 的人都知道,JavaScript 引擎没有对递归调用优化。因此,当运行下面的代码时:

1
2
3
4
5
6
7
8
9
const evenSteven = (n) => {
if (n>0) {
n = n - 1
return evenSteven(n)
}
return 'over';
}

console.log(evenSteven(1000000))

会出现如下错误(blowing the stack):

1
RangeError: Maximum call stack size exceeded

要解决这个问题我们可以返回一个函数,它包装调用,而不是直接调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const evenSteven = (n) => {
if (n > 0) {
n = n - 1
return () => {
return evenSteven(n)
}
}
return 'over';
}

console.log(evenSteven(0))
// over
console.log(evenSteven(1))
// [Function]
console.log(evenSteven(2))
// [Function]

这样,我们通过不断调用返回的函数就可以解决栈溢出的问题。

并且我们可以通过一个函数自动来进行 扁平化处理

1
2
3
4
5
6
7
8
9
10
11
12
const _ = require('lodash')

const trampoline = (func) => {
let res = func()
while (_.isFunction(res)) {
res = res()
}
return res
}

console.log(trampoline(evenSteven(1000000)))
// over

由于调用链的间歇性,使用蹦床增加了递归开销。然而,慢总比栈溢出好。

我们还可以进行一次包装,将蹦床隐藏在内。

1
2
3
const func = (n) => {
return trampoline(evenSteven(n))
}

评论和共享

WebGL 变量

向 shader 中传值

  • attribute: 属性,存放与顶点相关的数据,只能在顶点着色器中使用。
  • uniform: 一致变量,每次绘制像素点时都会调用且一直保持一致。传递与顶点无关的数据,在顶点着色器和片元着色器中都可以使用。
  • varying: 多变变量,从顶点着色器往片段着色器中传递的值,需要在顶点着色器和片段着色器中均设置匹配的多变变量。当 WebGL 绘制像素时,它会 栅格化 该值,然后传递到片段着色器中相对应的片段着色器。

向 attribute 中传值

1
2
var a_PointSize = gl.getAttribLocation(shaderProgram, "a_PointSize");
gl.vertexAttrib1f(a_PointSize, 10.0);

向 uniform 中传值

1
2
3
4
5
var u_Width = gl.getUniformLocation(gl.program, "u_Width");
// uniform[1234][fi][v]
gl.uniform1f(u_Width, gl.drawingBufferWidth);
// uniformMatrix[234]fv()
gl.uniformMatrix2fv(u_Width, false, [2,1, 2,2]);

使用 varying 变量再着色器间传值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var vertexShaderSrc = `
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main(){
gl_Position = a_Position;
v_Color = a_Color;
}`;

var fragmentShaderSrc = `
precision mediump float;
varying vec4 v_Color;
void main(){
gl_FragColor = v_Color;
}`;

评论和共享

WebGL 初探

WebGL 初探

基本原理

WebGL 是基于光栅化的 API ,而不是基于 3D 的 API。WebGL 程序的任务就是实现具有投影矩阵坐标和颜色的 WebGL 对象即可。

无论要实现的图形尺寸有多大,其投影矩阵的坐标的范围始终是从 -1 到 1。

GPU 有两个基础任务,第一个就是将点处理为投影矩阵。第二部分就是基于第一部分将相应的像素点描绘出来。

顶点着色器

上图左侧的是用户自己提供的数据。定点着色器就是用户在 GLSL 中写的函数。处理每个定点时,均会被调用一次。用户可以将投影矩阵的值存储在特定的变量 gl_Position 中。GPU 会处理这些值,并将他们存储在其内部。

假设用户希望绘制三角形 TRIANGLES, 那么每次绘制时,上述的第一部分就会产生三个顶点,然后 GPU 会使用他们来绘制三角形。首先 GPU 会将三个顶点对应的像素绘制出来,然后将三角形光栅化,或者说是使用像素点绘制出来。对每一个像素点,GPU 都会调用用户定义的片段着色器来确定该像素点该涂成什么颜色。当然,用户定义的片段着色器必须在 gl_FragColor 变量中设置对应的值。

WebGL 将会连接顶点着色器中的变量和片段着色器中的相同名字和类型的变量。

我们仅仅计算三个顶点。我们的顶点着色器被调用了三次,因此,仅仅计算了三个颜色。而我们的三角形可以有好多颜色,这就是为何被称为 varying

例子

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
const canvas = document.getElementById("canvas")
const gl = canvas.getContext("webgl")
if (!gl) {
console.log('浏览器不支持 WebGL')
}

// 顶点着色器
const VSHADER_SOURCE = `
attribute vec2 a_position;

void main() {
gl_Position = vec4(a_position, 0, 1);
}
`
// 片元着色器
const FSHADER_SOURCE = `
void main() {
gl_FragColor = vec4(0, 1, 0, 1); // green
}
`

// 编译着色器
const vertShader = gl.createShader(gl.VERTEX_SHADER)
gl.shaderSource(vertShader, VSHADER_SOURCE)
gl.compileShader(vertShader)

const fragShader = gl.createShader(gl.FRAGMENT_SHADER)
gl.shaderSource(fragShader, FSHADER_SOURCE)
gl.compileShader(fragShader)

// 合并程序
const shaderProgram = gl.createProgram()
this.shaderProgram = shaderProgram
gl.attachShader(shaderProgram, vertShader)
gl.attachShader(shaderProgram, fragShader)
gl.linkProgram(shaderProgram)
gl.useProgram(shaderProgram)

const positionLocation = gl.getAttribLocation(shaderProgram, "a_position")
const buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
-1.0, -1.0,
1.0, -1.0,
-1.0, 1.0,
-1.0, 1.0,
1.0, -1.0,
1.0, 1.0
]),
gl.STATIC_DRAW
)
gl.enableVertexAttribArray(positionLocation)
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0)

// draw
gl.drawArrays(gl.TRIANGLES, 0, 6)

评论和共享

2017 年度总结

2017 年度总结

此刻我正在归家的火车上,回顾我这短暂,也是最值得回味的一年。顺便写下此文,可以在未来某一天迷茫的时候能不忘初心。

虽然我努力的在脑海搜寻今年前三个月做些了什么,却没有任何收获,或许这就是时间的无情,亦或是生活的浑浑噩噩使我身心麻痹。

直到那一天接到一个电话,是阿里面试官。这是我接到的第一次电话面试,也是我回答最烂的一次(在这里非常感谢非戈师兄帮我内推)。我仍记得阿里的面试官问我的每一个问题,但印象最深的是一个看似非常简单的问题——“如何判断两个JavaScript对象相等?”。这个问题我回答的非常糟糕,甚至都不知道自己在说些什么。阿里的面试让我倍受打击,以至于一个星期我都非常迷茫。

在那之前,我一度以为自己已经很不错了,可以在大大小小的比赛上取得不错的名次,可以一个人写完看上去不错的应用。殊不知自己其实不过是苔藓地上的一株杂草,却以为可以伸手遮蔽天空。

之后一周时间我又面试了几个我觉得不错的企业,有大公司,也有创业公司,这里面包括我目前实习的心知天气,结果都很不如人意。我明白如果我只想找一个差不多的工作,糊里糊涂过完余下的时光,那么完全没问题。但是我明白,我想要的是什么,我有我的野心。

之后我就买了很多书,打算在春招前把我欠缺的基础知识打扎实了,这段时间过的很快,现在回想起来只记得那段时间把《JavaScript权威指南》这本书翻断胶了,每一页上画满笔记;左手腕因为长时间的使用键盘产生了肌肉劳损,还一度怀疑自己得了腱鞘炎,不过后来去校医那里针灸了几次,贴了几片膏药就好了,中医真神奇。我把这段时光叫做“拆掉房子盖地基”。

期间我又一次联系俨哥,想获取心知天气的一个实习的机会,但还是被拒绝了。应该还是自己的能力没有达到要求,不过这次俨哥给了我很多建议,给了我很多帮助。

之后就到了暑假,本来要去安恒信息去实习,但是后来了解到我所做的工作后,我决定放弃这次的实习机会,有没有实习经历确实很重要。但是与其把时间浪费在获取一个实习证明上,不如静下心来好好学学知识,就像之前参加过一堆没什么卵用的比赛,投入了大把的时间,却没有什么实质的收获。

一个假期后,我又一次鼓起勇气投了阿里的简历(这次要感谢钟华学长帮我内推)。前两次面试都是技术面试,聊的很轻松愉快,基本没有什么问题,但是三面的时候,是一个部门经理的面试,问了许多开放性的问题,也是我最头疼的一类问题。加上非常紧张,并且我的语言表达能力实在太差,很多东西明明自己会用,但是感觉讲不出来。最后聊到一半我的心就凉了。

之后便开学回了学校,准备春招,后来偶然又在知乎上看到了心知的招聘,所以就有了现在在心知的实习。

后面就是在北京实习的两个多月的时光,经历了和黑中介斗智斗勇,经历了清退拆隔断,学习到很多新的知识,还学会了单板滑雪。。。

感觉在北京的这几个月真的学会了很多东西,很多无法用语言描述的东西,可能真正经历一遍才能理解吧。以至于二狗子和我聊天的时候都说我变成熟了。噗,感觉自己正在变成大叔的路上一去不返。

2018计划

再学一门新的语言,我想就 Python 吧。

继续深入探索函数式编程。

维护一个自己的 github 项目,先定个小目标,比如100个 star 吧。

锻炼自己的表达能力,会用的不一定能讲明白,但能讲明白的一定会用。

继续补救自己的英语,(哭

结语

这一年要感谢很多人。感谢我爸妈;感谢二狗子每天晚上和我聊天,不然我早就坚持不下去了;感谢非戈师兄和钟华师兄帮我内推,虽然不争气的我没有抓住这两次机会,但这对我的影响却是不可代替的;感谢俨哥,肥力,大师,以及心知的每一位小伙伴,这里真的很温暖。还要感谢我的老师,同学,以及陪伴我度过这一年每一分每一秒的人,2018新年快乐!

新年快乐

评论和共享

作者的图片

Archie Shi

Nothing to say


Front-End Development Engineer