learnfront

learnfront 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

平庸无能的己,能被允许创造一个小小的奇迹吗?!

个人动态

learnfront 提出了问题 · 10月18日

怎么通过chrome的开发者工具的dom断点定位到源码

如何通过chrome浏览器的开发者工具的break on attributes modifications 来设置断点,快速定位到js或css代码的实现源码处.
具体例子如图下:
网址在这: https://www.cnblogs.com/summi...
dom断掉调试
想通过定位源码查看图中圈出的hover效果是怎么实现的.
当鼠标悬停到另外一个选项卡的时候,原来的选项卡底下的蓝色横条会逐渐消失,鼠标当前指向的选项卡下面横条会逐渐增加至全部.
我自己试着给li元素或者a标签设置了 break on attribute modifications的断点.但是尝试了很多次,鼠标移动上去,并没有进入断点定位到js或者css代码处(压根没反应)。请教各位,具体应该怎么做呀?

关注 1 回答 1

learnfront 收藏了问题 · 10月18日

vscode怎么展开压缩过只有一行的js代码?

vscode跟踪代码的时候,遇到压缩过的只有一行的js代码,想问怎么展开,让它正常显示?

learnfront 收藏了问题 · 10月12日

Javascript中return和return false在什么情况下使用?

Javascript中return和return false在什么情况下使用?
比如下面这个使用了return false,可是看不明白问什么要使用。

$(function () {
    $("body").on("click", "#refreshimg", function () {
        $.post("newsession.php");
        $("#captchaimage").load("image_req.php");
        return false;
    });
});  

同样的,有时看到这样的代码:

$(function () {
    $("body").on("click", "#foo", function () {
        //...
        //...
        return;
    });
}); 

不知道为什么,请大神帮解释一下。
1、什么情况下使用?
2、两者的区别是什么?

learnfront 赞了问题 · 10月11日

Javascript中return和return false在什么情况下使用?

Javascript中return和return false在什么情况下使用?
比如下面这个使用了return false,可是看不明白问什么要使用。

$(function () {
    $("body").on("click", "#refreshimg", function () {
        $.post("newsession.php");
        $("#captchaimage").load("image_req.php");
        return false;
    });
});  

同样的,有时看到这样的代码:

$(function () {
    $("body").on("click", "#foo", function () {
        //...
        //...
        return;
    });
}); 

不知道为什么,请大神帮解释一下。
1、什么情况下使用?
2、两者的区别是什么?

关注 13 回答 7

learnfront 收藏了文章 · 2019-12-29

nginx配置location总结及rewrite规则写法

location正则写法

一个示例:

location  = / {
  # 精确匹配 / ,主机名后面不能带任何字符串
  [ configuration A ] 
}

location  / {
  # 因为所有的地址都以 / 开头,所以这条规则将匹配到所有请求
  # 但是正则和最长字符串会优先匹配
  [ configuration B ] 
}

location /documents/ {
  # 匹配任何以 /documents/ 开头的地址,匹配符合以后,还要继续往下搜索
  # 只有后面的正则表达式没有匹配到时,这一条才会采用这一条
  [ configuration C ] 
}

location ~ /documents/Abc {
  # 匹配任何以 /documents/ 开头的地址,匹配符合以后,还要继续往下搜索
  # 只有后面的正则表达式没有匹配到时,这一条才会采用这一条
  [ configuration CC ] 
}

location ^~ /images/ {
  # 匹配任何以 /images/ 开头的地址,匹配符合以后,停止往下搜索正则,采用这一条。
  [ configuration D ] 
}

location ~* \.(gif|jpg|jpeg)$ {
  # 匹配所有以 gif,jpg或jpeg 结尾的请求
  # 然而,所有请求 /images/ 下的图片会被 config D 处理,因为 ^~ 到达不了这一条正则
  [ configuration E ] 
}

location /images/ {
  # 字符匹配到 /images/,继续往下,会发现 ^~ 存在
  [ configuration F ] 
}

location /images/abc {
  # 最长字符匹配到 /images/abc,继续往下,会发现 ^~ 存在
  # F与G的放置顺序是没有关系的
  [ configuration G ] 
}

location ~ /images/abc/ {
  # 只有去掉 config D 才有效:先最长匹配 config G 开头的地址,继续往下搜索,匹配到这一条正则,采用
    [ configuration H ] 
}

location ~* /js/.*/\.js
  • =开头表示精确匹配
    如 A 中只匹配根目录结尾的请求,后面不能带任何字符串。
  • ^~ 开头表示uri以某个常规字符串开头,不是正则匹配
  • ~ 开头表示区分大小写的正则匹配;
  • ~* 开头表示不区分大小写的正则匹配
  • / 通用匹配, 如果没有其它匹配,任何请求都会匹配到

顺序 no优先级:
(location =) > (location 完整路径) > (location ^~ 路径) > (location ~,~* 正则顺序) > (location 部分起始路径) > (/)

上面的匹配结果
按照上面的location写法,以下的匹配示例成立:

  • / -> config A
    精确完全匹配,即使/index.html也匹配不了
  • /downloads/download.html -> config B
    匹配B以后,往下没有任何匹配,采用B
  • /images/1.gif -> configuration D
    匹配到F,往下匹配到D,停止往下
  • /images/abc/def -> config D
    最长匹配到G,往下匹配D,停止往下
    你可以看到 任何以/images/开头的都会匹配到D并停止,FG写在这里是没有任何意义的,H是永远轮不到的,这里只是为了说明匹配顺序
  • /documents/document.html -> config C
    匹配到C,往下没有任何匹配,采用C
  • /documents/1.jpg -> configuration E
    匹配到C,往下正则匹配到E
  • /documents/Abc.jpg -> config CC
    最长匹配到C,往下正则顺序匹配到CC,不会往下到E

实际使用建议

所以实际使用中,个人觉得至少有三个匹配规则定义,如下:
#直接匹配网站根,通过域名访问网站首页比较频繁,使用这个会加速处理,官网如是说。
#这里是直接转发给后端应用服务器了,也可以是一个静态首页
# 第一个必选规则
location = / {
    proxy_pass http://tomcat:8080/index
}
# 第二个必选规则是处理静态文件请求,这是nginx作为http服务器的强项
# 有两种配置模式,目录匹配或后缀匹配,任选其一或搭配使用
location ^~ /static/ {
    root /webroot/static/;
}
location ~* \.(gif|jpg|jpeg|png|css|js|ico)$ {
    root /webroot/res/;
}
#第三个规则就是通用规则,用来转发动态请求到后端应用服务器
#非静态文件请求就默认是动态请求,自己根据实际把握
#毕竟目前的一些框架的流行,带.php,.jsp后缀的情况很少了
location / {
    proxy_pass http://tomcat:8080/
}

http://tengine.taobao.org/book/chapter_02.html
http://nginx.org/en/docs/http/ngx_http_rewrite_module.html

Rewrite规则

rewrite功能就是,使用nginx提供的全局变量或自己设置的变量,结合正则表达式和标志位实现url重写以及重定向。rewrite只能放在server{},location{},if{}中,并且只能对域名后边的除去传递的参数外的字符串起作用,例如 http://seanlook.com/a/we/index.php?id=1&u=str 只对/a/we/index.php重写。语法rewrite regex replacement [flag];

如果相对域名或参数字符串起作用,可以使用全局变量匹配,也可以使用proxy_pass反向代理。

表明看rewrite和location功能有点像,都能实现跳转,主要区别在于rewrite是在同一域名内更改获取资源的路径,而location是对一类路径做控制访问或反向代理,可以proxy_pass到其他机器。很多情况下rewrite也会写在location里,它们的执行顺序是:

  1. 执行server块的rewrite指令
  2. 执行location匹配
  3. 执行选定的location中的rewrite指令

如果其中某步URI被重写,则重新循环执行1-3,直到找到真实存在的文件;循环超过10次,则返回500 Internal Server Error错误。

flag标志位

  • last : 相当于Apache的[L]标记,表示完成rewrite
  • break : 停止执行当前虚拟主机的后续rewrite指令集
  • redirect : 返回302临时重定向,地址栏会显示跳转后的地址
  • permanent : 返回301永久重定向,地址栏会显示跳转后的地址

因为301和302不能简单的只返回状态码,还必须有重定向的URL,这就是return指令无法返回301,302的原因了。这里 last 和 break 区别有点难以理解:

  1. last一般写在server和if中,而break一般使用在location中
  2. last不终止重写后的url匹配,即新的url会再从server走一遍匹配流程,而break终止重写后的匹配
  3. break和last都能组织继续执行后面的rewrite指令

if指令与全局变量

if判断指令
语法为if(condition){...},对给定的条件condition进行判断。如果为真,大括号内的rewrite指令将被执行,if条件(conditon)可以是如下任何内容:

  • 当表达式只是一个变量时,如果值为空或任何以0开头的字符串都会当做false
  • 直接比较变量和内容时,使用=!=
  • ~正则表达式匹配,~*不区分大小写的匹配,!~区分大小写的不匹配

-f!-f用来判断是否存在文件
-d!-d用来判断是否存在目录
-e!-e用来判断是否存在文件或目录
-x!-x用来判断文件是否可执行

例如:

if ($http_user_agent ~ MSIE) {
    rewrite ^(.*)$ /msie/$1 break;
} //如果UA包含"MSIE",rewrite请求到/msid/目录下

if ($http_cookie ~* "id=([^;]+)(?:;|$)") {
    set $id $1;
 } //如果cookie匹配正则,设置变量$id等于正则引用部分

if ($request_method = POST) {
    return 405;
} //如果提交方法为POST,则返回状态405(Method not allowed)。return不能返回301,302

if ($slow) {
    limit_rate 10k;
} //限速,$slow可以通过 set 指令设置

if (!-f $request_filename){
    break;
    proxy_pass  http://127.0.0.1; 
} //如果请求的文件名不存在,则反向代理到localhost 。这里的break也是停止rewrite检查

if ($args ~ post=140){
    rewrite ^ http://example.com/ permanent;
} //如果query string中包含"post=140",永久重定向到example.com

location ~* \.(gif|jpg|png|swf|flv)$ {
    valid_referers none blocked www.jefflei.com www.leizhenfang.com;
    if ($invalid_referer) {
        return 404;
    } //防盗链
}

全局变量
下面是可以用作if判断的全局变量

  • $args : #这个变量等于请求行中的参数,同$query_string
  • $content_length : 请求头中的Content-length字段。
  • $content_type : 请求头中的Content-Type字段。
  • $document_root : 当前请求在root指令中指定的值。
  • $host : 请求主机头字段,否则为服务器名称。
  • $http_user_agent : 客户端agent信息
  • $http_cookie : 客户端cookie信息
  • $limit_rate : 这个变量可以限制连接速率。
  • $request_method : 客户端请求的动作,通常为GET或POST。
  • $remote_addr : 客户端的IP地址。
  • $remote_port : 客户端的端口。
  • $remote_user : 已经经过Auth Basic Module验证的用户名。
  • $request_filename : 当前请求的文件路径,由root或alias指令与URI请求生成。
  • $scheme : HTTP方法(如http,https)。
  • $server_protocol : 请求使用的协议,通常是HTTP/1.0或HTTP/1.1。
  • $server_addr : 服务器地址,在完成一次系统调用后可以确定这个值。
  • $server_name : 服务器名称。
  • $server_port : 请求到达服务器的端口号。
  • $request_uri : 包含请求参数的原始URI,不包含主机名,如:”/foo/bar.php?arg=baz”。
  • $uri : 不带请求参数的当前URI,$uri不包含主机名,如”/foo/bar.html”。
  • $document_uri : 与$uri相同。

例:http://localhost:88/test1/test2/test.php
$host:localhost
$server_port:88
$request_uri:http://localhost:88/test1/test2/test.php
$document_uri:/test1/test2/test.php
$document_root:/var/www/html
$request_filename:/var/www/html/test1/test2/test.php

常用正则

  • . : 匹配除换行符以外的任意字符
  • ? : 重复0次或1次
  • + : 重复1次或更多次
  • * : 重复0次或更多次
  • \d :匹配数字
  • ^ : 匹配字符串的开始
  • $ : 匹配字符串的介绍
  • {n} : 重复n次
  • {n,} : 重复n次或更多次
  • [c] : 匹配单个字符c
  • [a-z] : 匹配a-z小写字母的任意一个

小括号()之间匹配的内容,可以在后面通过$1来引用,$2表示的是前面第二个()里的内容。正则里面容易让人困惑的是\转义特殊字符。

rewrite实例

例1

http {
    # 定义image日志格式
    log_format imagelog '[$time_local] ' $image_file ' ' $image_type ' ' $body_bytes_sent ' ' $status;
    # 开启重写日志
    rewrite_log on;

    server {
        root /home/www;

        location / {
                # 重写规则信息
                error_log logs/rewrite.log notice; 
                # 注意这里要用‘’单引号引起来,避免{}
                rewrite '^/images/([a-z]{2})/([a-z0-9]{5})/(.*)\.(png|jpg|gif)$' /data?file=$3.$4;
                # 注意不能在上面这条规则后面加上“last”参数,否则下面的set指令不会执行
                set $image_file $3;
                set $image_type $4;
        }

        location /data {
                # 指定针对图片的日志格式,来分析图片类型和大小
                access_log logs/images.log mian;
                root /data/images;
                # 应用前面定义的变量。判断首先文件在不在,不在再判断目录在不在,如果还不在就跳转到最后一个url里
                try_files /$arg_file /image404.html;
        }
        location = /image404.html {
                # 图片不存在返回特定的信息
                return 404 "image not found\n";
        }
}

对形如/images/ef/uh7b3/test.png的请求,重写到/data?file=test.png,于是匹配到location /data,先看/data/images/test.png文件存不存在,如果存在则正常响应,如果不存在则重写tryfiles到新的image404 location,直接返回404状态码。

例2

rewrite ^/images/(.*)_(\d+)x(\d+)\.(png|jpg|gif)$ /resizer/$1.$4?width=$2&height=$3? last;

对形如/images/bla_500x400.jpg的文件请求,重写到/resizer/bla.jpg?width=500&height=400地址,并会继续尝试匹配location。

例3
ssl部分页面加密

参考


原文链接地址:http://seanlook.com/2015/05/17/nginx-location-rewrite/


查看原文

learnfront 赞了文章 · 2019-12-29

ES6精华:Proxy & Reflect

导语

本文主要介绍了ES6中ProxyReflect的精华知识,并附有恰当实例。Proxy意为代理器,通过操作为对象生成的代理器,实现对对象各类操作的拦截式编程。Reflect是一个包揽更为严格、健全的操作对象方法的模块。因为Proxy所能代理的方法和Reflect所包括的方法基本对应,而且在拦截方法里应该使用对应的Reflect方法返回结果,所以将两者合并在一起分享。

1 Proxy

1.1 登堂

先想个问题,如何管控对象某一属性的读取和修改(不涉及闭包创建私有属性)?
先创建不应被直接改动的容器属性:_属性名,再设置相应的settergetter函数或创建相应的操作方法。

--- 设置 setter 和 getter 函数
let obj = {
  _id: undefined,
  get id() {
    return this._id;
  },
  set id(v) {
    this._id = v;
  }
};
obj.id = 3; // 相当:obj._id = 3
console.log(obj.id); // console.log(obj._id);

--- 创建获取及修改方法
let obj = {
  _id: undefined,
  id() {
    if (!arguments.length) return this._id;
    this._id = arguments[0];
  }
};
obj.id(3); // 相当:obj._id = 3
console.log(obj.id()); // console.log(obj._id);

这样有明显的缺陷:要为每个需要管控的属性进行重复设置,而且实际上容器属性可以被任意修改。
如果要求升级,我们需要监听查看、删除、遍历对象属性的操作,怎么办?ES6之前只能凉拌,ES6之后Proxy代你办。

let obj = { id: 0, name: 'Wmaker' };
let objProxy = new Proxy(obj, {
  get(target, attr) {
    console.log(`Get ${attr}`);
    return target[attr];
  },
  set(target, attr, val) {
    console.log(`Set ${attr}`);
    target[attr] = val;
    return true;
  }
});
objProxy.id; // 打印出:Get id,相当:obj.id;
objProxy.name; // 打印出:Get name,相当:obj.name;

1.2 入室

如前节示例可知,Proxy是生成代理器的构造函数。传入的第一个参数为需要被代理的对象,第二个参数是需要拦截操作的配置对象(之后会列出所有可拦截的操作及其意义和注意事项)。配置对象中的每个拦截操作,都有默认格式的传入参数,有些也要求有特定的返回值(下面会罗列出某些规律)。

生成的代理器是一个与被代理对象关联的代理实例,可以像操作普通对象一样对待它。即是说,可以被delete掉某个属性,可以被遍历或获取原型。所有作用于代理器的操作,都相当直接作用于被代理对象,还可为其配置拦截操作。说的激愤点,苍老师能做好的,我们的小泽老师怎么会差呢?另外可代理的不单单是对象,属于对象的函数、数组都是无条件接受的。

为对象生成代理器之后,依然可以操作原对象,但这自然是不建议的。

参数
不同拦截函数的传入参数不尽相同,但一般为被代理对象,该操作需要的参数等和代理器对象。
不必记忆每个拦截函数的参数,为脑瓜减减负担,使用时先打印出arguments查看便会一目了然。

返回值
在拦截方法里,应尽量使用Reflect对应的方法进行操作,并返回该方法的返回值。一方面是简单,更重要的是在不同方法或某些环境下,对返回值有硬性的要求,否则直接报错。比如construct()必须返回一个对象,否则报错。再比如set()在严格模式下,必须返回true或可转化成true的值,无论操作是否成功。

"use strict";

--- 错误的做法
let obj = { id: 0 };
let objProxy = new Proxy(obj, {
  set(target, attr, val) {
    console.log(`Set ${attr}`);
    return target[attr] = val;
  }
});

objProxy.id = 1; // 操作成功。
objProxy.id = 0; // 操作已经成功,但报错,不再往下执行。

--- 推荐的做法
let obj = { id: 0 };
let objProxy = new Proxy(obj, {
  set(target, attr, val) {
    console.log(`Set ${attr}`);
    return Reflect.set(target, attr, val);
  }
});

objProxy.id = 1; // 操作成功。
objProxy.id = 0; // 操作成功。

拦截方法的返回值并不会直接反映到外部,JS会进行某些验证和排除。
比如即便在ownKeys()中返回全部的值,但实际到外部的只有相应的系列。

两次打印的结果不相等。

let obj = { id: 0, [Symbol.iterator]() {} };
let objProxy = new Proxy(obj, {
  ownKeys(target) {
    let res = Reflect.ownKeys(target);
    console.log('1', res);
    return res;
  }
});

console.log('2', Object.keys(objProxy));

限制性的延续
当被代理对象自身已有某些限制,比如不可扩展或属性不可写不可配置等。对象本身的操作已经受到了限制,这时如果执行相应的代理操作,自然会报错。比如当属性不可写时,如果代理并执行了set()操作,则会直接报错。

let obj = { id: 0 };
Object.defineProperty(obj, 'name', {
  value: 'Wmaker'
});

let objProxy = new Proxy(obj, {
  set(target, attr, val) {
    return Reflect.set(target, attr, val);
  }
});

objProxy.id = 1; // 操作成功。
objProxy.name = 'Limo'; // 报错。

this
有些原生对象的内部属性或方法,只有通过正确的this才能访问,所以无法进行代理。
比如日期对象,new Proxy(new Date(), {}).getDate(),报错提示:这不是个Date对象。

也有变通的办法,比如对于需要正确 this 的方法可以这样做。

let p = new Proxy(new Date(), {
  get(target, attr) {
    const v = Reflect.get(target, attr);
    return typeof v === 'function'
      ? v.bind(target)
      : v;
  }
});
p.getTime(); // 没有错误。

处于配置对象中的this直接指向配置对象,不是被代理对象或代理器对象。
处于被代理对象中的this,分为存在于方法和存在于getter/setter中两种。两者获取this的方式不同,我们以实例说明。

--- 例一,没有 set 拦截操作。
let obj = {
  get id() {
    console.log('o', this);
  },
  fn() {
    console.log('o', this);
  }
};

let objProxy = new Proxy(obj, {});

objProxy.id; // 打印出 objProxy 。
objProxy.fn(); // 打印出 objProxy 。


--- 例二,有 set 拦截操作。实际使用了 target[attr] 获取属性值。
let obj = {
  get id() {
    console.log('o', this);
  },
  fn() {
    console.log('o', this);
  }
};

let objProxy = new Proxy(obj, {
  get(target, attr) {
    console.log('p', this);
    return target[attr];
  }
});

objProxy.id;
// 打印出配置对象和 obj。
// 因为实际是通过被代理对象即 target 访问到 id 值的。

objProxy.fn();
// 打印出配置对象和 objProxy。
// 可以等价的将方法转化成 (objProxy.fn).call(objProxy)。
// 虽然方法也是通过 target 访问到的,但对于方法来说,环境变量一开始就确定了。

原型为代理器
如果对象的原型为代理器,当操作进行到原型时,实际是操作原型的代理器对象。这时,其行为和普通代理器一致。

let obj = Object.create(new Proxy({}, {
  get(target, attr) {
    console.log('In proxy.');
    return Reflect.get(target, attr);
  }
}));

obj.name; // 打印出 In proxy. 。
// 当在实例上找不到对应属性时,转到了原型上,这时便被拦截了。

1.3 代理类别

这里仅仅是罗列出某些重点,详细的请看手册(标准和行为处于变动中)。

get
拦截属性的读取操作,包括数组取值。

set
拦截属性的赋值操作,包括数组赋值。
严格模式下,必须返回可转化为true的值。
严格模式下,如果代理对象有某些限制(属性不可写等),执行相应的拦截操作自然会报错。

apply
拦截函数的调用、callapply操作。

has
拦截判断对象是否具有某属性。
只对inReflect.has()方法有效,for in属于遍历系列。

construct
拦截new命令,必须返回一个对象。

deleteProperty
拦截delete属性操作。
严格模式下,必须返回可转化为true的值。
严格模式下,如果代理对象有某些限制(属性不可写等),执行相应的拦截操作自然会报错。

defineProperty
拦截Object.defineProperty(),不会拦截defineProperties
严格模式下,如果代理对象有某些限制(属性不可配置等),执行相应的拦截操作自然会报错。

getOwnPropertyDescriptor
拦截Object.getOwnPropertyDescriptor(),不会拦截getOwnPropertyDescriptors
必须返回一个对象或undefined,即返回与原方法相同的返回值,否则报错。

getPrototypeOf
拦截获取对象原型,必须返回一个对象或者null
如果对象不可扩展,必须返回真实的原型对象。
直接访问__proto__,通过各类方法等等都会触发拦截。

setPrototypeOf
拦截Object.setPrototypeOf()方法。

isExtensible
拦截Object.isExtensible()操作。
返回值必须与目标对象的isExtensible属性保持一致,否则会抛出错误。

preventExtensions
拦截Object.preventExtensions(),返回值会自动转成布尔值。

ownKeys
拦截对象自身属性的遍历操作。
比如keys(),getOwnPropertyNames(),getOwnPropertySymbols()等等。
返回值必须是数组,项只能是字符串或Symbol,否则报错。
返回值会根据调用方法的需求进行过滤,比如Object.keys()里不会有symbole
如果目标对象自身包含不可配置的属性,则该属性必须被返回,否则报错。
如果目标对象不可扩展,返回值必须包含原对象的所有属性,且不能包含多余的属性,否则报错。

1.4 Proxy.revocable

此静态方法也用于生成代理器对象的,但它还会返回一个回收代理器的方法。
使用场景:不允许直接访问目标对象,必须通过代理访问。而且一旦访问结束,就收回代理权,不允许再次访问。

let {proxy, revoke} = Proxy.revocable(obj, {});
revoke();
此时其内部属性值 [[IsRevoked]] 为 true,不能再操作代理器,否则报错。

2 Reflect

2.1 作用

最终目的是成为语言内部方法的宿主对象。
比如说defineProperty, getPrototypeOf, preventExtensions都很明显属于内部方法,不应挂在Object下。

提供替代命令式操作的相应函数。
使用Reflect.has(obj, attr)替代in操作
使用Reflect.deleteProperty(obj, attr)替代delete操作

使函数的行为更加完善和严格。
在无法定义属性时,Reflect.defineProperty返回false而不是抛出错误。
在要求类型是对象的参数为非对象时,会直接报错而不是调用Object()进行转化。

Porxy可拦截的方法对应,方便在实现自定义行为时,直接调用以完成默认行为。

2.2 静态方法

这里仅仅是罗列出某些重点,详细的请看手册

Reflect.get
Reflect.get(obj, attr, receiver)
返回属性值,没有则返回undefined
receiver是设置gettersetter里的this指向,默认指向obj

Reflect.set
Reflect.set(obj, attr, value, receiver)
设置属性值,返回布尔值。
注意:当该属性不是getter时,传入receiver等同于设置receiver对象上的属性值。

Reflect.has
Reflect.has(obj, attr)
等同attr in obj

Reflect.deleteProperty
Reflect.deleteProperty(obj, attr)
等同delete obj[attr]

Reflect.construct
Reflect.construct(func, args)
args等同于使用apply方法传入的参数数组。
提供了不使用new来调用构造函数的方法,等同new func(...args)

Reflect.getPrototypeOf
作用及参数等同Object.getPrototypeOf(obj)

Reflect.setPrototypeOf
作用及参数等同Object.setPrototypeOf(obj, newProto)

Reflect.apply
作用及参数等同Function.prototype.apply.call(func, thisArg, args)

Reflect.defineProperty
作用及参数等同Object.defineProperty(obj, attr, descriptor)

Reflect.getOwnPropertyDescriptor
作用及参数等同Object.getOwnPropertyDescriptor(obj, attr)

Reflect.isExtensible
Reflect.isExtensible(obj)
返回一个布尔值,表示当前对象是否可扩展。

Reflect.preventExtensions
Reflect.preventExtensions(obj)
设置对象为不可扩展,返回布尔值。

Reflect.ownKeys
Reflect.ownKeys(obj)
返回对象本身的所有属性。
等同于Object.getOwnPropertyNamesObject.getOwnPropertySymbols之和。

查看原文

赞 31 收藏 33 评论 3

learnfront 收藏了文章 · 2019-12-29

nginx配置location总结及rewrite规则写法

location正则写法

一个示例:

location  = / {
  # 精确匹配 / ,主机名后面不能带任何字符串
  [ configuration A ] 
}

location  / {
  # 因为所有的地址都以 / 开头,所以这条规则将匹配到所有请求
  # 但是正则和最长字符串会优先匹配
  [ configuration B ] 
}

location /documents/ {
  # 匹配任何以 /documents/ 开头的地址,匹配符合以后,还要继续往下搜索
  # 只有后面的正则表达式没有匹配到时,这一条才会采用这一条
  [ configuration C ] 
}

location ~ /documents/Abc {
  # 匹配任何以 /documents/ 开头的地址,匹配符合以后,还要继续往下搜索
  # 只有后面的正则表达式没有匹配到时,这一条才会采用这一条
  [ configuration CC ] 
}

location ^~ /images/ {
  # 匹配任何以 /images/ 开头的地址,匹配符合以后,停止往下搜索正则,采用这一条。
  [ configuration D ] 
}

location ~* \.(gif|jpg|jpeg)$ {
  # 匹配所有以 gif,jpg或jpeg 结尾的请求
  # 然而,所有请求 /images/ 下的图片会被 config D 处理,因为 ^~ 到达不了这一条正则
  [ configuration E ] 
}

location /images/ {
  # 字符匹配到 /images/,继续往下,会发现 ^~ 存在
  [ configuration F ] 
}

location /images/abc {
  # 最长字符匹配到 /images/abc,继续往下,会发现 ^~ 存在
  # F与G的放置顺序是没有关系的
  [ configuration G ] 
}

location ~ /images/abc/ {
  # 只有去掉 config D 才有效:先最长匹配 config G 开头的地址,继续往下搜索,匹配到这一条正则,采用
    [ configuration H ] 
}

location ~* /js/.*/\.js
  • =开头表示精确匹配
    如 A 中只匹配根目录结尾的请求,后面不能带任何字符串。
  • ^~ 开头表示uri以某个常规字符串开头,不是正则匹配
  • ~ 开头表示区分大小写的正则匹配;
  • ~* 开头表示不区分大小写的正则匹配
  • / 通用匹配, 如果没有其它匹配,任何请求都会匹配到

顺序 no优先级:
(location =) > (location 完整路径) > (location ^~ 路径) > (location ~,~* 正则顺序) > (location 部分起始路径) > (/)

上面的匹配结果
按照上面的location写法,以下的匹配示例成立:

  • / -> config A
    精确完全匹配,即使/index.html也匹配不了
  • /downloads/download.html -> config B
    匹配B以后,往下没有任何匹配,采用B
  • /images/1.gif -> configuration D
    匹配到F,往下匹配到D,停止往下
  • /images/abc/def -> config D
    最长匹配到G,往下匹配D,停止往下
    你可以看到 任何以/images/开头的都会匹配到D并停止,FG写在这里是没有任何意义的,H是永远轮不到的,这里只是为了说明匹配顺序
  • /documents/document.html -> config C
    匹配到C,往下没有任何匹配,采用C
  • /documents/1.jpg -> configuration E
    匹配到C,往下正则匹配到E
  • /documents/Abc.jpg -> config CC
    最长匹配到C,往下正则顺序匹配到CC,不会往下到E

实际使用建议

所以实际使用中,个人觉得至少有三个匹配规则定义,如下:
#直接匹配网站根,通过域名访问网站首页比较频繁,使用这个会加速处理,官网如是说。
#这里是直接转发给后端应用服务器了,也可以是一个静态首页
# 第一个必选规则
location = / {
    proxy_pass http://tomcat:8080/index
}
# 第二个必选规则是处理静态文件请求,这是nginx作为http服务器的强项
# 有两种配置模式,目录匹配或后缀匹配,任选其一或搭配使用
location ^~ /static/ {
    root /webroot/static/;
}
location ~* \.(gif|jpg|jpeg|png|css|js|ico)$ {
    root /webroot/res/;
}
#第三个规则就是通用规则,用来转发动态请求到后端应用服务器
#非静态文件请求就默认是动态请求,自己根据实际把握
#毕竟目前的一些框架的流行,带.php,.jsp后缀的情况很少了
location / {
    proxy_pass http://tomcat:8080/
}

http://tengine.taobao.org/book/chapter_02.html
http://nginx.org/en/docs/http/ngx_http_rewrite_module.html

Rewrite规则

rewrite功能就是,使用nginx提供的全局变量或自己设置的变量,结合正则表达式和标志位实现url重写以及重定向。rewrite只能放在server{},location{},if{}中,并且只能对域名后边的除去传递的参数外的字符串起作用,例如 http://seanlook.com/a/we/index.php?id=1&u=str 只对/a/we/index.php重写。语法rewrite regex replacement [flag];

如果相对域名或参数字符串起作用,可以使用全局变量匹配,也可以使用proxy_pass反向代理。

表明看rewrite和location功能有点像,都能实现跳转,主要区别在于rewrite是在同一域名内更改获取资源的路径,而location是对一类路径做控制访问或反向代理,可以proxy_pass到其他机器。很多情况下rewrite也会写在location里,它们的执行顺序是:

  1. 执行server块的rewrite指令
  2. 执行location匹配
  3. 执行选定的location中的rewrite指令

如果其中某步URI被重写,则重新循环执行1-3,直到找到真实存在的文件;循环超过10次,则返回500 Internal Server Error错误。

flag标志位

  • last : 相当于Apache的[L]标记,表示完成rewrite
  • break : 停止执行当前虚拟主机的后续rewrite指令集
  • redirect : 返回302临时重定向,地址栏会显示跳转后的地址
  • permanent : 返回301永久重定向,地址栏会显示跳转后的地址

因为301和302不能简单的只返回状态码,还必须有重定向的URL,这就是return指令无法返回301,302的原因了。这里 last 和 break 区别有点难以理解:

  1. last一般写在server和if中,而break一般使用在location中
  2. last不终止重写后的url匹配,即新的url会再从server走一遍匹配流程,而break终止重写后的匹配
  3. break和last都能组织继续执行后面的rewrite指令

if指令与全局变量

if判断指令
语法为if(condition){...},对给定的条件condition进行判断。如果为真,大括号内的rewrite指令将被执行,if条件(conditon)可以是如下任何内容:

  • 当表达式只是一个变量时,如果值为空或任何以0开头的字符串都会当做false
  • 直接比较变量和内容时,使用=!=
  • ~正则表达式匹配,~*不区分大小写的匹配,!~区分大小写的不匹配

-f!-f用来判断是否存在文件
-d!-d用来判断是否存在目录
-e!-e用来判断是否存在文件或目录
-x!-x用来判断文件是否可执行

例如:

if ($http_user_agent ~ MSIE) {
    rewrite ^(.*)$ /msie/$1 break;
} //如果UA包含"MSIE",rewrite请求到/msid/目录下

if ($http_cookie ~* "id=([^;]+)(?:;|$)") {
    set $id $1;
 } //如果cookie匹配正则,设置变量$id等于正则引用部分

if ($request_method = POST) {
    return 405;
} //如果提交方法为POST,则返回状态405(Method not allowed)。return不能返回301,302

if ($slow) {
    limit_rate 10k;
} //限速,$slow可以通过 set 指令设置

if (!-f $request_filename){
    break;
    proxy_pass  http://127.0.0.1; 
} //如果请求的文件名不存在,则反向代理到localhost 。这里的break也是停止rewrite检查

if ($args ~ post=140){
    rewrite ^ http://example.com/ permanent;
} //如果query string中包含"post=140",永久重定向到example.com

location ~* \.(gif|jpg|png|swf|flv)$ {
    valid_referers none blocked www.jefflei.com www.leizhenfang.com;
    if ($invalid_referer) {
        return 404;
    } //防盗链
}

全局变量
下面是可以用作if判断的全局变量

  • $args : #这个变量等于请求行中的参数,同$query_string
  • $content_length : 请求头中的Content-length字段。
  • $content_type : 请求头中的Content-Type字段。
  • $document_root : 当前请求在root指令中指定的值。
  • $host : 请求主机头字段,否则为服务器名称。
  • $http_user_agent : 客户端agent信息
  • $http_cookie : 客户端cookie信息
  • $limit_rate : 这个变量可以限制连接速率。
  • $request_method : 客户端请求的动作,通常为GET或POST。
  • $remote_addr : 客户端的IP地址。
  • $remote_port : 客户端的端口。
  • $remote_user : 已经经过Auth Basic Module验证的用户名。
  • $request_filename : 当前请求的文件路径,由root或alias指令与URI请求生成。
  • $scheme : HTTP方法(如http,https)。
  • $server_protocol : 请求使用的协议,通常是HTTP/1.0或HTTP/1.1。
  • $server_addr : 服务器地址,在完成一次系统调用后可以确定这个值。
  • $server_name : 服务器名称。
  • $server_port : 请求到达服务器的端口号。
  • $request_uri : 包含请求参数的原始URI,不包含主机名,如:”/foo/bar.php?arg=baz”。
  • $uri : 不带请求参数的当前URI,$uri不包含主机名,如”/foo/bar.html”。
  • $document_uri : 与$uri相同。

例:http://localhost:88/test1/test2/test.php
$host:localhost
$server_port:88
$request_uri:http://localhost:88/test1/test2/test.php
$document_uri:/test1/test2/test.php
$document_root:/var/www/html
$request_filename:/var/www/html/test1/test2/test.php

常用正则

  • . : 匹配除换行符以外的任意字符
  • ? : 重复0次或1次
  • + : 重复1次或更多次
  • * : 重复0次或更多次
  • \d :匹配数字
  • ^ : 匹配字符串的开始
  • $ : 匹配字符串的介绍
  • {n} : 重复n次
  • {n,} : 重复n次或更多次
  • [c] : 匹配单个字符c
  • [a-z] : 匹配a-z小写字母的任意一个

小括号()之间匹配的内容,可以在后面通过$1来引用,$2表示的是前面第二个()里的内容。正则里面容易让人困惑的是\转义特殊字符。

rewrite实例

例1

http {
    # 定义image日志格式
    log_format imagelog '[$time_local] ' $image_file ' ' $image_type ' ' $body_bytes_sent ' ' $status;
    # 开启重写日志
    rewrite_log on;

    server {
        root /home/www;

        location / {
                # 重写规则信息
                error_log logs/rewrite.log notice; 
                # 注意这里要用‘’单引号引起来,避免{}
                rewrite '^/images/([a-z]{2})/([a-z0-9]{5})/(.*)\.(png|jpg|gif)$' /data?file=$3.$4;
                # 注意不能在上面这条规则后面加上“last”参数,否则下面的set指令不会执行
                set $image_file $3;
                set $image_type $4;
        }

        location /data {
                # 指定针对图片的日志格式,来分析图片类型和大小
                access_log logs/images.log mian;
                root /data/images;
                # 应用前面定义的变量。判断首先文件在不在,不在再判断目录在不在,如果还不在就跳转到最后一个url里
                try_files /$arg_file /image404.html;
        }
        location = /image404.html {
                # 图片不存在返回特定的信息
                return 404 "image not found\n";
        }
}

对形如/images/ef/uh7b3/test.png的请求,重写到/data?file=test.png,于是匹配到location /data,先看/data/images/test.png文件存不存在,如果存在则正常响应,如果不存在则重写tryfiles到新的image404 location,直接返回404状态码。

例2

rewrite ^/images/(.*)_(\d+)x(\d+)\.(png|jpg|gif)$ /resizer/$1.$4?width=$2&height=$3? last;

对形如/images/bla_500x400.jpg的文件请求,重写到/resizer/bla.jpg?width=500&height=400地址,并会继续尝试匹配location。

例3
ssl部分页面加密

参考


原文链接地址:http://seanlook.com/2015/05/17/nginx-location-rewrite/


查看原文

learnfront 收藏了问题 · 2019-12-25

vscode 这样的注释怎么生成?

如下图中的 @param 这样的注释怎么用快捷键生成?
图片描述

learnfront 收藏了文章 · 2019-12-19

Nuxt.js实战和配置

前段时间刚好公司有项目使用了Nuxt.js来搭建,而刚好在公司内部做了个分享,稍微再整理一下发出来。本文比较适合初用Nuxt.js的同学,主要讲下搭建过程中做的一些配置。建议初次使用Nuxt.js的同学先过一遍官方文档,再回头看下我这篇文章。

一、为什么要用Nuxt.js

原因其实不用多说,就是利用Nuxt.js的服务端渲染能力来解决Vue项目的SEO问题。

二、Nuxt.js和纯Vue项目的简单对比

1. build后目标产物不同

vue: dist

nuxt: .nuxt

2. 网页渲染流程

vue: 客户端渲染,先下载js后,通过ajax来渲染页面;

nuxt: 服务端渲染,可以做到服务端拼接好html后直接返回,首屏可以做到无需发起ajax请求;

3. 部署流程

vue: 只需部署dist目录到服务器,没有服务端,需要用nginx等做Web服务器;

nuxt: 需要部署几乎所有文件到服务器(除node_modules,.git),自带服务端,需要pm2管理(部署时需要reload pm2),若要求用域名,则需要nginx做代理。

4. 项目入口

vue: /src/main.js,在main.js可以做一些全局注册的初始化工作;
nuxt: 没有main.js入口文件,项目初始化的操作需要通过nuxt.config.js进行配置指定。

三、从零搭建一个Nuxt.js项目并配置

新建一个项目

直接使用脚手架进行安装:

npx create-nuxt-app <项目名>

图片描述
大概选上面这些选项。

值得一说的是,关于Choose custom server framework(选择服务端框架),可以根据你的业务情况选择一个服务端框架,常见的就是Express、Koa,默认是None,即Nuxt默认服务器,我这里选了Express

  • 选择默认的Nuxt服务器,不会生成server文件夹,所有服务端渲染的操作都是Nuxt帮你完成,无需关心服务端的细节,开发体验更接近Vue项目,缺点是无法做一些服务端定制化的操作。
  • 选择其他的服务端框架,比如Express,会生成server文件夹,帮你搭建一个基本的Node服务端环境,可以在里面做一些node端的操作。比如我公司业务需要(解析protobuf)使用了Express,对真正的服务端api做一层转发,在node端解析protobuf后,返回json数据给客户端。

还有Choose Nuxt.js modules(选择nuxt.js的模块),可以选axiosPWA,如果选了axios,则会帮你在nuxt实例下注册$axios,让你可以在.vue文件中直接this.$axios发起请求。

开启eslint检查

nuxt.config.js的build属性下添加:

  build: {
    extend (config, ctx) {
      // Run ESLint on save
      if (ctx.isDev && ctx.isClient) {
        config.module.rules.push({
          enforce: 'pre',
          test: /\.(js|vue)$/,
          loader: 'eslint-loader',
          exclude: /(node_modules)/
        })
      }
    }
  }

这样开发时保存文件就可以检查语法了。nuxt默认使用的规则是@nuxtjs(底层来自eslint-config-standard),规则配置在/.eslintrc.js:

module.exports = {
  root: true,
  env: {
    browser: true,
    node: true
  },
  parserOptions: {
    parser: 'babel-eslint'
  },
  extends: [
    '@nuxtjs', // 该规则对应这个依赖: @nuxtjs/eslint-config
    'plugin:nuxt/recommended'
  ],
  // add your custom rules here
  rules: {
    'nuxt/no-cjs-in-config': 'off'
  }
}

如果不习惯用standard规则的团队可以将@nuxtjs改成其他的。

使用dotenv和@nuxtjs/dotenv统一管理环境变量

在node端,我们喜欢使用dotenv来管理项目中的环境变量,把所有环境变量都放在根目录下的.env中。

  • 安装:
npm i dotenv
  • 使用:
  1. 在根目录下新建一个.env文件,并写上需要管理的环境变量,比如服务端地址APIHOST:
APIHOST=http://your_server.com/api
  1. /server/index.js中使用(该文件是选Express服务端框架自动生成的):
require('dotenv').config()

// 通过process.env即可使用
console.log(process.env.APIHOST) // http://your_server.com/api

此时我们只是让服务端可以使用.env的文件而已,Nuxt客户端并不能使用.env,按Nuxt.js文档所说,可以将客户端的环境变量放置在nuxt.config.js中:

module.exports = {
  env: {
    baseUrl: process.env.BASE_URL || 'http://localhost:3000'
  }
}

但如果node端和客户端需要使用同一个环境变量时(后面讲到API鉴权时会使用同一个SECRET变量),就需要同时在nuxt.config.js.env维护这个字段,比较麻烦,我们更希望环境变量只需要在一个地方维护,所以为了解决这个问题,我找到了@nuxtjs/dotenv这个依赖,它使得nuxt的客户端也可以直接使用.env,达到了我们的预期。

  • 安装:
npm i @nuxtjs/dotenv

客户端也是通过process.env.XXX来使用,不再举例啦。

这样,我们通过dotenv@nuxtjs/dotenv这两个包,就可以统一管理开发环境中的变量啦。

另外,@nuxtjs/dotenv允许打包时指定其他的env文件。比如,开发时我们使用的是.env,但我们打包的线上版本想用其他的环境变量,此时可以指定build时用另一份文件如/.env.prod,只需在nuxt.config.js指定:

module.exports = {
    modules: [
    ['@nuxtjs/dotenv', { filename: '.env.prod' }] // 指定打包时使用的dotenv
  ],
}

@nuxtjs/toast模块

toast可以说是很常用的功能,一般的UI框架都会有这个功能。但如果你的站点没有使用UI框架,而alert又太丑,不妨引入该模块:

npm install @nuxtjs/toast

然后在nuxt.config.js中引入

module.exports = {
    modules: [
    '@nuxtjs/toast',
    ['@nuxtjs/dotenv', { filename: '.env.prod' }] // 指定打包时使用的dotenv
  ],
  toast: {// toast模块的配置
    position: 'top-center', 
    duration: 2000
  }
}

这样,nuxt就会在全局注册$toast方法供你使用,非常方便:

this.$toast.error('服务器开小差啦~~')
this.$toast.error('请求成功~~')

API鉴权

对于某些敏感的服务,我们可能需要对API进行鉴权,防止被人轻易盗用我们node端的API,因此我们需要做一个API的鉴权机制。常见的方案有jwt,可以参考一下阮老师的介绍:《JSON Web Token 入门教程》。如果场景比较简单,可以自行设计一下,这里提供一个思路:

  1. 客户端和node端在环境变量中声明一个秘钥:SECRET=xxxx,注意这个是保密的;
  2. 客户端发起请求时,将当前时间戳(timestamp)和SECRET通过某种算法,生成一个signature,请求时带上timestampsignature
  3. node接收到请求,获得timestampsignature,将timestamp和秘钥用同样的算法再生成一次签名_signature
  4. 对比客户端请求的signature和node用同样的算法生成的_signature,如果一致就表示通过,否则鉴权失败。

具体的步骤:

客户端对axios进行一层封装:

import axios from 'axios'
import sha256 from 'crypto-js/sha256'
import Base64 from 'crypto-js/enc-base64'
// 加密算法,需安装crypto-js
function crypto (str) {
  const _sign = sha256(str)
  return encodeURIComponent(Base64.stringify(_sign))
}

const SECRET = process.env.SECRET

const options = {
  headers: { 'X-Requested-With': 'XMLHttpRequest' },
  timeout: 30000,
  baseURL: '/api'
}

// The server-side needs a full url to works
if (process.server) {
  options.baseURL = `http://${process.env.HOST || 'localhost'}:${process.env.PORT || 3000}/api`
  options.withCredentials = true
}

const instance = axios.create(options)
// 对axios的每一个请求都做一个处理,携带上签名和timestamp
instance.interceptors.request.use(
  config => {
    const timestamp = new Date().getTime()
    const param = `timestamp=${timestamp}&secret=${SECRET}`
    const sign = crypto(param)
    config.params = Object.assign({}, config.params, { timestamp, sign })
    return config
  }
)

export default instance

接着,在server端写一个鉴权的中间件,/server/middleware/verify.js

const sha256 = require('crypto-js/sha256')
const Base64 = require('crypto-js/enc-base64')

function crypto (str) {
  const _sign = sha256(str)
  return encodeURIComponent(Base64.stringify(_sign))
}
// 使用和客户端相同的一个秘钥
const SECRET = process.env.SECRET

function verifyMiddleware (req, res, next) {
  const { sign, timestamp } = req.query
  // 加密算法与请求时的一致
  const _sign = crypto(`timestamp=${timestamp}&secret=${SECRET}`)
  if (_sign === sign) {
    next()
  } else {
    res.status(401).send({
      message: 'invalid token'
    })
  }
}

module.exports = { verifyMiddleware }

最后,在需要鉴权的路由中引用这个中间件, /server/index.js

const { Router } = require('express')
const { verifyMiddleware } = require('../middleware/verify.js')
const router = Router()

// 在需要鉴权的路由加上
router.get('/test', verifyMiddleware, function (req, res, next) {
    res.json({name: 'test'})
})

静态文件的处理

根目录下有个/static文件夹,我们希望这里面的文件可以直接通过url访问,需要在/server/index.js中加入一句:

const express = require('express')
const app = express()

app.use('/static', express.static('static'))

四、Nuxt开发相关

生命周期

Nuxt扩展了Vue的生命周期,大概如下:

export default {
  middleware () {}, //服务端
  validate () {}, // 服务端
  asyncData () {}, //服务端
  fetch () {}, // store数据加载
  beforeCreate () {  // 服务端和客户端都会执行},
  created () { // 服务端和客户端都会执行 },
  beforeMount () {}, 
  mounted () {} // 客户端
}

asyncData

该方法是Nuxt最大的一个卖点,服务端渲染的能力就在这里,首次渲染时务必使用该方法。
asyncData会传进一个context参数,通过该参数可以获得一些信息,如:

export default {
  asyncData (ctx) {
    ctx.app // 根实例
    ctx.route // 路由实例
    ctx.params  //路由参数
    ctx.query  // 路由问号后面的参数
    ctx.error   // 错误处理方法
  }
}

渲染出错和ajax请求出错的处理

  • asyncData渲染出错

使用asyncData钩子时可能会由于服务器错误或api错误导致无法渲染,此时页面还未渲染出来,需要针对这种情况做一些处理,当遇到asyncData错误时,跳转到错误页面,nuxt提供了context.error方法用于错误处理,在asyncData中调用该方法即可跳转到错误页面。

export default {
    async asyncData (ctx) {
        // 尽量使用try catch的写法,将所有异常都捕捉到
        try {
            throw new Error()
        } catch {
            ctx.error({statusCode: 500, message: '服务器开小差了~' })
        }
    }
}

这样,当出现异常时会跳转到默认的错误页,错误页面可以通过/layout/error.vue自定义。

这里会遇到一个问题,context.error的参数必须是类似{ statusCode: 500, message: '服务器开小差了~' }statusCode必须是http状态码,
而我们服务端返回的错误往往有一些其他的自定义代码,如{resultCode: 10005, resultInfo: '服务器内部错误' },此时需要对返回的api错误进行转换一下。

为了方便,我引入了/plugins/ctx-inject.js为context注册一个全局的错误处理方法: context.$errorHandler(err)。注入方法可以参考:注入 $root 和 contextctx-inject.js:

// 为context注册全局的错误处理事件
export default (ctx, inject) => {
  ctx.$errorHandler = err => {
    try {
      const res = err.data
      if (res) {
        // 由于nuxt的错误页面只能识别http的状态码,因此statusCode统一传500,表示服务器异常。
        ctx.error({ statusCode: 500, message: res.resultInfo })
      } else {
        ctx.error({ statusCode: 500, message: '服务器开小差了~' })
      }
    } catch {
      ctx.error({ statusCode: 500, message: '服务器开小差了~' })
    }
  }
}

然后在nuxt.config.js使用该插件:

export default {
  plugins: [
    '~/plugins/ctx-inject.js'
  ]
}

注入完毕,我们就可以在asyncData介个样子使用了:

export default {
    async asyncData (ctx) {
        // 尽量使用try catch的写法,将所有异常都捕捉到
        try {
            throw new Error()
        } catch(err) {
            ctx.$errorHandler(err)
        }
    }
}
  • ajax请求出错

对于ajax的异常,此时页面已经渲染,出现错误时不必跳转到错误页,可以通过this.$toast.error(res.message) toast出来即可。

loading方法

nuxt内置了页面顶部loading进度条的样式
推荐使用,提供页面跳转体验。
打开: this.$nuxt.$loading.start()
完成: this.$nuxt.$loading.finish()

打包部署

一般来说,部署前可以先在本地打包,本地跑一下确认无误后再上传到服务器部署。命令:

// 打包
npm run build
// 本地跑
npm start

除node_modules,.git,.env,将其他的文件都上传到服务器,然后通过pm2进行管理,可以在项目根目录建一个pm2.json方便维护:

{
  "name": "nuxt-test",
  "script": "./server/index.js",
  "instances": 2,
  "cwd": "."
}

然后配置生产环境的环境变量,一般是直接用.env.prod的配置:cp ./.env.prod ./.env
首次部署或有新的依赖包,需要在服务器上npm install一次,然后就可以用pm2启动进程啦:

// 项目根目录下运行
pm2 start ./pm2.json

需要的话,可以设置开机自动启动pm2: pm2 save && pm2 startup
需要注意的是,每次部署都得重启一下进程:pm2 reload nuxt-test

五、最后

Nuxt.js引入了Node,同时nuxt.config.js替代了main.js的一些作用,目录结构和vue项目都稍有不同,增加了很多的约定,对于初次接触的同学可能会觉得非常陌生,更多的内容还是得看一遍官方的文档。

demo源码: fengxianqi/front_end-demos/src/nuxt-test

查看原文

learnfront 赞了文章 · 2019-12-19

Nuxt.js实战和配置

前段时间刚好公司有项目使用了Nuxt.js来搭建,而刚好在公司内部做了个分享,稍微再整理一下发出来。本文比较适合初用Nuxt.js的同学,主要讲下搭建过程中做的一些配置。建议初次使用Nuxt.js的同学先过一遍官方文档,再回头看下我这篇文章。

一、为什么要用Nuxt.js

原因其实不用多说,就是利用Nuxt.js的服务端渲染能力来解决Vue项目的SEO问题。

二、Nuxt.js和纯Vue项目的简单对比

1. build后目标产物不同

vue: dist

nuxt: .nuxt

2. 网页渲染流程

vue: 客户端渲染,先下载js后,通过ajax来渲染页面;

nuxt: 服务端渲染,可以做到服务端拼接好html后直接返回,首屏可以做到无需发起ajax请求;

3. 部署流程

vue: 只需部署dist目录到服务器,没有服务端,需要用nginx等做Web服务器;

nuxt: 需要部署几乎所有文件到服务器(除node_modules,.git),自带服务端,需要pm2管理(部署时需要reload pm2),若要求用域名,则需要nginx做代理。

4. 项目入口

vue: /src/main.js,在main.js可以做一些全局注册的初始化工作;
nuxt: 没有main.js入口文件,项目初始化的操作需要通过nuxt.config.js进行配置指定。

三、从零搭建一个Nuxt.js项目并配置

新建一个项目

直接使用脚手架进行安装:

npx create-nuxt-app <项目名>

图片描述
大概选上面这些选项。

值得一说的是,关于Choose custom server framework(选择服务端框架),可以根据你的业务情况选择一个服务端框架,常见的就是Express、Koa,默认是None,即Nuxt默认服务器,我这里选了Express

  • 选择默认的Nuxt服务器,不会生成server文件夹,所有服务端渲染的操作都是Nuxt帮你完成,无需关心服务端的细节,开发体验更接近Vue项目,缺点是无法做一些服务端定制化的操作。
  • 选择其他的服务端框架,比如Express,会生成server文件夹,帮你搭建一个基本的Node服务端环境,可以在里面做一些node端的操作。比如我公司业务需要(解析protobuf)使用了Express,对真正的服务端api做一层转发,在node端解析protobuf后,返回json数据给客户端。

还有Choose Nuxt.js modules(选择nuxt.js的模块),可以选axiosPWA,如果选了axios,则会帮你在nuxt实例下注册$axios,让你可以在.vue文件中直接this.$axios发起请求。

开启eslint检查

nuxt.config.js的build属性下添加:

  build: {
    extend (config, ctx) {
      // Run ESLint on save
      if (ctx.isDev && ctx.isClient) {
        config.module.rules.push({
          enforce: 'pre',
          test: /\.(js|vue)$/,
          loader: 'eslint-loader',
          exclude: /(node_modules)/
        })
      }
    }
  }

这样开发时保存文件就可以检查语法了。nuxt默认使用的规则是@nuxtjs(底层来自eslint-config-standard),规则配置在/.eslintrc.js:

module.exports = {
  root: true,
  env: {
    browser: true,
    node: true
  },
  parserOptions: {
    parser: 'babel-eslint'
  },
  extends: [
    '@nuxtjs', // 该规则对应这个依赖: @nuxtjs/eslint-config
    'plugin:nuxt/recommended'
  ],
  // add your custom rules here
  rules: {
    'nuxt/no-cjs-in-config': 'off'
  }
}

如果不习惯用standard规则的团队可以将@nuxtjs改成其他的。

使用dotenv和@nuxtjs/dotenv统一管理环境变量

在node端,我们喜欢使用dotenv来管理项目中的环境变量,把所有环境变量都放在根目录下的.env中。

  • 安装:
npm i dotenv
  • 使用:
  1. 在根目录下新建一个.env文件,并写上需要管理的环境变量,比如服务端地址APIHOST:
APIHOST=http://your_server.com/api
  1. /server/index.js中使用(该文件是选Express服务端框架自动生成的):
require('dotenv').config()

// 通过process.env即可使用
console.log(process.env.APIHOST) // http://your_server.com/api

此时我们只是让服务端可以使用.env的文件而已,Nuxt客户端并不能使用.env,按Nuxt.js文档所说,可以将客户端的环境变量放置在nuxt.config.js中:

module.exports = {
  env: {
    baseUrl: process.env.BASE_URL || 'http://localhost:3000'
  }
}

但如果node端和客户端需要使用同一个环境变量时(后面讲到API鉴权时会使用同一个SECRET变量),就需要同时在nuxt.config.js.env维护这个字段,比较麻烦,我们更希望环境变量只需要在一个地方维护,所以为了解决这个问题,我找到了@nuxtjs/dotenv这个依赖,它使得nuxt的客户端也可以直接使用.env,达到了我们的预期。

  • 安装:
npm i @nuxtjs/dotenv

客户端也是通过process.env.XXX来使用,不再举例啦。

这样,我们通过dotenv@nuxtjs/dotenv这两个包,就可以统一管理开发环境中的变量啦。

另外,@nuxtjs/dotenv允许打包时指定其他的env文件。比如,开发时我们使用的是.env,但我们打包的线上版本想用其他的环境变量,此时可以指定build时用另一份文件如/.env.prod,只需在nuxt.config.js指定:

module.exports = {
    modules: [
    ['@nuxtjs/dotenv', { filename: '.env.prod' }] // 指定打包时使用的dotenv
  ],
}

@nuxtjs/toast模块

toast可以说是很常用的功能,一般的UI框架都会有这个功能。但如果你的站点没有使用UI框架,而alert又太丑,不妨引入该模块:

npm install @nuxtjs/toast

然后在nuxt.config.js中引入

module.exports = {
    modules: [
    '@nuxtjs/toast',
    ['@nuxtjs/dotenv', { filename: '.env.prod' }] // 指定打包时使用的dotenv
  ],
  toast: {// toast模块的配置
    position: 'top-center', 
    duration: 2000
  }
}

这样,nuxt就会在全局注册$toast方法供你使用,非常方便:

this.$toast.error('服务器开小差啦~~')
this.$toast.error('请求成功~~')

API鉴权

对于某些敏感的服务,我们可能需要对API进行鉴权,防止被人轻易盗用我们node端的API,因此我们需要做一个API的鉴权机制。常见的方案有jwt,可以参考一下阮老师的介绍:《JSON Web Token 入门教程》。如果场景比较简单,可以自行设计一下,这里提供一个思路:

  1. 客户端和node端在环境变量中声明一个秘钥:SECRET=xxxx,注意这个是保密的;
  2. 客户端发起请求时,将当前时间戳(timestamp)和SECRET通过某种算法,生成一个signature,请求时带上timestampsignature
  3. node接收到请求,获得timestampsignature,将timestamp和秘钥用同样的算法再生成一次签名_signature
  4. 对比客户端请求的signature和node用同样的算法生成的_signature,如果一致就表示通过,否则鉴权失败。

具体的步骤:

客户端对axios进行一层封装:

import axios from 'axios'
import sha256 from 'crypto-js/sha256'
import Base64 from 'crypto-js/enc-base64'
// 加密算法,需安装crypto-js
function crypto (str) {
  const _sign = sha256(str)
  return encodeURIComponent(Base64.stringify(_sign))
}

const SECRET = process.env.SECRET

const options = {
  headers: { 'X-Requested-With': 'XMLHttpRequest' },
  timeout: 30000,
  baseURL: '/api'
}

// The server-side needs a full url to works
if (process.server) {
  options.baseURL = `http://${process.env.HOST || 'localhost'}:${process.env.PORT || 3000}/api`
  options.withCredentials = true
}

const instance = axios.create(options)
// 对axios的每一个请求都做一个处理,携带上签名和timestamp
instance.interceptors.request.use(
  config => {
    const timestamp = new Date().getTime()
    const param = `timestamp=${timestamp}&secret=${SECRET}`
    const sign = crypto(param)
    config.params = Object.assign({}, config.params, { timestamp, sign })
    return config
  }
)

export default instance

接着,在server端写一个鉴权的中间件,/server/middleware/verify.js

const sha256 = require('crypto-js/sha256')
const Base64 = require('crypto-js/enc-base64')

function crypto (str) {
  const _sign = sha256(str)
  return encodeURIComponent(Base64.stringify(_sign))
}
// 使用和客户端相同的一个秘钥
const SECRET = process.env.SECRET

function verifyMiddleware (req, res, next) {
  const { sign, timestamp } = req.query
  // 加密算法与请求时的一致
  const _sign = crypto(`timestamp=${timestamp}&secret=${SECRET}`)
  if (_sign === sign) {
    next()
  } else {
    res.status(401).send({
      message: 'invalid token'
    })
  }
}

module.exports = { verifyMiddleware }

最后,在需要鉴权的路由中引用这个中间件, /server/index.js

const { Router } = require('express')
const { verifyMiddleware } = require('../middleware/verify.js')
const router = Router()

// 在需要鉴权的路由加上
router.get('/test', verifyMiddleware, function (req, res, next) {
    res.json({name: 'test'})
})

静态文件的处理

根目录下有个/static文件夹,我们希望这里面的文件可以直接通过url访问,需要在/server/index.js中加入一句:

const express = require('express')
const app = express()

app.use('/static', express.static('static'))

四、Nuxt开发相关

生命周期

Nuxt扩展了Vue的生命周期,大概如下:

export default {
  middleware () {}, //服务端
  validate () {}, // 服务端
  asyncData () {}, //服务端
  fetch () {}, // store数据加载
  beforeCreate () {  // 服务端和客户端都会执行},
  created () { // 服务端和客户端都会执行 },
  beforeMount () {}, 
  mounted () {} // 客户端
}

asyncData

该方法是Nuxt最大的一个卖点,服务端渲染的能力就在这里,首次渲染时务必使用该方法。
asyncData会传进一个context参数,通过该参数可以获得一些信息,如:

export default {
  asyncData (ctx) {
    ctx.app // 根实例
    ctx.route // 路由实例
    ctx.params  //路由参数
    ctx.query  // 路由问号后面的参数
    ctx.error   // 错误处理方法
  }
}

渲染出错和ajax请求出错的处理

  • asyncData渲染出错

使用asyncData钩子时可能会由于服务器错误或api错误导致无法渲染,此时页面还未渲染出来,需要针对这种情况做一些处理,当遇到asyncData错误时,跳转到错误页面,nuxt提供了context.error方法用于错误处理,在asyncData中调用该方法即可跳转到错误页面。

export default {
    async asyncData (ctx) {
        // 尽量使用try catch的写法,将所有异常都捕捉到
        try {
            throw new Error()
        } catch {
            ctx.error({statusCode: 500, message: '服务器开小差了~' })
        }
    }
}

这样,当出现异常时会跳转到默认的错误页,错误页面可以通过/layout/error.vue自定义。

这里会遇到一个问题,context.error的参数必须是类似{ statusCode: 500, message: '服务器开小差了~' }statusCode必须是http状态码,
而我们服务端返回的错误往往有一些其他的自定义代码,如{resultCode: 10005, resultInfo: '服务器内部错误' },此时需要对返回的api错误进行转换一下。

为了方便,我引入了/plugins/ctx-inject.js为context注册一个全局的错误处理方法: context.$errorHandler(err)。注入方法可以参考:注入 $root 和 contextctx-inject.js:

// 为context注册全局的错误处理事件
export default (ctx, inject) => {
  ctx.$errorHandler = err => {
    try {
      const res = err.data
      if (res) {
        // 由于nuxt的错误页面只能识别http的状态码,因此statusCode统一传500,表示服务器异常。
        ctx.error({ statusCode: 500, message: res.resultInfo })
      } else {
        ctx.error({ statusCode: 500, message: '服务器开小差了~' })
      }
    } catch {
      ctx.error({ statusCode: 500, message: '服务器开小差了~' })
    }
  }
}

然后在nuxt.config.js使用该插件:

export default {
  plugins: [
    '~/plugins/ctx-inject.js'
  ]
}

注入完毕,我们就可以在asyncData介个样子使用了:

export default {
    async asyncData (ctx) {
        // 尽量使用try catch的写法,将所有异常都捕捉到
        try {
            throw new Error()
        } catch(err) {
            ctx.$errorHandler(err)
        }
    }
}
  • ajax请求出错

对于ajax的异常,此时页面已经渲染,出现错误时不必跳转到错误页,可以通过this.$toast.error(res.message) toast出来即可。

loading方法

nuxt内置了页面顶部loading进度条的样式
推荐使用,提供页面跳转体验。
打开: this.$nuxt.$loading.start()
完成: this.$nuxt.$loading.finish()

打包部署

一般来说,部署前可以先在本地打包,本地跑一下确认无误后再上传到服务器部署。命令:

// 打包
npm run build
// 本地跑
npm start

除node_modules,.git,.env,将其他的文件都上传到服务器,然后通过pm2进行管理,可以在项目根目录建一个pm2.json方便维护:

{
  "name": "nuxt-test",
  "script": "./server/index.js",
  "instances": 2,
  "cwd": "."
}

然后配置生产环境的环境变量,一般是直接用.env.prod的配置:cp ./.env.prod ./.env
首次部署或有新的依赖包,需要在服务器上npm install一次,然后就可以用pm2启动进程啦:

// 项目根目录下运行
pm2 start ./pm2.json

需要的话,可以设置开机自动启动pm2: pm2 save && pm2 startup
需要注意的是,每次部署都得重启一下进程:pm2 reload nuxt-test

五、最后

Nuxt.js引入了Node,同时nuxt.config.js替代了main.js的一些作用,目录结构和vue项目都稍有不同,增加了很多的约定,对于初次接触的同学可能会觉得非常陌生,更多的内容还是得看一遍官方的文档。

demo源码: fengxianqi/front_end-demos/src/nuxt-test

查看原文

赞 20 收藏 14 评论 9

认证与成就

  • 获得 1 次点赞
  • 获得 5 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 4 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-09-11
个人主页被 189 人浏览