月度存档: 四月 2018

《ES6 标准入门》 阅读笔记

第三章

    1.var {foo:baz} = {foo:"aaa", bar:"bbb"}   //bas = bbb, foo = undefined

    对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者

    

    2.嵌套赋值的例子,平时开发经常会用到

    let obj = {};

    let arr = [];

    ({foo:obj.prop, bar:arr[0]}) = {foo:123, bar:true};

    obj //{prop:123}

    arr //[true]

    3.只要等号右边的值不是对象,就先将其转为对象。

    let {toString:s} = 123;

    s === Number.prototype.toString //true

    

    let {toString:s} = true

    s === Boolean.prototype.toString //true

    4. 交换变量

    es6 :[x, y] = [y, x];

    想到一种es5的写法 x = [y, y = x][0]

第五章

    1.正则表达式的  y修饰符,y修饰符隐含了头部匹配的标志

    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

    var s = "aaa_aa_a";

    var r = /a+_/y   //换一下表达式

    r.exec(s) //["aaa_"]

    r.exec(s) //["aa_"]

第七章

    1.Array.from() 作用是把类数组的对象转化为真正的数组, 适用于(arguments对象,NodeList,Iterable对象,Set,Map)

    let arr = {'0':'a', '1':'b', '2':c, length:3};

    ES5:

    var arr1 = [].slice.call(arr);

    ES6

    let arr2 = Array.from(arr);

    2.Array.includes()

    [1,2,3].includes(2) //true 方法属于es7

    3.数组推导,可以在[]内用循环创建数组

    var years = [1,2,3,4,5,6,7];

    [for (year of years) if(year>3)]   //[4,5,6] 

第八章

    1.

    function m1({x = 0, y = 0}){

        return [x, y];

    }

    function m2({x, y} = {x:0, y:0}){

        return [x,y];

    }

    

    m1({x:3}) //[3, 0]

    m2({x:3}) //[3, undefined]     //如果有参数,则不取默认参数,然后对{x,y}赋值

    m2() // [0,0]    //如果没有参数,取默认参数

    

    2.函数的length属性 如果指定了默认值后,length 属性将失真

     (function(a){}).length //1

    (function(a=5){}).length //0

    (function(a, b, v = 5){}).length //2

    3. 函数的length属性不包括rest参数

    (function(a) {}).length //1

    (function(…a){}).length //0

    (function(a, …b){}).length //1

    4.  任何类似数组的对象都可以用扩展运算符转为真正的数组(Map,Set,Ceberatir也可以)

    var nodeList = document.querySelectorAll('div');

    var array = […nodeList];

    5. ()=>{console.log(arugments)} 箭头函数函数体内没有arguments对象

    6.箭头函数的管道机制,即前一个函数的输出是后一个函数的输入

    var s = (a)=> b => a * b

    s(2)(3) //6

    7 尾递归可以极大节省内存

       尾递归的实现往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量都改写成函数的参数。

        function factorial(n, total==1){

            if(n===1) return total;

            return tailFactorial(n – 1, n * total);

        }

        factorial(5);

     8 柯里化 的意思是将对参数的函数转换成单参数的形式。

第九章

    1.Object.is   跟全等号===类似

    不同之处有两个

    Object.is(+0, -0); //false

    Object.is(NaN, NaN)  //true

    

    2.克隆对象

    function clone(origin) {

        return Object.assign({}, origin);

    }

     上面的代码将原始对象复制到一个空对象,就得到了原始对象的克隆。

    不过,采用这种克隆,只能克隆原始对象自身的值,不能克隆它的继承值。如果想要保持继承链,如下:

    function clone(origin){

        let originProto = Object.getPrototypeOf(origin);

        return Object.assign(Object.create(originProto), origin);

    }

    3. 属性的遍历

    for…in  //遍历包括自身和继承的可枚举属性

    Object.keys(obj) //只遍历自身的可枚举属性

    Object.getOwnPropertyNames(obj) //遍历自身和继承的属性包括不可枚举的属性

    Object.getOwnProertySymbols(obj) //返回自身所有的Symbol属性 

    Refect.ownKeys(); 

    Refect.enumerate()

    以上的遍历排序按一下规则:

    1.数字优先,按数字小到大排序

    2.其实是字符串,按生成时间排序

    3.最后是Symbol的值,按生成时间排序

    Object.keys({[Symbol():0, b:0, 10:0, 2:0, a:0]})

    //['2', '10', 'b', 'a', Symbol()]

    4.Object.setPrototypeOf 

    可以修改实例的原型链,如 

    let p = {a:1}

    function d(){};

    d.protype = p

    

    let a = new d();

    let c = new d();

    Object.setPrototypeOf(a, {b:1})

    a.b = 1;

    a.a = undefined;

    注意:c.prototype也会受到牵连

    5.如果扩展运算符的参数是null 或者 undefined,则会忽略不会报错

    let emptyObject = {…null, …undefined} // {} ,不会报错 

第十章

    1. Symbol是第7种数据类型

    2.Symbol属性不会出现在for…in,for…of,Object.keys()等循环中,只有getOwnPropertySymbols可以获取对象的所有Symbol属性名

    3.以Symbol值作为名称的属性不会被常规方法遍历得到,可以利用这个特效,为对象定义一些非私有但又希望只用于内部的方法。

 let objClass  = function (){
        this.sym = Symbol();
        this[this.sym] = 0;
        this.add = function(){
            this[this.sym]++
        };
        this.size = function(){
            return this[this.sym];
        }
    }
    var s = new objClass()
    s.add();
    s.size() //1

第十一章

    1. Proxy 用于拦截一个对象

  var obj = new Proxy({},
       get:function(){
    },
      set:function(){
    }
    })

        常用的方法有 get,set.has,deleteProperty,ownKeys等

    2.Reflect其实是重新标准化了一下Object的调用
        1.修改某些Object方法的返回结果,让其变得更加合理 比如 Object.defineProperty(obj, name, desc)在无法定义属性时会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false

        2.让Object的操作变成函数行为。如:name in obj 和 delete obj[name],等于 Reflect.has(obj, name) 和 Reflect.deleteProperty(obj, name)

        3.Reflect对象的方法与Proxy的方法一一对应。这样Proxy就能方便调用Reflect的方法

第十三章

    1.Set 类型类似与数组,但成员的值都是唯一的,没有重复。

    var s = new Set();
    []2,3,4,5,6,2,2].map(x=>s.add(x))
    for(i on s){console.log(i)} // 2 3 5 4

    2.Array.from方法可以将Set结构转为数组,于是就多了一种数组去重的方法

    3. Set().keys, Set().values 方法返回的东西是一样的

     4.我们可以很容易的实现用Set实现并集,交集,差集

        let a = new Set([1, 2, 3]);

        let b = new Set([4, 3, 2]);

        并集:

        let union = new Set([…a, …b]); //[1,2,3,4]

        交集:

        let intersect = new Set([…a].filter(x => b.has(x))); //[2,3]

        差集:

        let difference= new Set([…a].filter(x => !b.has(x))); //[1]

    5.WeakSet 与Set类似,但有2个不同

         1.WeakSet的成员只能是对象,不能是其他类型的值

          2、垃圾回收机制不考虑WeakSet对该对象的引用

      6.如果你需要“键值对”的数据结构,Map比Object更合适,主要是Map允许变量作为key

     7.WeakMap与WeakSet同样只接受对象作为键名,

        let myWeakMap = new WeakMap();

        myWeakMap.set(DOM, {clickTime:0});

        当这个DOM被删除时,不必担心它被引用,会自动消失。

第十四章

    1.如果想要一个对象支持遍历器,可以这么做

      如 JQuery.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

    2.forEach的缺点是无法中途跳出forEach循环,break或者return都不行

第十五章

    1.Cenerator 函数  xx.next() 的返回值结果总是{value:'xxx', done:false/true}

    2.next方法可以带一个参数,该参数会被当作上一条yield语句的返回值

    function* f(){
        for(var i=0; true; i++){
            var reset = yield i;
            if(rest) { i = -1;}
        }    
    }    
    var g = f();
    g.next() // {value:0, done:false}
    g.next() // {value:1, done:false}
    g.next(true) //{value:0, done:false}

        可以达到在Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整整个函数行为。

    3. yield* 语句返回单个字符串。因为字符串具有Iterator接口,所以能用yield*遍历

    let read = (function(){
        yield 'hello';
        yield* 'hello'; 
    })()
    read.next().value // "hello"
    read.next().value // "h"

    4. 

    var clock = function*(_) {
            while (true) {
                yield _;
                console.log('Tick!');
                yield _;
                console.log('Tock!');
            }
      };
    let toggle = clock(); 
    toggle.next();
    toggle.next(); // Tick!
    toggle.next(); // Tock!
    ......

如果使用 toggle 那么用Generator函数不需要一个变量来保存状态

第十六章

1.

//bad
promise.then(function(data)) {
    //success
},function(err) {
    //error
};

//good
promise.then(function(data)) {
    //success
}).catch(function(err) {
    //error
});

 第二种写法要好于第一种写法,理由是前者更接近同步的写法. Promise.reject() 会出发catch

2.Promise.race()   与 Promise.all() 很像,区别就是 Promise.race()只需要其中一个最快的promise指向完,就触发返回

3.Promise.resolve('foo') 等价于 new Promise(resolve=>resolve('foo'))

    所以如果希望得到一个Promise对象,比较方便的方法就算直接调用Promise.resolve方法

第十七章

var gen = function* (){
    var f1 = yield readFile('/etc/fstab/');
    var f2 = yield readFile('/etc/shells');
    console.log(f1.toString());
    console.log(f2.toString());
}
 写成async函数就是这样
var asyncReadFile = async function (){
    var f1 = await readFile('/etc/fstab/');
    var f2 = await readFile('/etc/shells');
    console.log(f1.toString());
    console.log(f2.toString());
}

一比较就发现,async函数就算将Cenerator 函数的星号(*)替换成async, 将yield替换成await仅此而已。

第十八章

1.

class Point{
    constructor(x, y) {
        //...
    }
    toString() {
        //...
    }
}
Object.keys(Point.prototype)
  // ["toString"]

    toString方法是Point类内部定义的方法,它是不可枚举的。这一点与es5的行为不一致

 var Point = function(x,y){
        //...
    }
    Point.prototype.toString = function() {
        //...
    }
    Object.keys(Point.prototype)
    // ["toString"]

2.

constructor方法默认返回实例对象this,不过完全可以指定返回另外一个对象

class Foo{
    constructor() {
        return Object.create(null)
    }
}
new Foo() instanceof Foo // false

这里类似于ES5的

 function Foo(){
    return {a:1}
}
var f = new Foo() // {a:1}
f instanceof Foo // false

3.  类和模块的内部默认就算严格模式,所以不需要使用use strict指定运行模式。

    考虑到未来所有代码其实都是运行在模块中,所以ES6实际上把整个语言升级到来严格模式

4。如果子类没有定义 constructor  方法,那么这个方法会被默认添加。代码如下

    constructor(…args) {

        super(…args);

    }

    也就是说,不管有没有显式定义,任何一个子类都有constructor方法

5. 

    1.子类的__proto__属性表示构造函数的继承,总是指向父类

    2.子类prototype属性的__proto__属性表示的继承,总是指向父类的prototype属性

class A {

}

class B extends A {

}

B.__proto__ === A // true
B.prototype.__proto___ === A.prototype //true

6。Class的Cenerator方法

如果在某个方法前加上星号(*),就表示该方法是一个Generator函数。

class Foo{
    constructor(...args) {
        this.args = args;
    }
    * [Symbol.iterator]() {
        for (let arg of this.args) {
            yield arg;
        }    
    }
}
for (let x of new Foo('hello', 'world')) {
    console.log(x);
}
// hello
// world

7. new.target 返回当前的类,用于判断这个函数是不是被new调用

function Person(name) {
    if (new.target === Person) {
        this.name = name;
    } else {
        throw new Error('必须使用new生成实例') ;
    }
}

第十九章

修饰器不仅可以修饰类,还可以修饰类的属性

class Person {
    @readonly
    name() {return `${this.first} ${this.last}`}
}

第二十章

1.

function v1(){ ... }
function v1(){ ... }
export {
    v1 as streamV1,
    v2 as streamV2,
    v2 as streamLatestVersion
}

上面的代码v2可以用不同的名字输出2次。

2.如果export处于块级作用域内,会报错。

3.export语句输出值是动态绑定的

export var foo = 'bar';

setTimeout(()=>foo = 'baz', 500);

上面的代码输出变量foo, 值为bar, 500毫秒之后变成baz

4.import命令具有提升效果,会提升到整个模块的头部首先执行。

5.module 命令可以取代import语句,达到整体输入模块的作用

module circle from './circle'  == import circle from './circle'

看起来代码量一样啊。。。囧

6.export * from 'circle' ;  可以把整个模块导出

7. 由于ES6 输入的模块变量只是一个“符号链接”,所以这个变量是只读的,对它进行重新赋值会报错。

// lib.js
export let obj = {};
//main.js
import {obj} from './lib';
obj.prop = 123; //OK
obj = {} //TypeError

第二十一章 编程风格建议

  1. 对象尽量静态化,一旦定义,就不得随意添加新的属性,如果添加属性不可避免,要使用Object.assign方法

//bad
const a = {};
a.x = 3;

// 如果不可避免
const a = {};
Object.assign(a, {x:3})

//good
const a = {x:null}
a.x = 3

2.如果对象的属性名是动态的,可以在创造对象时使用属性表达式定义。

//bad
const obj = {
    id:5,
    name: "nameA"
};
obj[getKey('enabled')] = true;

//good
const obj = {
    id:5,
    name: 'nameA'
    [getKey('enabled')]:true
}

3. 

如果模块默认输出一个函数,函数名的首字母应该小写。

function makeStyleGuide(){

}

export default makeStyleGuide;

 如果模块默认输出一个对象,对象名的首字母应该大写。

const StyleCuide = {

    es6:{

    }

}

export default StyleGuide

一个关于 npm install 的安全问题

最近发现一个模块  pre-commit  https://www.npmjs.com/package/pre-commit

这个模块的用途是在git commit的时候可以运行一些命令。本质上是在.git/hooks/pre-commit 插入一些命令。

问题来了:为什么  npm install –save-dev pre-commit 具有这么大的权限?能够对非./node_modules/pre-commit 以外的文件做修改?如果真能修改是不是可以修改windows文件了?这样会有安全问题。

关键点在于 npm install 这个命令,自己还不够熟悉,要持续观察。以后找到原因再更新文章。

SLAM 导航小车的持续研究

     对于智能硬件的研究,在2014年的时候就开始投入了,也不知道是为啥,无论为了前景还是兴趣,作为前端工程师的我,好像跟这个硬件关系不大,纯粹就是觉得好玩。对那些比如四轴飞机,固定翼飞机,机器人等等非常好奇,好奇他们是怎么做到的,可能对于机械自动化专业的人觉得挺简单的,我自己也开始折腾的生涯。

     对于网上一大把的智能小车,手机蓝牙控制的,几年前已经玩过了,手机控制小车,51单片机,蓝牙模块与用过了,觉得51单片机太难入门,于是换成了树莓派。

     之后参加了一场黑客马拉松,认识了一些懂硬件的小伙伴,认识了arduino,于是把arduino和树莓派都装到了小车上。

     后来有一段时间迷上了固定翼飞机(FPV),买了一个遥控器和电池回来,后来一个电机,装上。有一次电机失控,螺旋桨直接飞了出来,幸好当时没人在附近,但是由于太危险了,就放弃玩这个了。

     后来发现,蓝牙控制小车距离太短,而且感觉有点low,于是我把买给飞机用的遥控器和电池组都移植到了小车上,这样我的遥控车就有了超长距离的遥控(几公里吧),和十足的动力(900元的4S电池)。

     有了超远距离的实时控制还不够,其实我还需要想,能不能在公司,就能控制家里的小车,这其实是一件非常非常难的事情。wifi是有延迟的,稍微一点的网络延迟几百毫秒,车子就撞墙了,信号就丢了,再加上上面有实时摄像头图像传输,那一定会更加慢了。这都是经过我实际躺坑总结出来的经验。

     于是乎,我在想,与其实时控制,那还不如实时定位。先画出一副家里的地图,鼠标点到哪里,车子就自动走到哪里,像玩梦幻西游,点击NPC就会自动走路过去的效果。于是乎。。。这里涉及到的问题,太多太深奥了。

      第一步,要先画出一副家里的地图(这就是这篇文章要探讨内容)

      第二步,让机器识别当前位置是在地图的哪一个位置

      第三步,鼠标点一下地图,用算法自动导航到指定坐标。

      第四步,没电了,是不是应该会自动充电?

      第五步,是不是能够自动跟随某个东西,比如人,宠物?

      第六步,是不是给它整一个机械臂,做一些简单的拿拖鞋之类的事情?

      哈哈,太多了,其中设计到的硬件知识,电子知识,机械知识,软件知识,图像处理,人工智能。而且。。还要花很多钱买设备。以后的事情以后再考虑吧。眼前我只能先踏出第一步。

      好了,故事说到这里,下面是干货。

      建立地图。可以理解为,自己做一个拥有导航功能的扫地机器人。

      硬件准备

            1. 一台智能小车,假设你已经对它非常熟悉。

            2. rpladir A1 激光测距摄像头(2016年买的,这东西我花了900大洋,现在有更贵的版本了)

            3. 树莓派3

            4. 台式机Ubuntu系统