0%


title: 多人博客项目date: 2019-11-27 15:53:44

tags: 项目

blog3

说明

使用Vue-cli3搭建,高度集成webpack,不暴露webpack配置,相应插件需要自行创建vue.config.js,在其中配置.

前后端接口约定

后端接口规范

  1. 当前接口路径
  2. 当前接口提交数据类型,如:

GET: 获取数据

POST: 提交或者创建

PUT: 修改数据

DELETE: 删除数据

PATCH: 修改数据,部分修改

  1. 参数类型格式: json或者application/x-www-form-urlencoded的数据
  2. 参数字段及限制条件
  3. 返回成功的数据格式
  4. 返回失败的数据格式

下面和后端做以下接口约定,开发阶段使用postman或者curl命令测试接口

认证相关

POST /auth/register

功能: 用户注册

提交参数

参数类型: Content-type: application/x-www-form-urlencoded;chratset=utf-8

参数字段:

username: 用户名,长度1-15个字符,只能是字母数字下划线

password: 密码,长度6-16位任意字符

返回数据

失败:

返回格式: {"status": "fail", "msg": "错误原因"}

成功:

返回格式:

1
2
3
4
5
6
7
8
9
10
11
{
"status": "ok",
"msg": "注册成功",
"data": {
"id" : 1,
"username": "Tom",
"avatar": "http://avatar.com/1.png",
"createdAt": "2019-10-19T15:15:33.343Z",
"updatedAt": "2019-10-19T15:15:33.343Z"
}
}

测试命令

# -d用来传递数据

# 对于POST和PUT可以: -X POST,对于GET,不加-X

curl -d "usernme=Tom&password=123456" -X POST "http://localhost:3000/auth/regitster"

GET /auth/login

功能: 用户登录

提交参数

参数类型: Content-type: application/x-www-form-urlencoded;chratset=utf-8

参数字段:

1
2
username: 用户名,长度1-15个字符,只能是字母数字下划线
password: 密码,长度6-16位任意字符

返回数据

失败:

1
返回格式: `{"status": "fail", "msg": "用户不存在"}或者 {"status": "fail", "msg": "密码不正确"}`

成功:

返回格式:

1
2
3
4
5
6
7
8
9
10
11
{
"status": "ok",
"msg": "注册成功",
"data": {
"id" : 1,
"username": "Tom",
"avatar": "http://avatar.com/1.png",
"createdAt": "2019-10-19T15:15:33.343Z",
"updatedAt": "2019-10-19T15:15:33.343Z"
}
}

测试命令

# -d用来传递数据

# -i 可以显示响应头

# 会发现响应头里有setCookie信息,得到cookie

curl -d "usernme=Tom&password=123456" "http://localhost:3000/auth/login" -i

GET /auth

功能: 判断用户是否登录

提交参数: 无

返回数据:

已经登录的情况

返回格式:

1
2
3
4
5
6
7
8
9
10
{
"status": "ok",
"isLogin": "true",
"data": {
"id" : 1,
"username": "Tom",
"createdAt": "2019-10-19T15:15:33.343Z",
"updatedAt": "2019-10-19T15:15:33.343Z"
}
}

没有登录的情况

1
2
3
4
{
"status": "ok"
"isLogin": false
}

测试命令

#先通过登录接口获取 cookie,带上 cookie 就能测试登录

curl "http://localhost:3000/auth" -b "connect.sid=s%3AmeDbrn03UtTM8fqChaPQ20wmWlnKeHiu.e3uMtu7j1zQ1iNeaajCmxkYYGQ%2FyHV1ZsozMvZYWC6s"

GET /auth/logout

功能: 注销登录

提交参数:无

返回数据:

1
2
3
4
5
失败:
返回格式: `{ "status": "fail", "msg": "用户尚未登录"}`

成功:
返回格式: `{ "status": "success", "msg": "注销成功"}`

测试命令

curl "http://localhost:3000/auth/logout" -b "connect.sid=s%3AmeDbrn03UtTM8fqChaPQ20wmWlnKeHiu.e3uMtu7j1zQ1iNeaajCmxkYYGQ%2FyHV11ZsozMvZYWC6s"

博客相关

GET /blog

功能: 获取博客列表

提交参数:

1
2
3
page: 页码,不传默认为1.
userId: 用户ID,不传获取全部用户ID
atIndex: 是否展示在首页.true只得到展示到首页的博客列表,false得到不展示到首页的列表,不传得到全部类型的博客列表

如 /blog?page=2&userId=1 获取属于用户1的第二页博客列表

返回数据:

1
失败: { "status": "系统异常"}

成功:

返回格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"status": "ok",
"msg": "获取成功",
"total": 200,//全部博客总数
"page": 2,//当前页数
"totalPage": 10, //总页数
"data": {
"id": 1,
"title": "博客标题",
"description": "博客内容简要描述",
"user": {
"id": 100, //博客所属用户id
"username": "博客所属用户username",
"avatar": "头像"
},
"createdAt": "2019-10-19T15:15:33.343Z", //创建时间
"updatedAt": "2019-10-19T15:15:33.343Z", //更新时间
}
}

测试命令

curl "http://localhost:3000/blog?page=1&userId=1"

curl "http://localhost:3000/blog?page=1"

curl "http://localhost:3000/blog"

GET /blog/:blogId

功能: 获取id为blogId的博客详情,如 /blog/1

提交参数: 无

返回数据:

失败: { "status": "fail", "msg": "系统异常"}

成功:

返回格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"status": "ok",
"msg": "获取成功",
"data": {
"id": 1,
"title": "博客标题",
"description": "博客内容简要描述",
"content": "博客内容",
"user": {
"id": 100, //博客所属用户id
"username": "博客所属用户username",
"avatar": "头像"
},
"createdAt": "2019-10-19T15:15:33.343Z", //创建时间
"updatedAt": "2019-10-19T15:15:33.343Z", //更新时间
}
}

POST /blog

功能: 创建博客

提交参数:

1
2
3
4
5
参数类型: Content-type: application/x-www-form-urlencoded; charset=utf-8;
参数字段:
title : 博客标题, 博客标题不能为空,且不超过100个字符
content : 博客内容, 博客内容不能为空,且不超过10000个字符
description: 博客内容简要描述,可为空,如果为空则后台自动从content 中提取

返回数据:

1
失败: { "status": "fail", "msg": "登录后才能操作"}

成功:

返回格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"status": "ok",
"msg": 创建成功",
"data": {
"id": 1,
"title": "博客标题",
"description": "博客内容简要描述",
"content": "博客内容",
"user": {
"id": 100, //博客所属用户id
"username": "博客所属用户username",
"avatar": "头像"
},
"createdAt": "2019-10-19T15:15:33.343Z", //创建时间
"updatedAt": "2019-10-19T15:15:33.343Z", //更新时间
}
}

测试命令

curl -d "title=hello&content=world&description=jirengu" -X POST "http://localhost:3000/blog" -b "connect.sid=s%3AdyZh-z5fqPU_ThG9Qn8nGD6euI0UI75e.8uso0k4P6WzqWv02iQCUwxbUML2RdlOCnpKp7RSJpj0"

PATCH /blog/:blogid

功能: 修改博客id为:blogid的博客

范例: /blog/1

提交参数

参数类型: Content-Type: application/x-www-form-urlencoded; charset=utf-8

参数字段:

1
2
3
4
title: 博客标题
content : 博客内容, 博客内容不能为空,且不超过10000个字符
description: 博客内容简要描述,可为空,如果为空则后台自动从content 中提取
atIndex: true/false, 展示到首页/从首页异常, 可选

返回数据

失败

返回格式:

1
2
3
{"status": "fail", "msg": "登录后才能操作"}
{"status": "fail", "msg": "博客不存在"}
{"status": "fail", "msg": "无法修改别人的博客"}

成功

返回格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"status": "ok",
"msg": 修改成功",
"data": {
"id": 1,
"title": "博客标题",
"description": "博客内容简要描述",
"content": "博客内容",
"user": {
"id": 100, //博客所属用户id
"username": "博客所属用户username",
"avatar": "头像"
},
"createdAt": "2019-10-19T15:15:33.343Z", //创建时间
"updatedAt": "2019-10-19T15:15:33.343Z", //更新时间
}
}

测试命令

curl -d "title=hello100&content=world1&description=jirengu2234444444&atIndex=true" -X PATCH "http://localhost:3000/blog/12" -b "connect.sid=s%3At_9V2bMXA7U9oSAmr1dhRXpdRPAsNM2B.jlpWgkwiWdpgTjexeTHGNydt8gvc%2F%2BEkJpQ9yaAmTg0"

DELETE /bog/:blogid

功能: 删除id为:blogid的博客

提交参数:无

返回数据

失败

返回格式范例

1
2
3
{"status": "fail", "msg": "登录后才能操作"}
{"status": "fail", "msg": "博客不存在"}
{"status": "fail", "msg": "无法删除别人的博客"}

成功

返回格式

1
2
3
4
{
"status": "ok",
"msg": "删除成功"
}

测试命令

curl -X DELETE "http://localhost:3000/blog/12" -b "connect.sid=s%3AG_Chytg2F0RLWh2wTSCdLWVxpNg1MWWb.nPuMcgyMN6zxuxjSkyu8qSqM1boruol1Nce7egaXrPw"

文件介绍

main.js

项目入口

1
2
3
4
5
6
7
import Vue,app,router from ...
new Vue({
el:"#app",
router,
component: { App },
template: '<App />
})

App.vue

当前整个项目的模板

tempalte,js,style

component/xxx.vue

各个组件,也可以同名的文件夹,包含vue,css,js

router.js

路由组件

请求接口封装

helpers/request.js

请求组件

import axios

//约定请求文件格式

axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'

//接口路径

axios.defaults.baseURL = 'http://blog-server.hunger-valley.com'

//前后端分离,即使异步请求也带上cookie

axios.defaults.withCredentials = true

//函数导出

export default function request(){ ... }

api接口封装

@/api/auth.js

//引入请求接口

import request from '@/helpers/request'

把各个接口进行封装,便于后续调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const URL = {
REGISTER: '/auth/register',
LOGIN: '/auth/login',
LOGOUT: '/auth/logout',
GET_INFO: '/auth'
}

export default {
register({username, password}){
return request(URL.REGISTER, 'POST', { username, password })
},
login({ username, password}){
return request(URL.LOGIN, 'POST', { username, password })
},
logout(){
return request(URL.LOGOUT)
},
getInfo(){
return request(URL.GET_INFO)
}
}

封装blog接口

同样引入请求接口

import request from '@/helpers/request'

封装blog的各个接口

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
const URL = {
GET_LIST: '/blog',
GET_DETAIL: '/blog/:blogId',
CREATE: '/blog',
UPDATE: '/blog/:blogId',
DELETE: '/blog/:blogId'
}
export default {
getBlogs({ page=1, userId, atIndex } = { page:1 }){
return request(URL.GET_LIST, 'GET', { page, userId, atIndex })
},
getIndexBlogs({ page=1 } = { page: 1 }){
return this.getBlogs({ userId, page, atIndex })
}
getDetail({ blogId }){
return reuqest(URL.GET_DETAIL, replace(':blogId', blogId))
},
updateBlog({ blogId }, { title, content, description, atIndex }){
return request(URL.UPDATE, replace(':blogId', blogId), 'PATCH', { title, content, description, atIndex })
},
deleteBlog({ blogId }){
return request(URL.DELETE, replace(':blogId',blogId), 'DELETE')
},
createBlog({ title = '', content = '', description = '',atIndex = false } = { title = '', content = ''}){
return request(URL.CREATE, 'POST', { title, content, description })
}
}

首页布局

状态管理

store.js

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
59
// @/assets/moudles/auth.js
const state = {
user: null,
isLogin: false,
};

const getters = {
user: (state) => state.user,
isLogin: (state) => state.isLogin,
};

const mutations = {
setUser(state, payload) {
state.user = payloag.user;
},
setLogin(state, payload) {
state.isLogin = payload.isLogin;
},
};

const actions = {
async login({ commit }, { username, password }) {
let res = await auth.login({ username, password });
commit("setUser", { user: res.data });
commit("setLogin", { isLogin: true });
return res.data;
},
async register({ commit }, { username, password }) {
let res = await auth.register({ username, password });
commit("setUser", { user: res.data });
commit("setLogin", { isLogin: true });
return res.data;
},
async logout({ commit }) {
await auth.logout();
commit("setUser", { user: null });
commit("setLogin", { isLogin: false });
},
async checkLogin({ commit, state }) {
//判断state中是否有登录状态,有返回true
if (state.isLogin) return true;
//如果state中没有,调用res
let res = await auth.getInfo();
//从res中获取isLogin状态设置Login
commit("setLogin", { isLogin: res.isLogin });
//如果res中未登录,返回false
if (!res.isLogin) return false;
//如果res中已登录,设置user并返回true
commit("setUser", { user: res.data });
return true;
},
};

export default {
state,
getters,
mutations,
actions,
};

在 header.vue 中调用 vuex 中的参数

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
//header.vue
import { mapState, mapActions } from 'vuex'

export default {
data(){
return {}
},
computed(){
...mapGetters([
'isLogin',
'user'
])
},
created(){
//在生命周期created调用methods的方法
this.checkLogin()
},

methods: {
//将state中的方法映射进来
...mapActions({
'checkLogin'
})
}
}

登录和注册

1
2
3
4
5
6
7
8
9
10
11
// Login/template.vue
<template>
<div id="login">
<h4>用户名</h4>
<input v-model="username" placeholder="用户名">
<h4>密码</h4>
<input v-model="password" type="password" placeholder="密码" @key.enter="onLogin">
<el-button size="medium" @click="onLogin">立即登录</el-button>
<p class="notice">没有账号?<router-link to="/register">注册新用户</router-link></p>
</div>
</template>
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
//Login/template.js
import { mapActions } from 'vuex'

export default {
data(){
return {
username: '',
password: ''
}
},
methods: {
//把login从vuex中拿出来
...mapActions(['login'])

onLogin(){
//test
console.log(this.username + this.password)
//执行登录,把用户名密码传递
this.login({username: this.username, password: this.password})
//调用then下一步操作,跳转到首页或者定向页面
.then(() => {
this.$router.push({path: this.$route.query.redirect || '/'})
})
}
}
}

注册

注册类似于登录

将 login 换成 register 即可.

路由组件 router.js

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
59
60
61
62
63
64
65
66
67
68
69
70
71
import Vue from "vue";
import Router from "vue-router";

//引入store为了checkin
import store from "./src/store.js";

Vue.use(Router);

//先声明,最后再导出
const router = new Router({
routes: [
{
path: "/",
//使用匿名函数import方式实现异步懒加载
//当需要跳转该模块时才会引入相关路径
component: () => import("@/pages/Index/template.vue"),
},
{
path: "/login",
component: () => import("@/pages/Login/template.vue"),
},
{
path: "/detail/:blogId",
component: () => import("@/pages/Detail/template.vue"),
},
{
path: "/edit/:blogId",
component: () => import("@/pages/Edit/template.vue"),
meta: { requiresAuth: true },
},
{
path: "/create",
component: () => import("@/pages/Create/template.vue"),
meta: { requiresAuth: true },
},
{
path: "/user/:userId",
component: () => import("@/pages/User/template.vue"),
},
{
path: "/my",
component: () => import("@/pages/My/template.vue"),
meta: { requiresAuth: true },
},
{
path: "/register",
component: () => import("@/pages/Register/template.vue"),
},
],
});

router.beforeEach((to, from, next) => {
//判断是否有元信息meta
if (to.matched.some((record) => record.meta.requiresAuth)) {
//如果有就触发checkLogin检查登录状态
store.dispatch("checkLogin").then((isLogin) => {
if (!isLogin) {
next({
path: "/login",
query: { redirect: to.fullPath },
});
} else {
next();
}
});
} else {
next();
}
});

export default router;

创建博客页面

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
<template>
<div id="edit">
<!-- 创建博客 -->
<h1>创建文章</h1>
<h3>文章标题</h3>
<el-input v-model="title"></el-input>
<p class="msg">限30个字</p>
<h3>内容简介</h3>
<el-input type="textarea" v-model="description" :autosize="{ minRow: 2, maxRows: 4 }"></el-input>
<p class="msg">限30个字</p>
<h3>文章内容</h3>
<el-input type="textarea" v-model="content" :autosize="{ minRow: 4, maxRows: 30 }"></el-input>
<p class="msg">限30个字</p>
<p>
<label>是否展示到首页</label>
<el-switch v-model="atIndex" active-color="#13ce66" inactive-color="#fff">
</el-switch>
</p>
<el-button @click="onCreate">确定</el-button>
</div>
</template>

<script>
import blog from '@/api/blog'

export default {
data(){
return {
title: '',
description: '',
content: '',
atIndex: false
}
},
methods: {
onCreate(){
blog.createBlog({ title: this.title, content: this.content, description: this.description, atIndex: this.atIndex})
.then(res => {
this.$message.success(res.msg)
this.$router.push({ path: `/detail/${res.data.id}`})
})
}
}
}

</script>

完善首页

详情页

时间插件

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
function friendlyDate(datsStr) {
let dateObj = typeof datsStr === "object" ? datsStr : new Date(datsStr);
let time = dateObj.getTime();
let now = Date.now();
let space = now - time;
let str = "";

switch (true) {
case space < 60000:
str = "刚刚";
break;
case space < 1000 * 3600:
str = Math.floor(space / 60000) + "分钟前";
break;
case space < 1000 * 3600 * 24:
str = Math.floor(space / (1000 * 3600)) + "小时前";
break;
default:
str = Math.floor(space / (1000 * 3600 * 24)) + "天前";
}
return str;
}

export default {
install(Vue, options) {
Vue.prototype.friendlyDate = friendlyDate;
},
};

个人页面 user

有点东西

登陆者的个人页面 my

遇到过的问题

  1. vuex 中 actions 错误(原因: modules 拼错了)
  2. 登录时 enter 无法使用.(解决方法: 加.native)
  3. 文章内容应有一定区域(原因: 没写 palceholder)
  4. 编辑时少一个设置为首页的按钮(原因:按钮初始颜色透明)
  5. my 页面无法显示(原因: 因为错误 6)
  6. 过滤器 getMouth 错误,(原因: mouth 单词拼错)
  7. my 页面编辑删除两个按钮靠的太近
  8. header 布局错误(原因: h1 标签没有把 router-link 包裹,如果包裹,less 有设置 h1 下 a 标签的颜色为白色.

没有包裹的时候,a 标签的颜色是 common.less 设置的黑色,会覆盖)

标题颜色错误,把标题文本和普通文本颜色算在一起了.


title: Express

date: 2019-09-06 14:30:24

tags: 后端

categories:  # 这里写的分类会自动汇集到 categories 页面上,分类可以多级

  • 后端 # 一级分类
  • Node.js # 二级分类

介绍

Express 是基于 Node.js 平台的 web 开发框架.

快速创建

使用以下命令安装 express

1
2
//express脚手架
npm install express-generator -g

使用-h选项显示命令选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
express -h
Usage: express [options][dir]
//用法

Options:

-h, --help output usage information
--version output the version number
-e, --ejs add ejs engine support
--hbs add handlebars engine support
--pug add pug engine support
-H, --hogan add hogan.js engine support
--no-view generate without view engine
-v, --view <engine> add view <engine> support (ejs|hbs|hjs|jade|pug|twig|vash) (defaults to jade)
-c, --css <engine> add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css)
--git add .gitignore
-f, --force force on non-empty directory

例如,以下语句在当前工作目录中创建名为 myapp 的 Express 应用程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ express --view=pug myapp

create : myapp
create : myapp/package.json
create : myapp/app.js
create : myapp/public
create : myapp/public/javascripts
create : myapp/public/images
create : myapp/routes
create : myapp/routes/index.js
create : myapp/routes/users.js
create : myapp/public/stylesheets
create : myapp/public/stylesheets/style.css
create : myapp/views
create : myapp/views/index.pug
create : myapp/views/layout.pug
create : myapp/views/error.pug
create : myapp/bin
create : myapp/bin/www

然后安装依赖项:

1
2
cd myapp
npm install

在 Mac 或 Linux 下采用以下命令运行此程序

1
DEBUG=myapp:* npm start

在 windows 上

1
set DEBUG=myapp:* & npm start

然后在浏览器输入`http://localhost:3000 访问

基本路由

中间件: 处理请求的函数.

模板引擎: <%= 写入的变量名 %>,其中变量名可以在其他地方赋值,这样就可以批量改变变量了.

后台

基本页面: 创建,列表,展示

静态文件

提供图像,css 和 js 之类的静态文件,请使用 express.static 内置中间件函数.

使用以下代码在名为 public 的目录中提供图像,css 和 js 之类的静态文件

1
app.use(express.static("public"));

现在,可以访问位于 public 目录中的文件:

1
2
3
4
5
http://localhost:3000/images/kitten.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/app.js
http://localhost:3000/images/bg.png
http://localhost:3000/hello.html

要使用多个静态资源目录,请多次调用 express.static 中间件函数:

1
2
app.use(express.static("public"));
app.use(express.static("files"));

Express 以您使用 express.static 中间件函数设置静态目录的顺序来查找文件。

要为 express.static 函数提供的文件创建虚拟路径前缀(路径并不实际存在于文件系统中),请为静态目录指定安装路径,如下所示:

1
app.use("/static", express.static("public"));

向 express.static 函数提供的路径相对于您在其中启动 node 进程的目录。如果从另一个目录运行 Express 应用程序,那么对于提供资源的目录使用绝对路径会更安全:

1
app.use("/static", express.static(__dirname + "/public"));

路由

基本路由示例:

1
2
3
4
5
6
var express = require("express");
var app = express(); //在`myapp/bin/www`里创建了app的服务器

app.get("/", function (req, res) {
red.send("hello world"); //给浏览器发一个hello world
});

路由方法

GET 和 POST 的示例

1
2
3
4
5
6
7
8
9
//GET
app.get("/", function (req, res) {
res.send("GET request to the homepage");
});

//POST
app.post("/", function (req, res) {
res.send("POST request to the homepage");
});

有一种特殊路由方法:app.all(),它并非派生自 HTTP 方法。该方法用于在所有请求方法的路径中装入中间件函数。

在以下示例中,无论您使用 GET、POST、PUT、DELETE 还是在 http 模块中支持的其他任何 HTTP 请求方法,都将为针对“/secret”的请求执行处理程序。

1
2
3
4
app.all("/secret", function (req, res, next) {
console.log("Accessing the secret section ...");
next(); // pass control to the next handler
});

路由路径

‘/‘表示根路径

1
2
3
app.get("/", function (req, res) {
res.send("root");
});

和’/about’路径匹配

1
2
3
app.get("/about", function (req, res) {
res.send("random.txt");
});

基于字符串模式的匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//此路由路径将匹配 abcd、abbcd、abbbcd 等。

app.get("/ab+cd", function (req, res) {
res.send("ab+cd");
});
//此路由路径将匹配 abcd、abbcd、abbbcd 等。

app.get("/ab+cd", function (req, res) {
res.send("ab+cd");
});

//此路由路径将匹配 abcd、abxcd、abRABDOMcd、ab123cd 等。

app.get("/ab*cd", function (req, res) {
res.send("ab*cd");
});

//此路由路径将匹配 /abe 和 /abcde。

app.get("/ab(cd)?e", function (req, res) {
res.send("ab(cd)?e");
});

字符 ?、+、* 和 () 是其正则表达式同应项的子集。基于字符串的路径按字面理解连字符 (-) 和点 (.)。

路由处理程序

可以提供多个回调函数,以类似于中间件的行为方式来处理请求。

唯一例外是这些回调函数可能调用 next(‘route’) 来绕过剩余的路由回调。

可以使用此机制对路由施加先决条件,在没有理由继续执行当前路由的情况下,可将控制权传递给后续路由。

路由处理程序的形式可以是一个函数、一组函数或者两者的结合,如以下示例中所示。

单个回调函数可以处理一个路由。例如:

1
2
3
app.get("/example/a", function (req, res) {
res.send("Hello from A!");
});

多个回调函数可以处理一个路由(确保您指定 next 对象)。例如:

1
2
3
4
5
6
7
8
9
10
app.get(
"/example/b",
function (req, res, next) {
console.log("the response will be sent by the next function ...");
next();
},
function (req, res) {
res.send("Hello from B!");
}
);

一组回调函数可以处理一个路由。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var cb0 = function (req, res, next) {
console.log("CB0");
next();
};

var cb1 = function (req, res, next) {
console.log("CB1");
next();
};

var cb2 = function (req, res) {
res.send("Hello from C!");
};

app.get("/example/c", [cb0, cb1, cb2]);

独立函数与一组函数的组合可以处理一个路由。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var cb0 = function (req, res, next) {
console.log("CB0");
next();
};

var cb1 = function (req, res, next) {
console.log("CB1");
next();
};

app.get(
"/example/d",
[cb0, cb1],
function (req, res, next) {
console.log("the response will be sent by the next function ...");
next();
},
function (req, res) {
res.send("Hello from D!");
}
);

响应方法

响应对象 (res) 的方法可以向客户端发送响应,并终止请求/响应循环。如果没有从路由处理程序调用其中任何方法,客户端请求将保持挂起状态。

方法 描述
res.download() 提示将要下载文件。
res.end() 结束响应进程。
res.json() 发送 JSON 响应。
res.jsonp() 在 JSONP 的支持下发送 JSON 响应。
res.redirect() 重定向请求。
res.render() 呈现视图模板。
res.send() 发送各种类型的响应。
res.sendFile 以八位元流形式发送文件。
res.sendStatus() 设置响应状态码并以响应主体形式发送其字符串表示。

app.route()

可以使用app.route()为路由路径创建可连接的路由处理程序

因为在单一位置指定路径,可以减少冗杂和输入错误.

1
2
3
4
5
6
7
8
9
10
11
app
.route("/book")
.get(function (req, res) {
res.send("get a book");
})
.post(function (req, res) {
res.send("add a book");
})
.put(function (req, res) {
res.send("update a book");
});

express.Router

使用 express.Router 类来创建可安装的模块化路由处理程序。Router 实例是完整的中间件和路由系统;因此,常常将其称为“微型应用程序”。

以下示例将路由器创建为模块,在其中装入中间件,定义一些路由,然后安装在主应用程序的路径中。

在应用程序目录中创建名为 birds.js 的路由器文件,其中包含以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var express = require("express");
var router = express.Router();

// middleware that is specific to this router
//use是不管get还是post都可以匹配
router.use(function timeLog(req, res, next) {
console.log("Time: ", Date.now());
next();
});
// define the home page route
router.get("/", function (req, res) {
res.send("Birds home page");
});
// define the about route
router.get("/about", function (req, res) {
res.send("About birds");
});

module.exports = router;

接着,在应用程序中装入路由器模块:

1
2
3
var birds = require('./birds');
...
app.use('/birds', birds);

此应用程序现在可处理针对 /birds 和 /birds/about 的请求,调用特定于此路由的 timeLog 中间件函数。


title: Node.js

date: 2019-09-01 14:27:58

tags: 后端

categories:  # 这里写的分类会自动汇集到 categories 页面上,分类可以多级

  • 后端 # 一级分类
  • Node.js # 二级分类

node.js

基础

简易版 node-server

1
2
3
4
5
6
7
8
9
10
11
12
13
//index.js文件中引入http模块
var http = require("http"); //引入http模块
var server = http.createServer(function (request, response) {
response.setHeader("Content-Type", "text/plain; chartset=gbk"); //内容渲染形式:字符串.解码方式gbk
//设置响应头
response.writeHead(200, "ok"); //设置状态码
response.write("hello");
response.end(); //结束
}); //创建服务器

server.listen(9000); //启动服务器,监听9000端口

console.log("open http://localhost:9000");

在终端中输入node index.js就启动服务器.

简单版服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var http = require("http");
//http模块
var fs = require("fs");
//fs模块读写文件

//创建服务器
var server = http.createServer(function (req, res) {
console.log(__dirname + "/static" + req.url);
//fs同步读取,路径由文件绝对路径+static+请求url拼接.使用二进制格式
var fileContent = fs.readFileSync(__dirname + "/static" + req.url, "binary");
res.write(fileContent, "binary");
res.end();
});

//监听8080端口
server.listen(8080);
console.log("visit localhost:8080");

进阶

静态服务器.

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
var http = require("http") //引入http模块
var path = require("path") //自动处理路径问题
var fs = require("fs") //读写文件
var url = require("url") //解析url

//
function staticRoot(staticPath, req, res){
var pathObj = url.parse(req.url, true)
//url.parse是node.js方法,将一个URL字符串转换成对象并返回
console.log(pathObj)

if(pathObj.pathname === "/"){
pathObj.pathname += "index.html"
} //不加后缀可以默认加后缀

//请求文件的具体路径
var filePath = path.join(staticPath, pathObj.pathname)
//拼接文件地址

//同步读取文件
// var fileContent = fs.readFlieSync(filePath,'binary')
// res.write(fileContent, 'binary')
// res.end()

//异步读取文件
fs.readFile(filePath, "binary", function(err, fileContent){
//'binary'表示二进制格式
if(err){
res.writeHead(404, "not found")
res.end("<h1>404 Not Found</h1>")
}else{
res.writeHead(200, "ok")
res.write(fileContent, "binary")
res.end()
}
})

console.log("path.join(_dirname, "static")

var server = http.createServer(function(res,req){
staticRoot(path.join(__dirname, "static"), req, res)
//__dirname代表当前文件路径,join拼接上static路径
})

server.listen(8080)
console.log("visit http://localhost:8080")

mock 数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//服务器配置
var http = require("http");
var fs = require("fs");
var url = require("url");

http
.createServer(function (req, res) {
//解析请求url
var pathObj = url.parse(req.url, true);

switch (pathObj.pathname) {
case "/getWeather":
res.end(JSON.stringify({ a: 1, b: 2 }));
break;
case "user/123":
res.end(fs.readFileSync(__dirname + "/static/user.tpl"));
break;
default:
res.end(fs.readFileSync(__dirname + "static" + req.url));
}
})
.listen(8080);

问题

  1. __dirname是什么

__dirname总是指向被执行文件的绝对路径.

/d1/d2/my.js中的__dirname,他的值就是/d1/d2.

常用 API

fs.readFile(path[,options],callback)/fs.writeFile(file,data[,options],callback)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let fs = require("fs"); //引入fs模块

fs.readFile("flie.txt", "utf-8", function (err, str) {
if (err) {
console.log("error");
} else {
console.log(str);
//要去掉字符串里的数字空格
const strAfter = str.replace(/\d/gm, "");
console.log(strAfter);

//去掉数字后把strAfter写出来
fs.writeFile("fileAfter.txt", strAfter, (err) => {
if (err) throw err; //如果报错直接使用throw抛出
console.log("the file has been save!");
});
}
});

模块

将上面的去数字的方法封装成函数,单独写一个文件,并调用

创建stringApi.js

1
2
3
4
5
6
function replaceDigit(str) {
return str.replace(/\d/gm, "");
}

module.exports.replaceDigit = replaceDigit;
//把这个封装好的函数暴露出来可以使用了
1
2
3
4
5
6
//封装好后在上面的代码里就可以使用了
//先引入
let strApi = require("./stringApi"); //如果在同级目录下,直接使用相对路径引入

//然后将上面的代码const strAfter = str.replace(/\d/gm, '')替换成
const strAfter = strApi.replaceDigit(str);

NPM

如果需要一些功能,node.js 自带的没有,可以去 npm 官网上找一些其他人发布的 npm 包.

找到之后,首先进行安装

package.json

记录保存模块依赖,当没有依赖时,比如下载其他人的项目里没有依赖,可以使用npm.init初始化,将 package.json 中记录的模块依赖全都下载下来,方便.

npm 切换源工具

1
2
3
4
npm install -g nrm //安装nrm工具
nrm ls //查看nrm支持的源的列表
nrm use taobao //使用淘宝的源
nrm use npm //切换回npm源

NPM Script

在 package.json 文件里有一个 scripts 对象,写入里面的命令可以比较方便快捷的执行.直接npm xxx就可以执行.而且会自动搜索路径,不用写相对路径.

1
2
3
4
5
6
7
8
9
10
11
12
{
"scripts": {
"css:autoprefixer": "postcss -u autoprefixer -r dist/css/*",
"css:compress": "csso in.css --output out.css",
"js:lint": "eslint src/js",
"js:uglify": "mkdir -p dist/js && uglifyjs src/js/*.js -m -o dist/js/app.js",
"image:imagemin": "app=imagemin-cli npm run check; imagemin src/images dist/images -p",
"server": "browser-sync start --server --files 'dist/css/*.css, dist/js/*.js'",
"watch": "onchange 'src/js/*.js' -- npm run css:compress",
"start": "npm run server"
}
}
1
2
npm run css:autoprefixer
npm start


title: Vue.js进阶

date: 2019-09-13 15:47:32

tags:  # 这里写的分类会自动汇集到 categories 页面上,分类可以多级

  • Vue.js # 一级分类
  • Vue.js进阶 # 二级分类

关于h

h 作为 createElement 的别名是 Vue 生态系统中的一个通用惯例.

响应式原理

官网解释

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

官方解释总结:

  1. 任何一个 Vue 组件都有一个与之对应的Watcher实例。
  2. Vue 的data上的属性会被添加gettersetter属性。
  3. 当 Vue 组件的render函数被执行的时候,data上会被触碰(touch),即被读,getter方法会被调用,此时 Vue 会去记录此 Vue 组件所依赖的所有data。(这一过程被称为依赖收集)
  4. data被改动时(主要是用户操作),即被写,setter方法会被调用,此时 Vue 会去通知所有依赖于此data的组件去调用他们的render函数进行更新。

其他说法:

mvvm 用来初始化数据

observer用来对初始数据通过Object.defineProperty添加settergetter,当取数据(即调用 get)的时候添加订阅对象(watcher)到数组里, 当给数据赋值(即调用 set)的时候就能知道数据的变化,此时调用发布订阅中心的notify,从而遍历当前这个数据的订阅数组,执行里面所有的watcher,通知变化update

compiler 是用来把 data 编译到 dom 中。分三步:1.先把真实的 dom 移入到内存中fragment,2.编译:提取想要的元素节点v-model和文本节点;3.把编译好的 fragment 塞回到页面去。第二步骤中会对编译到 dom 中的data添加watcher,当data变化时,这里的watcher回调也能收到通知得到执行。

watcherobervercompiler之间通信的桥梁。

Object.defineProperty 的缺陷

  1. 无法监听数组变化。
  2. 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,如果属性值也是对象那么需要深度遍历,显然能劫持一个完整的对象是更好的选择。

使用 Proxy 实现 Vue 数据劫持

proxy 定义: Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等).

Proxy 第一个参数是目标对象,第二个参数是一个对象,其属性是当执行一个操作时定义代理的行为的函数。这时可以在第二个参数中加入一个 set 方法,这时可以监听到是哪个 key 做了改变。并且通过 Reflect 的 set 方法去模拟真实的 set 方法。

为什么说 Proxy 的性能比 Object.defineProperty 更好呢?

Object.defineProperty只能监听属性,而 Proxy 能监听整个对象,省去对非对象或数组类型的劫持,也能做到监听。

vue 是对对象每一个属性进行Object.defineProperty

第二点,Object.defineProperty不能监测到数组变化

总结

Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改;

Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利

当然,Proxy 的劣势就是兼容性问题,而且无法用polyfill实现

Proxy 基本语法

const obj = new Proxy(target, handler);

参数说明如下:

target: 被代理对象。

handler: 是一个对象,声明了代理 target 的一些操作。

obj: 是被代理完成之后返回的对象。

但是当外界每次对 obj 进行操作时,就会执行 handler 对象上的一些方法。handler 中常用的对象方法如下:

1
2
3
4
5
1. get(target, propKey, receiver)
2. set(target, propKey, value, receiver)
3. has(target, propKey)
4. construct(target, args):
5. apply(target, object, args)

常用(考)组件之keep-alive

作用: 缓存组件内部状态,避免重新渲染

注意: 和<transition>相似,<keep-alive>是一个抽象组件:自身不会渲染一个 DOM 元素,也不会出现在父组件链中.

用法:

缓存动态组件:

<keep-alive>包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们,此种方式并无太大的实用意义。

1
2
3
4
5
6
7
8
9
10
<!-- 基本 -->
<keep-alive>
<component :is="view"></component>
</keep-alive>

<!-- 多个条件判断的子组件 -->
<keep-alive>
<comp-a v-if="a > 1"></comp-a>
<comp-b v-else></comp-b>
</keep-alive>

使用keep-alive可以将所有路径匹配到的路由组件都缓存起来,包括路由组件里面的组件,keep-alive大多数使用场景就是这种。

1
2
3
<keep-alive>
<router-view></router-view>
</keep-alive>

常用(考)API 之nextTick

作用: $nextTick是将回调推迟到下次 DOM 更新循环之后再执行,在修改数据之后使用$nextTick,则可以在回调中获取更新后的 DOM,

Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。

1
2
3
4
5
6
7
8
9
10
11
// 修改数据
vm.msg = "Hello";
// DOM 还没有更新
Vue.nextTick(function () {
// DOM 更新了
});

// 作为一个 Promise 使用 (2.1.0 起新增,详见接下来的提示)
Vue.nextTick().then(function () {
// DOM 更新了
});

为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。在组件内使用vm.$nextTick()实例方法特别方便,因为它不需要全局 Vue,并且回调函数中的 this 将自动绑定到当前的 Vue 实例上.因为 $nextTick()返回一个 Promise 对象,所以可以使用 ES6 语法.

常用(考)API 之 set

返回: 设置的值.

用法: 向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性 .

注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。

常用(考)API 之 watch

一个对象,是需要观察的表达式对应回调函数。值也可以是方法名,或者包含选项的对象。

Vue 实例将会在实例化时调用 $watch(),遍历 watch 对象的每一个属性。

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
<div id="app">
<input type="text" v-model:value="childrens.name" />
<input type="text" v-model:value="lastName" />
</div>

<script type="text/javascript">
var vm = new Vue( {
el: '#app',
data: {
children: {
name: '小强',
age: 20,
sex: '男'
},
tdArray:["1","2"],
lastName:"张三"
},
watch:{
children:{
//如果childrens发生变化,函数就会执行
handler:function(val,oldval){
console.log(val.name)
},
deep:true//对象内部的属性监听,也叫深度监听
},
'children.name':function(val,oldval){
console.log(val+"aaa")
},//键路径必须加上引号
lastName:{function(val,oldval){
console.log(this.lastName)
},
immediate:true //立即以'childern.name'触发回调
}
},//以V-model绑定数据时使用的数据变化监测
} );
vm.$watch("lastName",function(val,oldval){
console.log(val)
})//主动调用$watch方法来进行数据监测
</script>
</body>

取消观察

vm.$watch 返回一个取消观察函数,用来停止触发回调:

1
2
3
var unwatch = vm.$watch("a", cb);
// 之后取消观察
unwatch();

注意,不应该使用箭头函数来定义watcher函数 (例如searchQuery: newValue => this.updateAutocomplete(newValue))。理由是箭头函数绑定了父级作用域的上下文,所以this将不会按照期望指向 Vue 实例,this.updateAutocomplete将是undefined

其他 API

Vue.extend(options)

用法: 使用基础 Vue 构造器创建一个子类.参数是一个包含组件选项的对象.

data 在Vue.extend中是函数

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建构造器
var Profile = Vue.extend({
template: "<p>{{firstName}} {{lastName}} aka {{alias}}</p>",
data: function () {
return {
firstName: "Walter",
lastName: "White",
alias: "Heisenberg",
};
},
});
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount("#mount-point");

Vue.set(target,propertyName/index,value)

返回值: 设置的值

用法: 向响应式添加新属性,并确保这个新属性同样是响应式的.且触发识图更新.它必须用于响应式对象上添加新属性,因为 Vue 无法探测新增的属性.

如果我们在创建实例以后,再在实例上绑定新属性,vue 是无法进行双向绑定的。

注意: 对象不能是 Vue 实例,或者 Vue 实例的根数据对象.

Vue.mixin

用法: 全局注册一个混入,影响注册之后所有创建的每个 Vue 实例.插件作者可以使用混入,向组件注入自定义行为.不推荐在应用代码中使用.

1
2
3
4
5
6
Vue.mixin({
beforeCreate() {
//..逻辑
//这种方法会影响到每个组件的beforeCreate钩子函数
},
});

虽然文档不建议我们在应用中直接使用 mixin ,但是如果不滥用的话也是很有帮助的,比如可以全局混入封装好的 ajax 或者一些工具函数等等。

mixins应该是我们最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过mixins混入代码,比如上拉下拉加载数据这种逻辑等等。

另外需要注意的是mixins混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并.

Vue 页面优化(spa 首屏单页面)

  1. 压缩代码
  2. 框架和插件按需加载
  3. 框架和插件从 CDN 中引入
  4. 路由懒加载
  5. SSR 服务端渲染

函数化组件

先设置functional: true,表示该组件无状态无实例,不能使用this

使用上下文context进行替换

替换规律:

1
2
this.text-----context.props.text
this.$slots.default------context.children
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
<div id="app">
<my-component value="haha">
</my-component>
</div>
<script>
Vue.component('my-component',{
functional: true, //开启函数化,无实例,无状态.this失效
render: function(createElement,context){
//添加context参数
return createElement('button',{
on: {
click: function(){
console.log(context)
console.log(context.parent) //父组件
console.log(context.parent.msg)
console.log(context.props.value)
}
}
},'点击我') //第三个参数
},
props:['value']
)

var app = new Vue({
el: "#app",
data: {
msg: "我是父组件内容"
}
})
</script>

虚拟 dom

虚拟 DOM 到底是什么,说简单点,就是一个普通的 JavaScript 对象,包含了 tagpropschildren 三个属性。

虚拟 dom 优点

用 JS 对象模拟 DOM 节点的好处是,页面的更新可以先全部反映在 JS 对象(虚拟 DOM )上,操作内存中的 JS 对象的速度显然要更快,等更新完成后,再将最终的 JS 对象映射成真实的 DOM,交由浏览器去绘制。

1
2
3
<div id="app">
<p class="text">hello world!!!</p>
</div>

上面的 HTML 转换为虚拟 DOM 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
tag: 'div',
props: {
id: 'app'
},
chidren: [
{
tag: 'p',
props: {
className: 'text'
},
chidren: [
'hello world!!!'
]
}
]
}

该对象就是我们常说的虚拟 DOM 了,因为 DOM 是树形结构,所以使用 JavaScript 对象就能很简单的表示。而原生 DOM 因为浏览器厂商需要实现众多的规范(各种 HTML5 属性、DOM 事件),即使创建一个空的 div 也要付出昂贵的代价。虚拟 DOM 提升性能的点在于 DOM 发生变化的时候,通过 diff 算法比对 JavaScript 原生对象,计算出需要变更的 DOM,然后只对变化的 DOM 进行操作,而不是更新整个视图.

diff 算法

diff 算法用来比较两棵 Virtual DOM 树的差异,如果需要两棵树的完全比较,那么 diff 算法的时间复杂度为 O(n^3)。但是在前端当中,你很少会跨越层级地移动 DOM 元素,所以 Virtual DOM 只会对同一个层级的元素进行对比,如下图所示, div 只会和同一层级的 div 对比,第二层级的只会跟第二层级对比,这样算法复杂度就可以达到 O(n)。

Vue 中的虚拟 dom

Vue 通过建立一个虚拟 DOM 来追踪自己要如何改变真实 DOM。请仔细看这行代码:

1
return createElement("h1", this.blogTitle);

createElement 到底会返回什么呢?其实不是一个实际的 DOM 元素。它更准确的名字可能是 createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为“虚拟节点 (virtual node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。

render 函数(渲染函数)

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
<template id="hdom">
//template下只允许有一个子节点,所以用div包裹三个标题
<div>
<h1 v-if="level==1">
<slot></slot>
</h1>
<h2 v-if="level==2">
<slot></slot>
</h2>
<h3 v-if="level==3">
<slot></slot>
</h3>
</div>
</template>

<script>

Vue.component('child', {
//使用template命名的方法将大段代码写在html里
props: ['level'],
template: '#hdom'
})

Vue.component('child', {
//使用render函数替代template,节省大量代码
render: function (createElement) {
return createElement('h' + this.level, this.$slots.default);
},
props: ['level']
})
</script>

render 函数的第一个参数

在 render 函数的方法中,参数必须是createElement,它的类型是function

createElement的第一个参数必选.类型可以是String|Object|Function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Vue.component("child", {
render: function (createElement) {
return createELement("h1");
//参数是字符串.返回<h1></h1>
return createElement({
template: `<div>鹅鹅鹅</div>`,
});
//参数是对象.返回<div>鹅鹅鹅</div>
var domFun = function () {
return {
template: `<div>鹅鹅鹅</div>`,
};
};
return createELement(domFun());
//参数是函数.返回<div>鹅鹅鹅</div>
},
});

render 函数的第二个参数

createElement的第二个参数可选.参数是数据对象,只能是Object

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
Vue.component('child',{
render: function(createElement){
return CreateElement({
template: `<div>鹅鹅鹅</div>`
},{
//添加class选项,其中为true的会添加到模板<div>中
'class':{
foo: true
baz: false
},
//添加style属性
style: {
color: 'red',
fontSize: '18px'
},
//正常的html属性
attrs: {
id: 'foo',
src: 'xxxx'
},
//原生DOM属性
domProps: {
innerHTML: '<span style="color:blue">我是蓝色</span>'
}
}

)
}
})

render 函数的第三个参数

createElement的第三个参数可选.参数可以是String|Array,代表子节点

1
2
3
4
5
6
7
8
Vue.component("child", {
render: function (createElement) {
return createElement("div", [
createElement("h1", "我是h1标题"),
createElement("h6", "我是h6标题"),
]);
},
});

在 render 函数中使用this.$slots

第三个参数存的是 VNode,也就是虚拟节点.组件树中的所有 VNode 必须是唯一的.

1
2
3
4
5
6
7
8
9
10
11
12
13
Vue.component("my-component", {
render: function (createElement) {
var header = this.$slots.header;
//返回的就是含有VNode的数组
var main = this.$slots.default;
var footer = this.$slots.footer;
return createElement("div", [
createElement("header", header),
createElement("main", main),
createElement("footer", footer),
]);
},
});

在 render 函数中使用 props 传数据

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
<div id="app">
<button @click="switchShow">点击切换</button>
<my-component :show="show"></my-component>
</div>

<script>
Vue.component('my-component',{
props: ['show'],
render: function(createElement){
var imgsrc
if(this.show){
imgsrc = 'img/001.jpg'
}else{
imgsrc = 'img/002.jpg'
}
return createElement('img',{
attrs: {
scr: imgsrc
}
})
}
})

var app = new Vue({
el: "#app",
data: {
show: true
},
methods: {
switchShow: function(){
this.show = !this.show
}
}
})
</script>

在 render 函数中使用v-model

v-model作用: 接收input的内容并绑定到后面的值上.

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
<div id="app">
<my-component v-bind:name="name" v-model="name"></my-component>
{{ name }}
</div>
<script>
Vue.component('my-component',{
render: function(createElement){
props:['name'],
var self = this //指的是当前的Vue实例
return createElement('input',{
domProps: {
//原生DOM
value: self.name
},
on: {
//这里添加事件
input: function(event){
//此处的this是window,所以需要声明self指代vue实例
self.$emit('input',event.target.value)
}
}
})
}
})

var app = new Vue({
el: "#app",
data: {
name: "Tom"
}
})
</script>

render 函数中使用作用域插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div id="app">
<my-component>
<template scoped="prop">
{{ prop.text }}
{{ prop.msg }}
</template>
</my-component>
</div>
<script>
Vue.component('my-component',{
render: function(createElement){
return createElement('div',this.$scopedSlots.default({
text: '我是子组件传递的数据',
msg: 'scopetext'
}))
})

var app = new Vue({
el: "#app",
data: {

}
})
</script>


title: Axios

date: 2019-09-24 11:33:48

tags:  # 这里写的分类会自动汇集到 categories 页面上,分类可以多级

  • HTML # 一级分类
  • HTTP进阶 # 二级分类

Axios

axios 是一个基于 Promise 用于浏览器和 nodejs 的 HTTP 客户端,它本身具有以下特征:

  • 从浏览器中创建 XMLHttpRequest
  • 从 node.js 发出 http 请求
  • 支持  Promise API 拦截请求和响应
  • 转换请求和响应数据
  • 取消请求
  • 自动转换 JSON 数据
  • 客户端支持防止  CSRF/XSRF

步骤:

  1. 安装

$ npm install axios

或者 CDN 引入 2. 引入 3. 一般是把 axios 挂载到原型上,便于全局使用

1
Vue.prototype.$http = axios;

Axios 之 get 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
methods: {
getData(){
this.$http.get('https://cnode.js.org/api/v1/topic',{
params: {
page: 1,
limit: 10
}
})
.then(res=>{
this.items = res.data.data
})
.catch(err=>{
console.log(err)
})
}
}
1
2
3
4
5
6
7
8
9
10
11
12
//上面get请求的其他写法
axios.get("/user", {
params: {
ID: 12345,
},
});
//如果只有一个选项
axios.get("/user", {
ID: 12345,
});
//将数据写到url里
axios.get("/user?page=1&limit=10");

Axios 之 post 请求

1
2
3
4
5
6
7
8
9
10
11
axios
.post("/user", {
firstName: "tom",
lastName: "green",
})
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});

POST 传递参数有两种格式:

  • form­-data  格式: ?page=1&limit=48.但是不能写在 url 里
  • x-­www-­form-­urlencoded 格式: { page: 1,limit: 10 }

在 axios 中,post 请求接收的参数必须是 form-­data

可以使用 qs 插件—­qs.stringify

执行多个并发请求

1
2
3
4
5
6
7
8
9
10
11
12
13
function getUserAccount() {
return axios.get("/user/12345");
}

function getUserPermissions() {
return axios.get("/user/12345/permissions");
}

axios.all([getUserAccount(), getUserPermissions()]).then(
axios.spread(function (acct, perms) {
// 两个请求现在都执行完成
})
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
postData(){
//先要安装qs插件,然后引入
//使用qs插件将对象格式转化为form-data格式
this.$http.post(url,qs.stringify({
page: 1,
limit: 10
}))
.then(res=>{
console.log(res)
})
.catch(err=>{
console.log(err)
})
}

拦截器 interceptors

在请求或响应被 then 或 catch 处理前拦截它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 添加请求拦截器
axios.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么
return config;
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}
);

// 添加响应拦截器
axios.interceptors.response.use(
function (response) {
// 对响应数据做点什么
return response;
},
function (error) {
// 对响应错误做点什么
return Promise.reject(error);
}
);

如果你想在稍后移除拦截器,可以这样:

1
2
3
4
var myInterceptor = axios.interceptors.request.use(function () {
/*...*/
});
axios.interceptors.request.eject(myInterceptor);

可以为自定义 axios 实例添加拦截器

1
2
3
4
var instance = axios.create();
instance.interceptors.request.use(function () {
/*...*/
});


title: 其他人面试题 date: 2019-11-16 18:40:25

tags: 面试题

第一份

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Person() {}
Person.prototype.name = "nick";
Person.prototype.age = 20;
Person.prototype.job = "Engineer";
Person.prototype.sayName = function () {
console.log(this.name);
};

var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
console.log(person1.name);
console.log(person2.name);
console.log(person1.hasOwnProperty("name"));

输出

1
2
3
Greg;
nick; //实例并没有改变对象的属性
true;
  1. 请列举 js 的数据类型
  2. 请写出 dom 事件流的三个阶段
  3. js 如何添加,移除,创建,复制,查找 dom 节点
  4. jQuery 的 bind,live,delegate 的区别
  5. 请写出至少 4 种 vue 的指令和用法
  6. 什么是闭包,什么时候使用闭包
  7. 如何编写响应式网页
  8. 简单描述一下微信小程序的文件类型
  9. 如何优化代码
  10. 请列举 ES6 的语法

第二份

  1. this 原型链 new 结合的题,问输出什么
  2. css 有什么选择器,优先级,什么属性,哪些可以继承,哪些不能继承,css3 新增的伪类有哪些
  3. vue 组件通信
  4. ajax 返回三个状态码,成功失败未知,假如是未知就轮询,1 到 5 秒延迟 3 秒发请求,6 到 10 秒延迟 1 秒,10 到 20 延迟 0.5 秒,轮询 20 次失败,手写可运行的代码
  5. 数组{城市,省份,代号}输出成{省份,城市[城市名,代号]}
  6. js 有什么 bug 让你写代码出错,答类型判断.追问,怎么通过原型链判断一个对象是 json,
  7. 平时写代码有什么逻辑上的错误
  8. 当前端有什么技巧?(不是优化).答:console 二分法定位错误

第三份

  1. 将下列数据封装成 json

    数据名 类型
    name kate string
    age 10 int
    phone 15455234376 string
    phone 15455234376 string
    date_time 2019-01-01 15:00:00 string
  2. 将如下 json 字符串转化成 js 对象并获取到 name 的值

1
var json_str = { name: "tiger" };

输出

1
2
3
var json_str = { name: "tiger" };
var str = JSON.parse("json_str");
console.log(str.name);
  1. 获取如下变量中的月和日
1
var date_time = "2019-01-01 15:00:00";

输出

1
2
new Date(date_time).getMonth() + 1;
new Date(date_time).getDate();
  1. 写出尽可能多的 dom 事件和 js 事件
  2. 写出尽可能多的伪类和伪元素
  3. css3 2D 转换,如右图效果(效果是第二个 div 方块向上位移一半,能够覆盖一点第一个 div,然后顺时针旋转 30 度),代码如下,请填写 div2 的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<head>
<style>
div {
width: 100px;
height: 75px;
background-color: lightgray;
border: 1px solid black;
}
div#div2{
/*请写代码 */

}
</style>
</head>
<body>
<div>1号div元素</div>
<div id="div2">2号div元素</div>
</body>
</html>

输出

1
2
3
4
5
div#div2{
transform: translateY(-20%);
transform: rotate(30deg);

}
  1. css 动画使用哪个属性,过渡使用哪个属性
  2. 写一种灰色的 rgb 值
  3. 尽可能多的 vue 的生命周期,并标注运行时间
  4. 控制元素是否显示的 vue 属性
  5. 下面代码的打印结果. 简述什么是变量的作用域,全局变量和局部变量的区别
1
2
3
4
5
6
7
8
9
10
var a = 10;
function test() {
a = 100;
console.log(a);
console.log(this.a);
var a;
console.log(a);
}
test();
console.log(a);

输出

1
2
3
4
100; //声明前置,函数内部a=100
10; //this指向window,a=10
100; //继续输出函数内部的a
10; //函数内部的a只在函数内有效,在外部看全局的a,a=10
  1. 简述 cookie 和 session 的区别
  2. 简述 axios 的作用和优势.

第四份

  1. v-show 和 v-if 的区别
  2. let 和 const 的区别
  3. fetch 和 axios 支持跨域请求吗?fetch 第一次得到什么数据,怎么进行转换?
  4. 什么是 MVC 和 MVVM,vue 属于那一种?小程序是什么思想实现双向数据绑定的?
  5. async 和 await 实现同步的好处
  6. React/Vue 中 key 会影响 diff 算法吗
  7. Vue-router 的路由原理
  8. 什么是虚拟 dom

第五份

  1. 使用 css 让该节点不可见,方法越多越好
1
<div class="hidden">Hi</div>

输出

  1. 在哪些场景下用过 position 的哪些值,他们分别有什么特性.
  2. 什么是单进程?什么是单进程异步?
  3. 输出结果
1
2
3
4
5
6
7
8
9
10
var fn = [];
for (var i = 0; i < 10; i++) {
fn[i] = function (param) {
console.log(i + param);
};
}

fn[5](5);
var data = { a: 10, b: 20 };
console.log("第" + i + "条数据:" + data);

输出

1
2
15; //闭包,打印最后是10个闭包,且i=10,传入[5],打出来i也是10
("第10条数据:[object Object]"); //字符串+对象就会把对象打印成[object Object].这是+运算符的特点
  1. 输出结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);

console.log(3);

new Promise((resolve, reject) => {
console.log(4);
resolve();
}).then(
() => {
console.log(5);
},
() => {
console.log(6);
}
);
console.log(7);

输出

1
2
3
4
5
6
7
8
//考察微任务和宏任务
1;
3;
4; //此时并未执行异步,仍属于宏任务中script下同步代码
7; //此时同步代码执行完毕,执行栈为空,
5; //开始执行微任务,promise属于微任务
//不输出6是因为那是失败时的回调
2; //开始下一轮event loop,执行宏任务中的异步代码.setTimeout开始执行.
  1. 输出结果
1
2
3
4
5
6
7
8
9
10
const a = [1, 1, 1, 1, 1];
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 5; j++) {
a[i] = a[i] + a[j];
}
}

for (let i = 0; i < 5; i++) {
console.log(a[i]);
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//双重遍历
//可以假设外层的不变
//当i=0时,内层开始遍历,得到
a[0] = a[0] + a[0]; //2
a[0] = a[0] + a[1]; //3
a[0] = a[0] + a[2]; //4
a[0] = a[0] + a[3]; //5
a[0] = a[0] + a[4]; //6,
//此时a[0] = 6
//当i=1时,内层再次遍历,得到
a[1] = a[1] + a[0]; //7
a[1] = a[1] + a[1]; //14
a[1] = a[1] + a[2]; //15
a[1] = a[1] + a[3]; //16
a[1] = a[1] + a[4]; //17,
//此时a[1] = 17

//以此类推,得到
a = [6, 17, 50, 149, 446];
  1. 写出 1 所在的 while 循环的作用.补全 2 的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//isSymmetry用来判断正整数n是否是一个对称数.例如:12321是对称数,123不是.
function isSymmetry(n) {
let i = n;
let j = 0;
//位置1
while (i) {
j = j * 10 + (i % 10);
i = parseInt(i / 10);
}
return; //位置2补充代码
}

console.log(isSymmetry(12321)); //true
console.log(isSymmetry(12312)); //false

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
假设: n = 11,
i = 11,j = 0;
i = 1,j = 1;
i = 0 ,j = 11

n = 111,
i = 111,j = 0;
i = 11,j = 1;
i = 1,j = 11;
i = 0,j = 111.

n = 12,
i = 12,j = 0;
i = 1,j = 2;
i = 0,j = 21.

//结论
return n == j
while的作用: 循环判断是否将输入值的高低位置颠倒.
  1. 把数组倒序输出(不是排序,不能用内置方法)
1
const arr = ["name", "first", "5", 7, 4, "2", 9];

输出

1
2
3
4
5
for (let key in arr) {
setTimeout(function () {
console.log(arr[key]);
}, 100 - key);
}
  1. (过一面必做题)数组中的重复项最多出现 N 次.(时间复杂度越低得分越高)
1
2
3
4
//原题
outputNth([1, 1, 1, 1], 2); //return [1,1]
outputNth([20, 37, 20, 20, 21], 2); //return [20,20,37,21]
//预估时间复杂度
1
2
3
4
//附加题
//如果要求
outputNth([20, 37, 20, 20, 21], 2); //return [20,37,20,21] 按原数组的先后返回
//预估时间复杂度

输出

第六份

  1. 假设有两个数组 a 和 b,数组的内容都是从小到大排好序的数字.现在我们合并这两个数字.并且合并之后的数字也是从小到大排好序的.

请写下你的实现.不要直接使用 js 的 sort 方法,并且确保只使用一次 for 循环.

例如,a=[2,5,10],b=[4,7,9,11],那么合并的结果是 c=[2,4,5,7,9,10,11]

提示,你可以在一个 for 循环中同时控制两个数组下标 i 和 j.比较 a[i]和 a[j].将小的数字加到 c 中,同时更新相应的下标.但是注意各种边界条件.

输出

1
2
3
//考察排序算法
var arr = a.concat(b); //arr=[2,5,10,4,7,9,11]
var c = [];
  1. 输入一个数字组成的字符串,请把它转化成整数并输出.

例如,输入字符串’123’,输出整数 123.不要使用库函数,函数原型为: function strToInt(str)

输出

1
2
3
var str = "123";
console.log(+str);
console.log(parseInt(str));
  1. 合并数组中相邻且重复的元素

说明: 请实现一个函数 merge,传入一个数组,合并数组中相邻且重复的元素,示例:

1
2
3
merge([3, 2, 2, 4, 5, 5, 6, 2, 1]); //[3,2,4,5,6,2,1]
merge([3, 2, 3]); //[3,2,3]
merge([2, 2, 3]); //[2,3]

输出

  1. 请按顺序取出下方 log
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
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}

async function async2() {
console.log("async2");
}

console.log("script start");

setTimeout(function () {
console.log("setTimeout");
}, 0);

async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});

console.log("script end");

输出

1
2
3
4
5
6
7
8
script start
async1 start //async 表达式定义的函数是立即执行的
async2
promise1
script end
async1 end //
promise2
setTimeout

第七份

  1. 对 MVC,MVVM 的理解.
  2. vue 对于 jquery 在开发上有什么优点
  3. 前后端分离的思想了解吗
  4. 你上一个项目都用了哪些方法优化 js 的性能
  5. 说一下你对 vue 和 veux 的使用方法,vue 的组件复用机制
  6. js 怎样实现一个类,怎么实例化这个类

第八份

  1. 下面的函数执行后会输出什么
1
2
3
4
5
6
7
8
9
10
11
12
13
const a = () => {
const b = new Promise((resolve, reject) => {
resolve("hello world");
reject("error");
});
for (let i = 0; i < 10; ++i) {
b.then((data) => {
console.log(data + i);
});
}
};

a(); //输出什么

输出

1
2
3
4
5
6
7
8
9
10
hello world0
hello world1
hello world2
hello world3
hello world4
hello world5
hello world6
hello world7
hello world8
hello world9
  1. 下面的函数执行后会输出什么
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var a = async function () {
var b = function () {
return new Promise(function (resolve, reject) {
resolve("hello");
reject("error");
})
.then(function (data) {
console.log(data + i);
})
.catch(function (err) {
console.log(err);
});
};
for (var i = 0; i < 10; ++i) {
await b();
}
};

a(); //输出什么

输出

1
2
3
4
5
6
7
8
9
10
hello0;
hello1;
hello2;
hello3;
hello4;
hello5;
hello6;
hello7;
hello8;
hello9;

第九份(半截)

  1. 什么是延迟加载?

  2. 请谈谈 vue 中的 mvvm 模式

  3. vue 中引入组件的步骤

  4. 什么是 vuex?为什么要用 vuex?

第十份

  1. 请说明值类型和引用类型的区别?那些是值类型,哪些是引用类型?
  2. 请简述 private,protected,public,internal 修饰符的访问权限.
  3. 简述 session 和 cookie 的作用和区别
  4. 请简述 ajax 工作原理和常用的 ajax 框架
  5. css 规范中, .后面跟一个名称代表什么?#代表什么?
  6. 字符串反转,给定字符串"we;tonight;you;",编码实现输出"ew;thginot;uoy;"

第十一份

1
2
3
4
5
6
7
8
9
10
var name = "The Window";
var obj = {
name: "My Obj",
getNameFunc: function () {
return function () {
return this.name;
};
},
};
console.log(obj.getNameFunc()()); //输出什么

输出

1
2
obj.getNameFunc()的this是obj
obj.getNameFunc()()的thiswindow
1
2
3
4
5
6
7
8
9
10
11
var name = "The Window";
var obj = {
name: "My Obj",
getNameFunc: function () {
var that = this;
return function () {
return that.name;
};
},
};
console.log(obj.getNameFunc()()); //输出什么

输出

1
My Obj

第十二份

  1. vue-router 原理
  2. 相关钩子函数
  3. nexttick
  4. css3 常用属性
  5. keep-alive
  6. 屏幕滚动
  7. 箭头函数什么时候就已经绑定 this
  8. 数组方法,map 和 forEach 的区别
  9. route 和 router 的区别
  10. 中间件原理

第十三份

  1. 虚拟 dom
  2. 生命周期,常用的那些,在这些阶段一般都做了什么
  3. Vuex 是干什么的?有哪些属性?怎么操作 state 中的属性
  4. vue-router 有哪些方法?怎么传递参数,获取参数,路由导航钩子有哪几种,全局钩子的移动端适配(媒体查询,flex,viewport,rem)
  5. 常用的 ES6 内容.const a ={b:1},改变 b 的值会不会报错.模板字符串,解构赋值,拓展运算符及使用场景.
  6. element-UI 常用的插件,之前项目是怎么搭建的


title: 上海公司面试题 date: 2019-11-16 16:28:19

tags: 面试题

云印(以 vue 为主)

1.页面初始化一般放在哪个生命周期里,为什么?

2.vue 组件之间如何传递参数

3.同步组件和异步组件之间的区别,优缺点

4.vue-router 实现原理

5.微信小程序的跳转方式有哪几种

  1. ajax 的同步和异步有什么区别

7.css 那些属性是默认继承的?

  1. canvas 和 style 怎么进行绑定?

  2. v-for 中 in 和 of 的区别

  3. watch 如何使用

  4. 如何存储 localstorage

  5. router 如何传值,如何取值

  6. vuex 如何存储值

  7. 分页组件的实现

  8. 如何监听物理返回键

报关(以 http 为主)

  1. http 请求原理
  2. http 请求的几种方式,有什么区别
  3. 如何进行登录验证,token 失效如何处理
  4. 如何调换数组中两个元素的位置.例如:[1,2,3,4,5]中调换 3 和 4 的位置,变成[1,2,4,3,5].
  5. 向后端发送文件的格式?

外包公司 1

  1. js 中如何检测一个变量是 String 类型.请写出函数具体实现
  2. 写出下面函数输出什么(tips:闭包)
1
2
3
4
5
6
7
8
9
10
11
var count = 10;
function add() {
var count = 0;
return function () {
count += 1;
console.log(count);
};
}
var s = add();
s();
s();

输出

1
2
3
//考察闭包
1;
2;
  1. 如何阻止事件冒泡和默认事件
  2. 下面的问题打印什么
1
2
3
4
5
6
7
8
var str = "我是MT";
test();
function test() {
console.log(str);
var str = "哈哈哈";
console.log(str);
}
console.log(str);

输出

1
2
3
4
5
//考察变量声明和作用域
undefined; //因为打印str时,首先从str所在的作用域找变量str,
//因为有声明前置,var str,但是未赋值,是undefined.所以不再从外部找str
哈哈哈;
我是MT;
  1. 什么是盒子模型
  2. sessionStorage,localStorage 和 cookie 的区别
  3. px 和 em 的区别
  4. 列举不同的清除浮动的技巧
  5. display: nonevisibility: hidden的区别
  6. 组件中<style scoped>scoped的作用.
  7. v-show 和 v-if 的共同点和区别
  8. vue 注册组件使用什么关键字
  9. vue 如何实现父子组件通信
  10. vue 的双向数据绑定原理是什么?

外包公司 2

  1. 使用 js 将数组: [{ name: "king", age: 18}, {name: "bob", age: 22}]变成[18,22],并且筛选出大于 20 的值.
  2. 使用 js 获取页面一个 id 为 myP 的<p>标签,并为其增加点击事件,并说明一下 addEventLister 方法的第三个可选参数的冒泡和捕获.
  3. 控制台运行一下代码:
1
2
3
4
5
6
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 0);
}
console.log(i);

输出什么

1
2
3
4
5
6
3 //先执行for循环,i=1,有异步代码,封存数据,console.log(i).
//i=2,console.log(i)
//i=3,console.log(i)
//此时i的最终形态是3,执行最后一行的console.log(i),是3

33 //同步代码执行完,看任务队列中排着3个console.log(i),i=3,连续输出3个3

大钱

  1. 写一个原生 ajax 请求
  2. 写几个常用的数组,字符串,对象的方法
  3. npm install --savenpm install --save --dev的区别
  4. IE 与火狐的事件机制有什么区别?如何阻止冒泡?
  5. 什么是闭包? 闭包的用途
  6. css3 动画最后一帧如何恢复到原样?
  7. sessionStorage,localStorage 和 cookie 的区别?分别在什么时候用?
  8. HTML5 新特性?移除那些?如何处理 HTML5 新标签的浏览器兼容问题?做过的项目中,那些 CSS 样式需要单独写兼容样式?
  9. ES6 运算符…是用来做什么的?用 ES6 语法遍历一个数组.
  10. px,em,rem 的区别
  11. prototype 是什么?什么时候用
  12. js 添加,移除,替换,删除,创建,查找节点的方法是什么
  13. 如何优化网站性能(js)
  14. 编写一个方法,去掉以下数组的重复元素
  15. vue 如何监听一个对象的变化,写个例子
  16. setTimeout,promise,async/await 的区别
  17. intercepter 用在什么地方
  18. 路由拦截在什么地方
  19. 切换用户如何处理
  20. 用户不是提交验证进来而是输入 url 进来如何处理
  21. 组件里 name 的作用

外包公司 3

  1. js 有哪些基本数据类型
  2. var let const 的区别
  3. 数字计算: 在 js 中,0.1+0.2 的结果是?
  4. 下面代码有什么问题?(tips:闭包)
1
2
3
4
5
6
7
8
9
10
11
12
13
<ul>
<li>test1</li>
<li>test2</li>
<li>test3</li>
</ul>
<script>
var lis = document.querySelectorAll('ul li');
for(var i = 0; i <lis.length; i++){
lis[i].addEventListener('click', function(){
console.log(i)
},false)
}
</script>

无论点击哪个都会输出 3

解决方法:

1.将 var 换成 let

  1. for 循环改造
1
2
3
4
5
6
7
8
9
10
11
for (var i = 0; i < lis.length; i++) {
(function (i) {
lis[i].addEventListener(
"click",
function () {
console.log(i);
},
false
);
})(i);
}
  1. 请写出下面输出结果,
1
2
3
let obj = {},arr = [];
({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true }));
console.log(obj, arr);

输出

1
2
3
4
{
prop: 123;
}
[true];
  1. promise 有几种状态,什么时候会进入 catch?
  2. 在 8*8 的国际象棋摆放八个皇后,使其不同相互攻击,即任意两个皇后不能处在同一行,同一列,同一对角线.总共有多少种摆法.
  3. git 命令有哪些?

外包公司 4 才匠

  1. 下面代码的输出是什么?
1
2
3
4
5
6
7
8
9
const shape = {
radius: 10,
diameter() {
return this.radius * 2;
},
perimeter: () => 2 * Math.PI * this.radius,
};
shape.diameter();
shape.perimeter();

输出

1
2
20;
NaN; //考察箭头函数的this
1
2
3
4
5
let c = { greeting: "hey" };
let d;
d = c;
c.greeting = "hello";
console.log(d.greeting);

输出

1
hello; //考察引用类型的指针,或者说堆内存
1
2
3
4
5
6
7
8
9
10
11
class Chameleon {
static colorChange(newColor) {
this.newColor = newColor;
}
constructor({ newColor = "green" } = {}) {
this.newColor = newColor;
}
}

const freddie = new Chameleon({ newColor: "purple" });
freddie.colorChange("orange");

输出

1
2
3
//考察class中static的方法
"freddie.colorChange is not a function";
//static不能在类的实例上调用
1
2
3
4
5
6
7
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
const member = new Person("Lydia", "Hallie");
Person.getFullName = () => this.firstName + this.lastName;
console.log(member.getFullName());

输出

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
//考察ES6中的class
"member.getFullName is not a function"
//getFullName并没有写到Person的原型上,member并不能调用该函数
解决办法:
ES6:
class Person{
constructor(firstName, lastName){
this.firstName = firstName
this.lastName = lastName
}
getFullName = () => this.firstName + this.lastName;
}
const member = new Person("Lydia","Hallie")
console.log(member.getFullName())
ES5原型法:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
Person.prototype.getFullName = function(){
return this.firstName + this.lastName;
} //这里不能用箭头函数,否则this指向window

const member = new Person("Lydia", "Hallie");
console.log(member.getFullName());
1
2
3
4
5
6
7
8
function checkAge(data) {
if (data === { age: 18 }) {
console.log("you are an adult!");
} else {
console.log("hh");
}
}
checkAge({ age: 18 });

输出

1
2
3
//考察引用类型
hh; //考点在{age: 18} === {age: 18}是否相等,
//两个不同的地址,所以不相等


title: JS深入

date: 2019-09-20 19:52:59

tags:  # 这里写的分类会自动汇集到 categories 页面上,分类可以多级

  • JS # 一级分类
  • JS深入 # 二级分类

JavaScript 自动垃圾收集机制

垃圾回收又称为 GC(Garbage Collecation)。编写 JavaScript 程序时,开发者不需要手工跟踪内存的使用情况,只要按照标准写 JavaScript 代码,JavaScript 程序运行所需内存的分配以及无用内存的回收完全是自动管理。JavaScript 中自动垃圾回收机制的原理为:

找出那些不再使用的变量,然后释放其占用的内存。垃圾收集器会按照固定的时间间隔(或预定的收集时间)周期性地执行此操作。

局部变量的正常生命周期

局部变量只在函数执行的过程中存在。

在函数执行过程中,会为局部变量在栈内存(或 堆内存)上分配相应的空间来存储它们的值。在函数中使用这些变量,直至函数执行结束,此时可以释放局部变量的内存供将来需要时使用。

以上情况下,较容易判断变量是否有存在的必要,更复杂的情况需要更精细的变量追踪策略。

JavaScript 中的垃圾收集器必须跟踪每个变量是否有用,需要为不再有用的变量打上标记,用于将来回收其占用的内存。标识无用变量的策略通常有两个:标记清除引用计数

JavaScript 中的栈内存与堆内存

上述过程中,JavaScript 中变量分为 基本类型值 和 引用类型值:

  • 基本类型值 在内存中占固定大小的空间,因此被保存在 栈内存 中;
  • 引用类型值 是对象,保存在 堆内存 中。包含引用类型值的变量实际包含并非对象本身,而是指向该对象的指针。一个变量从另一个变量复制引用类型的值时,复制的也是指向该对象的指针。

标记清除

标记清除(mark-and-sweep) 是 JavaScript 中最常用的垃圾回收方式。其执行机制如下:

  • 当变量进入环境时,就将其标记为“进入环境”
  • 当变量离开环境时将其标记为“离开环境”

逻辑上,永远不能释放进入环境的变量所占用的内存,因为执行流进入相应的环境时,可能会用到它们。

标记变量的方式有很多种,可以使用标记位的形式记录变量进入环境,也可单独为“进入环境”和“离开环境”添加变量列表来记录变化。

标记清除采用的收集策略为:

  1. JavaScript 中的垃圾收集器运行时会给存储在内存中的所有变量都加上标记;
  2. 然后去掉环境中的变量以及被环境中的变量引用的变量的标记;
  3. 此后,再被加上标记的变量被视为准备删除的变量;
  4. 最后,垃圾收集器完成内存清除,销毁那些带标记的值并回收其占用的内存空间。

2008 年之前,IE、Firefox、Opera、Chrome 和 Safari 的 JavaScript 实现使用的均为 标记清除式的垃圾回收策略,区别可能在垃圾收集的时间间隔。

引用计数

引用计数(reference counting) 是另一种垃圾收集策略。引用计数的本质是 跟踪记录每个值被引用的次数。其执行机制如下:

  1. 当声明一个变量并将一个引用类型值赋值给该变量时,这个值的引用次数为 1;
  2. 若同一个值(变量)又被赋值给另一个变量,则该值的引用次数加 1;
  3. 但是如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减 1;
  4. 当这个值的引用次数为 0 时,则无法再访问这个值,就可回收其占用的内存空间。

垃圾收集器下次运行时,会释放那些引用次数为零的值所占用的内存。

引用计数存在一个致命的问题: 循环引用。循环引用是指,对象 A 中包含一个指向对象 B 的指针,而对象 B 中也包含一个指向对象 A 的引用。下面的代码就是标准的循环引用的例子:

1
2
3
4
5
6
7
function cycleRefernce() {
var objectA = new Object();
var objectB = new Object();

objectA.someOtherObject = objectB;
objectB.anotherObject = objectA;
}

上述例子中 objectA 和 objectB 通过各自属性相互引用。按照引用计数的策略,两个对象的引用次数均为 2。若采用标记清除策略,函数执行完毕,对象离开作用域就不存在相互引用。但采用引用计数后,函数执行完,两个对象的引用次数永不为 0,会一直存尊内存中,若多次调用,导致大量内存得不到回收。

IE8 浏览器 之前中有一部分对象并不是原生的 JavaScript 对象,可能是使用 C++ 以 COM 对象的形式实现的(BOM, DOM)。而 COM 对象的垃圾收集机制采用的是 引用计数策略。即使 IE 的 JavaScript 引擎是使用标记清除策略实现的,但 JavaScript 访问 COM 对象仍然是基于 引用计数策略的。在这种情况下,只要在 IE 中涉及 COM 对象,就可能存在循环引用的问题。

为避免出现循环引用,最好在不使用这些对象时,手动断开 原生 JavaScript 对象 与 DOM 元素之间的连接。IE 中的循环引用与手动断开的操作如下所示:

1
2
3
4
5
6
7
8
9
var element = document.getElementById("some_element");
var myObject = new Object();
myObject.element = element;
element.someObject = myObject;
// 以上 存在循环引用
// ......
// 以下 手工断开连接
myObject.element = null;
element.someObject = null;

将变量设置成 null 即可切断变量与它之前引用的值之间的连接。下次垃圾收集器运行时,会删除这些值并回收它们占用的内存。

为解决上述问题,IE9 及以上版本把 BOM 和 DOM 对象都转换成了真正的 JavaScript 对象,避免了两种垃圾回收算法并存引起的问题。

垃圾回收的性能问题

垃圾收集器是周期运行的,确定垃圾收集的时间间隔是个重要的问题。

IE7 之前的垃圾收集器是根据内存分配量运行的,即 256 个变量、4096 个对象(数组)字面量或 64 KB 的字符串。达到这些临界值的任何一个,垃圾收集器就会运行。所以就导致如果一个脚本含有很多变量,在整个生命周期中一直保有前面临界值大小的变量,就会频繁触发垃圾回收,会存在严重的性能问题。

IE7 重写了垃圾收集例程。新的工作方式为:触发垃圾收集的变量分配、字面量和数组元素的临界值被调整为 动态修正。初始值与之前版本相同,但如果垃圾收集例程回收的内存低于 15%,则临界值加倍。若回收内存分配量超过 85%,则临界值重置回默认值。

JavaScript V8 引擎的垃圾回收机制

在 JavaScript 脚本中,绝大多数对象的生存期很短,只有部分对象的生存期较长。所以,V8 中的垃圾回收主要使用的是 分代回收 (Generational collection)机制。

分代回收机制

V8 引擎将保存对象的 (heap) 进行了分代:

  1. 对象最初会被分在 新生区(New Space) (1~8M),新生区的内存分配只需要保有一个指向内存区的指针,不断根据内存大小进行递增,当指针达到新生区的末尾,会有一次垃圾回收清理(小周期),清理掉新生区中不再活跃的死对象。
  2. 对于超过 2 个小周期的对象,则需要将其移动至 老生区(Old Space)。老生区在 标记-清除 或 标记-紧缩 的过程(大周期) 中进行回收。

大周期进行的并不频繁。一次大周期通常是在移动足够多的对象至老生区后才会发生。

Scavenge 算法

由于垃圾清理发生的比较频繁,清理的过程必须很快。V8 中的清理过程使用的是 Scavenge 算法,按照 经典的 Cheney 算法 实现的。Scavenge 算法的主要过程是:

  • 新生区被分为两个等大小的子区(semi-spaces):to-space 和 from-space;
  • 大多数的内存分配都是在 to-space 发生 (某些特定对象是在老生区);
  • 当 to-space 耗尽时,交换 to-space 和 from-space, 此时所有的对象都在 from-space;
  • 然后将 from-space 中活跃的对象复制到 to-space 或者老生区中;
  • 这些对象被直接压到 to-space,提升了 Cache 的内存局部性,可使内存分配简洁快速。

算法的伪代码描述如下:

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
def scavenge():
swap(fromSpace, toSpace)
allocationPtr = toSpace.bottom
scanPtr = toSpace.bottom

for i = 0..len(roots):
root = roots[i]
if inFromSpace(root):
rootCopy = copyObject(&allocationPtr, root)
setForwardingAddress(root, rootCopy)
roots[i] = rootCopy

while scanPtr < allocationPtr:
obj = object at scanPtr
scanPtr += size(obj)
n = sizeInWords(obj)
for i = 0..n:
if isPointer(obj[i]) and not inOldSpace(obj[i]):
fromNeighbor = obj[i]
if hasForwardingAddress(fromNeighbor):
toNeighbor = getForwardingAddress(fromNeighbor)
else:
toNeighbor = copyObject(&allocationPtr, fromNeighbor)
setForwardingAddress(fromNeighbor, toNeighbor)
obj[i] = toNeighbor

def copyObject(*allocationPtr, object):
copy = *allocationPtr
*allocationPtr += size(object)
memcpy(copy, object, size(object))
return copy

不能被忽视的写屏障 Write barriers

如果新生区有某个对象,只有一个指向它的指针,恰好该指针在老生区的对象中,在垃圾回收之前我们如何得知新生区的该对象是活跃的呢?

为解决此问题,V8 在写缓冲区有一个列表,其中记录了所有老生区对象指向新生区的情况。新生区对象诞生时不会有指向它的指针,当老生区的对象出现指向新生区对象的指针时,便记录跨区指向,记录行为总是发生在写操作中。

标记-清除算法 与 标记-紧缩算法

因为新生区的内存一般都不大,所以使用 Scavenge 算法进行垃圾回收效果比较好。老生区一般占用内存较大,因此采用的是 标记-清除(Mark-Sweep)算法 与 标记-紧缩(Mark-Compact)算法。

两种算法都包括两个阶段:标记阶段,清除或紧缩阶段。

标记阶段

在标记阶段,堆上所有的活跃对象都会被发现并且标记。

每一页都包含用来标记的位图

位图都要占据空间 (3.1% on 32-bit, 1.6% on 64-bit systems)

使用两位二进制标记对象的状态

状态为白(white), 它尚未被垃圾回收器发现

状态为灰(gray), 它已被垃圾回收器发现,但它的邻接对象仍未全部处理完毕

状态为黑(black), 它不仅被垃圾回收器发现,而且其所有邻接对象也都处理完毕

标记算法的核心是 深度优先搜索,具体过程为:

  1. 在标记的初期,位图是空的,所有对象也都是白的。
  2. 从根可达的对象会被染色为灰色,并被放入标记用的一个单独分配的双端队列。
  3. 标记阶段的每次循环,GC 会将一个对象从双端队列中取出,染色为黑,然后将它的邻接对象染色为灰,并把邻接对象放入双端队列。
  4. 这一过程在双端队列为空且所有对象都变黑时结束。
  5. 特别大的对象,如长数组,可能会在处理时分片,以防溢出双端队列。如果双端队列溢出了,则对象仍然会被染为灰色,但不会再被放入队列(这样他们的邻接对象就没有机会再染色了)。
  6. 因此当双端队列为空时,GC 仍然需要扫描一次,确保所有的灰对象都成为了黑对象。对于未被染黑的灰对象,GC 会将其再次放入队列,再度处理。

标记算法结束后,所有的活跃对象都被染成黑色,所有的死对象仍是白的。下一步就可以清除或者紧缩了。

清除 或 紧缩 算法

标记算法执行后,可以选择清除 或是紧缩,这两个算法都可以收回内存,而且两者都作用于页级(V8 中的内存页是 1MB 的连续内存块)

清除算法扫描连续存放的死对象,将其变为空闲空间,并将其添加到空闲内存链表中。清除算法只需要遍历页的位图,搜索连续的白对象。[每一页都包含数个空闲内存链表,其分别代表小内存区(<256 字)、中内存区(<2048 字)、大内存区(<16384 字)和超大内存区(其它更大的内存)]

紧缩算法会尝试将对象从碎片页(包含大量小空闲内存的页)中迁移整合在一起,来释放内存。这些对象会被迁移到另外的页上,因此也可能会新分配一些页。而迁出后的碎片页就返还给操作系统。

对目标碎片页中的每个活跃对象,在空闲内存链表中分配一块其它页的区域,将该对象复制至新页,并在碎片页中的该对象上写上转发地址。

迁出过程中,对象中的旧地址会被记录下来,这样在迁出结束后 V8 会遍历它所记录的地址,将其更新为新的地址。由于标记过程中也记录了不同页之间的指针,此时也会更新这些指针的指向。

增量标记 与 惰性清除

对于一个堆很大,活跃对象有很多的脚本时,标记-清除 与 标记-紧缩 的效率可能会很慢,为减少垃圾回收引起的停顿,引入了 增量标记(Incremental marking) 和 惰性清理(lazy sweeping)。

增量标记允许堆的标记(前面的标记阶段)发生在几次 5-10 毫秒的小停顿中。增量标记在堆的大小达到一定的阈值时启用,启用之后每当一定量的内存分配后,脚本的执行就会停顿并进行一次增量标记。就像普通的标记一样,增量标记也是一个深度优先搜索,并同样采用白灰黑机制来分类对象。

增量标记与普通标记的区别是,添加了从黑对象到白对象的指针,为此需要再次启用写屏障中,在记录 老->新 的同时,记录 黑->白。在进行清除时,一旦在写屏障中发现这样的指针,黑对象会被重新染色为灰对象,重新放回到双端队列中。

惰性清理是指在标记完成后,并不急着释放空间,无需一次清理所有的页,垃圾回收器会视情况逐一清理,直到所有页都清理完成。

余下的涉及垃圾回收原理的部分留着后面继续整理。(平行标记 与 并发标记)

Event Loop

众所周知 JS 是门非阻塞单线程语言,因为在最初 JS 就是为了和浏览器交互而诞生的。如果 JS 是门多线程的语言话,我们在多个线程中处理 DOM 就可能会发生问题(一个线程中新加节点,另一个线程中删除节点),当然可以引入读写锁解决这个问题。

JS 在执行的过程中会产生执行环境,这些执行环境会被顺序的加入到执行栈中。如果遇到异步的代码,会被挂起并加入到 Task(有多种 task) 队列中。一旦执行栈为空,Event Loop 就会从 Task 队列中拿出需要执行的代码并放入执行栈中执行,所以本质上来说 JS 中的异步还是同步行为。

1
2
3
4
5
6
7
console.log("script start");

setTimeout(function () {
console.log("setTimeout");
}, 0);

console.log("script end");

以上代码虽然 setTimeout 延时为 0,其实还是异步。这是因为 HTML5 标准规定这个函数第二个参数不得小于 4 毫秒,不足会自动增加。所以 setTimeout 还是会在 script end 之后打印。

微任务和宏任务

不同的任务源会被分配到不同的 Task 队列中,任务源可以分为 微任务(microtask)宏任务(macrotask)。在 ES6 规范中,microtask 称为 jobs,macrotask 称为 task。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
console.log("script start");

setTimeout(function () {
console.log("setTimeout");
}, 0);

new Promise((resolve) => {
console.log("Promise");
resolve();
})
.then(function () {
console.log("promise1");
})
.then(function () {
console.log("promise2");
});

console.log("script end");
// script start => Promise => script end => promise1 => promise2 => setTimeout

以上代码虽然 setTimeout 写在 Promise 之前,但是因为 Promise 属于微任务而 setTimeout 属于宏任务,所以会有以上的打印。

微任务包括 process.nextTickpromiseObject.observeMutationObserver

宏任务包括 scriptsetTimeoutsetIntervalsetImmediateI/OUI rendering

很多人有个误区,认为微任务快于宏任务,其实是错误的。因为宏任务中包括了script,浏览器会先执行一个宏任务,接下来有异步代码的话就先执行微任务。

所以正确的一次 Event loop 顺序是这样的

  1. 执行同步代码,这属于宏任务
  2. 执行栈为空,查询是否有微任务需要执行
  3. 执行所有微任务
  4. 必要的话渲染 UI
  5. 然后开始下一轮 Event loop,执行宏任务中的异步代码

通过上述的 Event loop 顺序可知,如果宏任务中的异步代码有大量的计算并且需要操作 DOM 的话,为了更快的 界面响应,我们可以把操作 DOM 放入微任务中。


title: 头条面试题 date: 2019-11-16 16:26:56

tags: 面试题

第一套

  1. 替换元素与非替换元素,差异?

  2. offsetWidth,clientWidth,scrollwidth 区别

  3. Dom 标准事件模型是什么?是否所有的事件都支持冒泡?如果不是,举例说明.

  4. css 选择器的优先级

  5. 简述什么是 IFC,作用

  6. 用 CSS 实现自适应正方形

  7. Http://toutiao.comhttp://mp.toutiao.comajax请求,跨域了吗?mp.toutiao.com的服务器可以收到请求吗,是怎样的请求?

  8. XSS 和 CRSF 分别是什么?有什么联系?如何防御?

  9. 关于 js Bridge

    1. 是什么?
    2. 实现原理
    3. 优化方案
  10. TCP/UDP 是什么?有什么区别?如何进行拥塞控制?

  11. 请用算法实现.从给定的无序,不重复的数组 A 中,取出 N 个数,使其相加和为 M.并给出算法的时间空间复杂度.

第二套

  1. ES5 和 ES6 的继承有什么区别?
  2. Web 动画的几种常见方式
  3. POST 提交数据的几种常见 content-type
  4. 如何定义一个方法,通过调用把视频的一帧生产预览图
  5. 什么是重放攻击,列举几个防御手段.

编程题

  1. 实现一个函数 sum,运算结果满足如下预期结果:
1
2
3
4
1.	sum(1,2,3).valueof(); //6
2. sum(2,3)(2).valueof(); //7
3. sum(1)(2)(3)(4).valueof(); //10
4. sum(2)(4,1)(2).valueof(); //9
  1. 实现一个优先队列,使得可以这样使用.
1
2
3
4
5
6
7
8
9
1.	Const priorityQuene = new PriorityQuene()
2. priorityQuene.enquene(‘优先级2-1’,2)’
3. priorityQuene.enquene(‘优先级1-1’,1)’
4. priorityQuene.enquene(‘优先级1-2’,1)’
5. priorityQuene.enquene(‘优先级3-1’,3)’
6. priorityQuene.enquene(‘优先级2-2’,2)’
7. priorityQuene.enquene(‘优先级1-3’,1)’
8. priorityQuene..print(); //按优先级顺序输出
9. priorityQuene.dequene(); //出队
  1. 硬币找零问题:

有面额为 d1…dn 的硬币,和要找零的钱数,找出所需最小硬币个数的方案,

例如,美国有以下面额的硬币: d1=1,d2=5,d3=10,d4=25.如果要找 36 美分.,所需最少硬币是[1,10,25],即满足如下输出:

1
2
3
4
const miniCoinChange = new MinCoinChange(1,5,10,25));
console.log(minCoinChange.makeChange(36)) //[1,10,25]
const minCoinChange2 = new MinCoinChange([1,3,4]
console.log(minCoinChange2.makeChange(6); //[3,3]


title: photoshop 切图 date: 2019-10-27 16:08:59

tags:

新建图层

建议背景图层透明.

移动工具: 快捷键V

按住Ctrl,自动选择图层.

放大缩小: Alt+滚轮

界面设置

视图设置:

  1. 智能参考线
  2. 标尺(ctrl+R)

##窗口设置

  1. 信息
  2. 字符
  3. 历史记录

修改信息-面板选项

|-鼠标坐标单位-像素

|-第一颜色信息-RGB 颜色

|-第二颜色信息-RGB 颜色

|-状态信息-文档尺寸

编辑设置

首选项-单位与标尺-标尺,文字改为像素

格局

  1. 图层和历史记录
  2. 信息和字符

保存为 web 切图

窗口-工作区-新建工作区-web 切图

基本操作

套索工具L

放大Ctrl+ +

缩小Ctrl+ -

按住空格键拖动

按住shift键: 增加选区

按住alt键: 删除选区

磁性套索工具L

可以吸附到同颜色区域边界.

多余地方可以按住shfit或者alt进行增删

快速选择工具W

可以快速选择相同颜色区块

画笔大小快捷键:[]

魔棒工具W

类似于快速选择工具

裁剪工具C

使用魔棒工具或快速选择工具选取一块区域,直接按裁剪工具,然后回车,可快速裁剪出所选区域.

此时,按住 Alt,点击当前图层的眼睛图标,可隐藏其他图层,只保留当前图层.即显影模式.

裁剪工具时,按住Alt,点击所选图案,可以让图案成为中心.

吸管工具 I

选取当前颜色,

前景色填充Alt+Delete

背景色填充Ctrl+Delete

污点修复画笔工具 J

选取周围的颜色覆盖选取区域的颜色.比如照片中黑点会被抹除.

修复画笔工具

按住Alt键单击左键选取要复制的区域,在其他区域就可以复制过去.

修补工具

画出一个选取,将选取拖动到想要使用修补的地方进行替换修补.

仿制图章工具

类似于修复画笔工具,双击选取要复制的点,在其他地方可以复制出来.与 修复画笔工具区别是,修复画笔工具复制出来的有羽化效果.

图案图章工具

有可选择的备用图案,作用类似于打马赛克.

历史记录画笔工具 Y

作用类似于祛斑.先用高斯模糊将照片模糊到没有斑点.再将画笔圆记录到历史记录的高斯模糊这一步,再回到原图,使用该画笔圆进行处理.

渐变

渐变颜色方向是顺时针

文字

双击空白区域,即可写字.按住 Ctrl 可以拖动区域大小,Alt 也可以拖动.

抓手工具 H

按住Alt缩小,按住shift放大.

图层

奥运五环

  1. 新建文件,背景色为白色.
  2. 右下角新建图层.
  3. 使用椭圆选区工具M,按住shift(有的是alt),拉出一个标准圆.
  4. 使用吸管工具I,点击拾色器,选择颜色,确定.使用快捷键Alt+Delete填充圆形选区的颜色.
  5. 使用选区工具M,右键变化选区,按住Alt进行同心圆的缩小.右上角对号确定.按下delete删除内圆,得到圆环.
  6. Ctrl+ T自由变换,进行调整.
  7. 使用移动工具V,按住Alt进行拖动,即复制一张图层.
  8. 在右下角图层菜单中,双击图层名,进行重命名.
  9. 选取复制图层,按住Ctrl单击图层菜单中缩略图.选择颜色,Alt+Delete填充颜色.
  10. M使用选区后,单击空白就结束选区了.
  11. 删除覆盖在黄色上的蓝色,按住Ctrl,单击蓝色图层的缩略图,选中蓝色选区,
  12. 使用选区工具M,点击黄色图层,按住Alt选取要排除的区块(因为此时要删掉黄色图层上的两小块区域,而选区是蓝色选区,排除掉保留的蓝色,剩下的就是要真正删除的蓝色小块了),按Delete删除,就出现交叉效果了.

参考线

  1. 视图-新建参考线(按住Alt,依次按V+E)
  2. Ctrl+R调出标尺,切换到移动工具V,然后从标尺处拖出参考线.

参考线切换: 拉动参考线,按住Alt.

参考线初始化

通用页面像素一般是 1920*1080,页面内容一般占 1200,两侧留白共 720px

起始左侧线可设置为 360px,右侧线设置为 1560px.

隐藏参考线

快捷键: Ctrl+ ;

切图

传统切图

裁剪工具C-切片工具

参考线辅助切图

先设置参考线,再点击切片工具C,上面菜单基于参考线切片.

通过删除多余切片将零碎切片合并为一个切片.

文件-存储为 web 所用格式,一般格式为 PNG-24.

建议将大图裁剪成多份,然后使用参考线切片.

精准切图

文件-脚本-将图层导出到文件-文件类型-PNG24-全部打 √

文件-导出-将图层导出导出到文件-文件类型-PNG24-全部打 √

(因为软件版本不同,可能命令不在上述中,上述是同一命令的两个位置)

选择导出目录,运行

扩展知识

将整个图层切成一个图

编辑-首选项-增效工具-启动生成器打 √

文件-生成-图像资源

抠图

先打开图片,

图层-新建图层-新建图层背景

图像-画布大小

Ctrl+T选中图片,Alt进行比例放大

Ctrl+ X可以把选取内不需要的内容删掉

导出-快速导出 PNG 即可.

描白边

使用魔棒选取大部分边缘,使用选区工具进行修补.

编辑-描边-白色-5px 左右,外部

使用魔棒点击外部区域,选择反向,就选中了包含白边的区域.

此时可以使用编辑-填充-黑色,就出现纯黑背景的白边图片了.