ES6读书笔记 | Node.js | *.js

1.ES6特性

1.1 import 和 require 区别

require是执行函数然后赋值给左边的过程, 无论 module.exports 赋值的是匿名函数 | 函数 | Object,require 都会把它直接获取到 , 该函数没有考虑 default

而import 是编译时的解构过程,必须放在文件开头,
export不允许接匿名函数,即禁止a = ()=>1 || {sth:sth}; export aexport function(){},可替换为 export {a}export function a(){},
它区分default 和非 default :

不同的export 对应的 import 方式 结果
export default {a,b} import a from './xx' log : {a:a,b:b}
export {a}; export {b}; import {a,b} from './xx' a,b 是两个不同的变量, 解构了
import * as a from './xx' { a: [Function: a],
b: [Function: b],
default:
{ a: [Function: a],
b: [Function: b] } }

require 输出的是一个值的复制,而 import 输出的是值的只读引用,类似于Unix的符号链接,会随着原始值变化

循环加载时二者的表现不同,require 模块遇到循环加载时返回的时当前已经执行的部分的值,而import 因为返回的是值的引用,只要引用存在就能执行.

1.2 正则表达式

/a+/yes6中新增加了y作为粘连(sticky)修饰符, 和 g 一样都是全局匹配, 但 y 要求每次匹配都都从上一次匹配成功的紧挨着的位置开始

1.2.1 具名组匹配

var e = /(?<year>\d{4})-(?<month>\d{2})/
var a = e.exec('2019-01'))
console.log(a.groups) // { year:"2019", month:"01" }
//也可以解构赋值
let { groups:{ year, month} } = a
//字符串替换时, 使用 $<组名> 来引用具名组
'2019-02'.replace(e, '$<month>-$<year>') // 02-2019
//正则表达式内部引用具名组匹配, 下面两个式子等价
; /(\w+)!(\1)/.exec('abc!abc') // ["abc!abc", "abc", "abc", index: 0, input: "abc!abc", groups: undefined]
; /(?<name>\w+)!(\k<name>)/.exec('abc!abc') //["abc!abc", "abc", "abc", index: 0, input: "abc!abc", groups: {…}]
//由上式可知, js 的正则可以匹配如上述的上下文相关文法了

node8不支持. 但 chrome 和 node10 已支持
实在有想法的人也可以用正则表达式匹配回文字符串, 但这样匹配实质上等于/(.)(.)(.)\3\2\1/, 也就是说对字符串长度有要求, 且可读性较差, 不如str.split('').reverse().join('') === str

1.3 Math 数学运算扩展

  • 8**2 == 64 加入了指数运算符
  • Math.sinh(x)等 加入了双曲函数方法

1.4 特殊的条件表达式

Object.is 和 ===的区别:

+0 === -1 //true
NaN === NaN // false
typeof null === "object" // true

Object.is(+0,-0) // false
Object.is(NaN, NaN) // true

1.5 Symbol

  • Symbol 是 js 第7种数据类型, 它是一种类似于字符串的原始类型, 不是对象, 不能添加属性.
  • Symbol的出发点是用于表示独一无二的值, 常用在 Object 的 key 值. 因此Symbol('123') !== Symbol('123')
  • Symbol值不能和其它类型的值进行运算
  • Symbol值可以显示转化为字符串String(Symbol('1'))或布尔 Boolean(Symbol()) , 但不能转化为数值
  • Symbol.for('foo')全局搜索以该字符串作为名称的 Symbol 值, 反向的有Symbol.keyFor(sym)
  • Symbol.toStringTag , 这个属性可以用于定制[object Object][object Array]中 object 后面的字符串

1.6 Set

  • Set 的创建 new Set([1,2,3]);new Set(1,2,3)不行,因为第一个参数必须部署了iterable接口
  • Set 的属性: 数组的arr.length 而set 有set.size
  • Set 的方法:
    • add delete has clear forEach
    • map 和 filter
      let set = new Set([1,2,3])
      set = new Set([...set].map(x => x*2) //或 filter(x=>x%2==0)
      
  • Set 的应用:
    • 数组去重
       let unique = [...new Set([1,2,2,3,3,4])] 
      
    • 集合求 并集
    let union = new Set([...a,...b])
    
    • 集合求 交集
    let intersect = new Set([...a]).filter(x=>b.has(x))
    
    • 集合求 差集
    let difference = new Set([...a]).filter(x=> !b.has(x))
    

1.8 Map

  • Map 构造 new Map([ ['key','value'], ['key2','value'] ]) 要求输入参数具有 Iterator 接口并且每个成员都是一个双元素数组
  • Map 属性 size
  • Map 方法 set get has delete clear forEach
  • Map 和 Set 都有的方法keys() values() entries(), 都返回遍历器对象, 同时对 map 和 set 使用 Object.keys 不会得到想要的结果

1.8.1 WeakMap 典型应用:为 DOM 注册事件的 listener

let myWeakMap = new WeakMap()
let el = document.getElementById('logo')
myWeakMap.set(el, { timesClicked : 0 } )
el.onclick = function(){
  myWeakMap.get(el).timesClicked++
}

这样的好处是当该 DOM 被删除时,对应的计数器变量也能被释放内存

附: 主动触发事件:

var click = new MouseEvent('click')
el.dispatchEvent(click)

1.9 Proxy

1.9.1 Proxy 可以拦截的操作

  • get ,set, has(拦截propKey in proxy的操作), deleteProperty(拦截 delete target[key])
  • ownKeys(只有 Object.keys()返回对象的可遍历属性,其它接口都返回全部属性,包括:
    • Object.getOwnPropertyNames()
    • Object.getOwnPropertySymbols()
    • Reflect.ownKeys()
  • apply(target,object,args) 拦截函数调用操作,包括f(),f.call(o,...args),f.apply()
  • construct 拦截 new 操作

1.9.2 proxy和Object.defineProperties区别

  • proxy创建了一个新的对象
  • proxy对this的处理 在Proxy 代理的情况下,目标对象内部的 this 会指向 Proxy 代理
  • proxy支持的属性更多
    答:后者定义get set writeable value enumerable configurable 而 Proxy 和后者的交集只有 get 和 set
  • proxy 定义 get 和 set 时的propKey可以是参数, 而后者必须是一个指定的字符串, 参数也比较少(set : function (newValue) { })

1.9.3 Proxy应用

  • P238 拦截数组读取负数索引(defineProperty 无法实现)
function proxyArray(target) {
  let handler = {
    get(target, propKey, receiver) {
      let index = Number(propKey);
      if (index < 0) {
        propKey = String(target.length + index);
      }
      return Reflect.get(target, propKey, receiver);
    },
    set(target,propKey,value){
      let index = Number(propKey);
      if (index < 0) {
        propKey = String(target.length + index);
      }
      return target[propKey] = value
    }
  };
 return new Proxy(target, handler);
}

let arr = proxyArray([1,2,3]);
arr[-1] // 3

1.10 Reflect 能否还原proxy或defineP的get和set?

答: 不能

1.11 Promise

1.11.1 Promise.all()

var p = Promise.all([p1,p2,p3]) 处理逻辑类似于&&运算, 只有p1 p2 p3都变成 Fulfilled.
相对的,Promise.race()就类似于或运算

1.12 Iterator

1.12.1 关键词

it.next() // { value : '', done : false }
一般部署了 next 接口的对象就可以视作 Iterator
部署了[Symbol.iterator]接口的对象就可以被遍历

1.12.2 Object.keys和values和Iterator有关吗?for in呢?说到底,哪些有关,哪些无关?

  • o[Symbol.iterator]有关: [...o],for of,yield* [1,2],Array.from()
  • 原生[Symbol.iterator]有关:
    o.keys(),o.values(),o.entries()
  • 无关: for in,Object.keys(a),Object.values(a),Object.entries(a)

测试代码

var a = [1,2,3]
a[Symbol.iterator] = ()=>{ return { next: ()=>{ return {value:1,done:Math.random()>0.3}} } }
[...a] // 可能为[], [1], [1,1]

1.12.3 实现类python的range函数

优点: 相对创建数组来说更节省内存

  • 原生 Iterator 实现 next 的方法:
function range(start,stop){
  var o = { value:start,stop:stop }
  o[Symbol.iterator] = function(){ 
    return {
      next:()=>{
        if(this.value < this.stop){ 
          return { done:false, value:this.value++ }
        }else { return { done:true } }
      }
    }
  }
  return o
}
for(let i of range(0,5)){
  console.log(i) // 0,1,2,3,4
}
  • 使用 Generator 的话更简洁:
function range(start,stop){
  return {
    *[Symbol.iterator](){
      for(let i=start;i<stop;i++){ yield i }
    }
  }
}
//或
function *range(start,stop){
      for(let i=start;i<stop;i++){ yield i }
}
[...range(0,5)] //[0,1,2,3,4]

1.12.4 为 Object 添加类似 map 的遍历接口

Object.prototype[Symbol.iterator] = function * () {
        for(let i in this){ 
            yield [i,this[i]] 
        }
}
var o = { 'Li': { age:23 }, 'Wang' : { age:24 } } ;
[...o].filter(x=>x[1].age<=23) // [['Li',{age:23}]]

1.13 Generator 和 Iterator 的关系

Generator 函数就是 Iterator 遍历器生成函数, Generator 函数执行后, 返回一个遍历器对象,该对象本身也具有[Symbol.iterator]属性,执行后返回自身

next()方法的参数会作为上一条 yield 语句的返回值而存在

1.13.1 fibonacci

function * fibonacci(){
  let [prev,curr] = [0,1]
  while(1) { 
    [prev,curr] = [curr, prev+curr]
    yield prev
  }
}
for(let n of fibonacci()) { 
  if(n>10) break; 
  console.log(n); // 1,1,2,3,5,8
}

1.14 async await

一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,另外它内置了执行器: spawn 函数(不用使用co模块了)

2. 函数节流throttle 和函数去抖 debounce

参考掘金 知乎专栏

定义:

css-tricks.com

实现:

function throttle(fn, interval = 300) {
    let canRun = true;
    return function () {
        if (!canRun) return;
        canRun = false;
        setTimeout(() => {
            fn.apply(this, arguments);
            canRun = true;
        }, interval);
    };
}

function debounce(fn, interval = 300) {
    let timeout = null;
    return function () {
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            fn.apply(this, arguments);
        }, interval);
    };
}

如果设置16.7ms 的话,更流畅的方法:
requestAnimationFrame()

The window.requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint. The method takes a callback as an argument to be invoked before the repaint.
可以将它看做一个钩子,刚好卡在浏览器重绘前向我们的操作伸出橄榄枝。

函数 curry 化

Function.prototype.curry = function(){
  var args = arguments, that = this
  return function( ){
    return that.call(this, ...args, ...arguments)
  }
}

使用 js 添加 css 样式表

1. 在head中插入新的link

    /**
    >在<head>中使用<link>标签引入一个外部样式文件
    @param {string} url - css文件的链接
    */
    function includeLinkStyle(url) {
        var link = document.createElement("link");
        link.rel = "stylesheet";
        link.type = "text/css";
        link.href = url;
        document.getElementsByTagName("head")[0].appendChild(link);
    }

缺点: 会被简书屏蔽掉. 原因参考Content Security Policy 入门教程

2. addStyle

    var nums = document.styleSheets.length;
    var position = document.styleSheets[nums - 1].cssRules.length
    /**
    >添加一条 css 规则
    @param {string} str - css样式
    */
    function addStyle(str) {
        document.styleSheets[nums - 1].insertRule(str, position++);
    }

要求 Content-Security-Policy 的 script 相关规则如下:content-security-policy: script-src 'self' 'unsafe-inline' 'unsafe-eval'

  • 允许执行页面内嵌的<script>标签和事件监听函数
  • 允许将字符串当作代码执行,比如使用eval、setTimeout、setInterval和Function等函数

3. addStyle 封装

/**
>增加 sidebar 需要的全部样式
@param {string} highlightColor - 高亮颜色, 默认为'#c7254e'
*/
function addAllStyle(highlightColor) {
    highlightColor = highlightColor || "#c7254e"
    var sheet = newStyleSheet()
    /**
    >创建一个新的`<style></style>`标签插入`<head>`中
    @return {Object} style.sheet,`它具有方法insertRule`
    */
    function newStyleSheet() {
        var style = document.createElement("style");
        // 对WebKit hack :(
        style.appendChild(document.createTextNode(""));
        // 将 <style> 元素加到页面中
        document.head.appendChild(style);
        return style.sheet;
    }
    var position = 0
    /**
    >添加一条 css 规则
    @param {string} str - css样式,也可以是@media
    */
    function addStyle(str) {
        sheet.insertRule(str,position++);
    }
    addStyle(`@media only screen and (max-width : 1300px){
        .content-with-sidebar {
            margin-left:310px !important;
        }
    }`)
}

为什么 js 中 0.1+0.2 != 0.3

十进制整数转二进制方法:除2取余;十进制小数转二进制方法:乘2除整
十进制0.1转换成二进制,乘2取整过程:

0.1 * 2 = 0.2 # 0
0.2 * 2 = 0.4 # 0
0.4 * 2 = 0.8 # 0
0.8 * 2 = 1.6 # 1
0.6 * 2 = 1.2 # 1
0.2 * 2 = 0.4 # 0

.....

从上面可以看出,0.1的二进制格式是:0.0001100011....。这是一个二进制无限循环小数,但计算机内存有限,我们不能用储存所有的小数位数, 所以0.1 + 0.2 == 0.30000000000000004

C++中也是如此,printf("%20.20lf",0.1+0.2);得到0.30000000000000004441, 0.1+0.2 <= 0.3 //false"

js 实现 new 操作符

1. 最基本的两个功能, this绑定成员和 prototype 继承

function New(f){
  var a = { }
  f.call(a)
  a.__proto__ = f.prototype
  return a
}

这两行对应原文中的小结2(this 绑定)小结3(proto链接), 另外, 小结1(constructor绑定)已经被a.__proto__的赋值操作顺带完成了

2. 在1中未覆盖到的new的细节

如果输入函数f没有返回值, 那么new f()会和上述的一样返回构建完成的Object, 但是考虑到函数f可能本身自带返回值, 例如:

function f(name){
  this.name  = name
  if(Math.random() > 0.5)
    return name
  else 
    return { text:'I am Object' }
}

实际的情况正如原文中的小结4所述, - 若函数返回基本类型, 则new f()时会将生成好的对象返回

  • 若函数返回一个Object(包含Functoin, Array, Date,RegExg,Error),那么new表达式会尊重原函数的返回结果, 仍然返回这个Object

3. 总结上述两点,再额外加上处理输入参数&new.target

function New(f){
    if(typeof f !== 'function'){
      throw 'New function the first param must be a function';
    }
    New.target = f;
    New.target.name = f.name
    //Object.create 是 newObj.__proto__ = f.prototype 的另一种写法,原文说__proto__是浏览器实现的, 但实测node8也能用
    var newObj = Object.create(f.prototype);
    var argsArr = [].slice.call(arguments, 1);
    var o = f.apply(newObj, argsArr)
    New.target = undefined

    var isObject = typeof o === 'object' && o !== null;
    var isFunction = typeof o === 'function';
    if(isObject || isFunction){
        return o;
    }
    // 如果函数没有返回`Object`那么`new`表达式中的函数调用会自动返回这个新的对象。
    return newObj;
}

深度比较两个 Object 并输出比较报告

function deepCompare(a, b) {
  var report = []
  compare(a, b)
  console.log(report)
  function compare(a, b, path) {
    path = path || ""
    if (path.length > 50 || !a || !b) return
    if (typeof a != typeof b) {
      report.push(`a${path} == ${a} , b${path} == ${b}`)
      return
    }
    else if (typeof a == "object" && typeof b == "object") {
      for (var i in a) {
        if (a.hasOwnProperty && b.hasOwnProperty && a.hasOwnProperty(i) && !b.hasOwnProperty(i))
          report.push(`a${path}.${i} √ , b${path}[${i}] x`)
        else
          compare(a[i], b[i], path + "." + i)
      }
      for (var i in b) {
        if (!a[i] && b[i])
          report.push(`a${path}[${i}] x , b${path}[${i}] √`)
      }
    }
    else if ( a!=b ) {
      report.push(`a${path} == ${a} , b${path} == ${b}`)
    }
  }
}

indexedDB

// 读取
var database
var request = indexedDB.open('threejs-editor', 1)
request.onsuccess = e => database = e.target.result

var transaction = database.transaction(['states'], 'readwrite')
var objectStore = transaction.objectStore('states')
var request = objectStore.get(0)
request.onsuccess = e => console.log(e.target.result)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容