重新学习JavaScript(1)

学习JavaScript,就从MDN开始,其中包含了大量的值得深入学习和理解的内容。在这篇博客我介绍一些最近看到的。

1,class expression

首先就是类表达式https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/class 说实话这是我最近才知道的用法。它的语法如下,非常类似正常的类定义语句(class declaration statement),但是可以赋值给一个变量。如果使用typeof查一下就知道,MyClass类型还是function

const MyClass = class [className] [extends otherClassName] {
    // class body
};

类表达式可以重复定义,这个和类定义方式不同。另外className可以省略,如果命名了,那这个名字也只在class body有效,这个和function expression是一样的 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function

2, this

this在JavaScript面试中几乎是必考的点,https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this 这里值得注意的几点,一个是call和apply怎么记忆呢?最简单的办法就是apply第二个参数接受的是数组类型,也就是array,a对a就能容易记住了。

下面这段代码,其中有个知识点就是如果第一个参数不是object类型,就会调用内部的ToObject函数,比如7就会变成Number,而字符串字面值‘foo’就会转成String。

function bar() {
  console.log(Object.prototype.toString.call(this));
}
bar.call(7);     // [object Number]
bar.call('foo'); // [object String]

而bind需要注意的是只会绑定一次

function f() {
  return this.a;
}
var g = f.bind({a: 'azerty'});
console.log(g()); // azerty
var h = g.bind({a: 'yoo'}); // bind only works once!
console.log(h()); // azerty

3,Destructuring assignment解构赋值

这个词有点难理解,多看看里面的示例会好很多,大致上分成两类数组解构和对象解构 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment

下面这个用法有点费解,最后得到的两个变量不是a和b,而是aa和bb。

const {a: aa = 10, b: bb = 5} = {a: 3};
console.log(aa); // 3
console.log(bb); // 5

继续加深理解

const user = {
  id: 42,
  displayName: 'jdoe',
  fullName: {
    firstName: 'John',
    lastName: 'Doe'
  }
};
function userId({id}) {
  return id;
}
function whois({displayName, fullName: {firstName: name}}) {
  return `${displayName} is ${name}`;
}
console.log(userId(user)); // 42
console.log(whois(user));  // "jdoe is John"

注意与下面的例子的区别,主要在参数的声明格式上。一个用的等号,一个用的冒号。

function drawChart({size = 'big', coords = {x: 0, y: 0}, radius = 25} = {}) {
  console.log(size, coords, radius);
}
drawChart({
  coords: {x: 18, y: 30},
  radius: 30
});

这个例子更复杂些,属性是计算得来

let key = 'z';
let {[key]: foo} = {z: 'bar'};
console.log(foo); // "bar"

还可以用rest 属性… 顺序也没有关系,只要能和属性名对上就行

let {c, a, ...rest} = {a: 10, b: 20, c: 30, d: 40}
a; // 10
c; // 30
rest; // { b: 20, d: 40 }
const foo = { 'fizz-buzz': true };
const { 'fizz-buzz': fizzBuzz } = foo;
console.log(fizzBuzz); // "true"

数组解构和对象解构组合起来

const props = [
  { id: 1, name: 'Fizz'},
  { id: 2, name: 'Buzz'},
  { id: 3, name: 'FizzBuzz'}
];
const [,, { name }] = props;
console.log(name); // "FizzBuzz"

在JavaScript中应用Mixin模式

编程中Mixin是什么?我最近看到这个词,有点感兴趣起来。我以前也见过这个词,依稀记得是在介绍Ruby的文章当中,但是JavaScript中的Mixin干什么用呢?有些公众号提到express.js和vue.js的实现应用了mixin模式,我最近主要也是做这些方面,所以得关注了解啊。

最权威的还是先看看wiki怎么说:https://en.wikipedia.org/wiki/Mixin 头一段说明有点绕,不是很容易理解,那就看看JS代码吧。Wiki列出了三种实现,分别是extend,Object.assign以及Flight-Mixin。

第一种extend方式的参考实现,就是把源对象上面的key,逐个赋值到目标对象上。其它的两种大致也都是如此,Flight-Mixin模式其实就是IIFE直接执行的变形。

剥掉了神秘感,剩下的就很简单了。有什么限制呢?首先,mixin的源对象操作this的属性,一定要在目标对象上存在,否则就会出现undefined问题了。其次mixin在JS这种动态类型的编程语言是合适的,如果用在c#这样的静态类型语言,就可以用扩展方法来实现,更灵活的就得用dynamic来定义this了。

那mixin的用处,大致就是mixin源对象可以定义behavior,然后可以动态的绑定到其它对象上,只要这些对象满足mixin的constraints就可以了,把行为抽象出来。就此推论,mixin也很容易实现成decorator。mixin还要注意的一点是,它实质是一种浅拷贝,浅拷贝可能有的问题它也会有。

在core-decorators项目中的代码更加完整 https://github.com/jayphelps/core-decorators/blob/5b754256a30c23a0aef846c1b45f261e0c7b21a2/src/mixin.js,其中使用了getOwnPropertyDescriptors以及defineProperty这样更为特定的函数处理不同的情况。

mixin有什么问题么?当然有,什么设计模式都有局限性和适用的场景。比如react就提到https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html 简单说,mixin在大的codebase中增加了隐形依赖复杂度(Mixins introduce implicit dependencies),命名冲突问题(Mixins cause name clashes),滚雪球复杂度(Mixins cause snowballing complexity)。所以在express.js这样的轻量级框架中使用mixin说的通,但是复杂的企业级应用,就不得不考虑复杂度带来的各种问题。

我的建议是如果软件超过7个人开发,那就考虑angular/TypeScript/rxjs这一套。如果是小项目,可以考虑react或者vue.js,但是最好也配上babel加ES最新标准。

感兴趣的可以留言讨论。

通过awesome和alternative学技术

编程技术学习到某个程度,就需要扩展眼界,这样才能把自己锻造成T字形人才,有深度有广度。

我这里推荐两个方法可以扩展某个领域的知识面,一个是Awesome,一个是alternative。

比如我想了解更多关于angular方方面面的知识,可以直接在github敲入awesome angular搜索,就可以找到这个repo https://github.com/PatrickJS/awesome-angular 如果感兴趣rust,就能找到https://github.com/rust-unofficial/awesome-rust 。Awesome类知识汇集有点类似百科全书,或者是以前的dmoz(还有人知道这个么?),有空就可以瞅瞅,对于某个项目/库感兴趣了就深挖一下。

alternative的用法又不一样了。我经常收到领导的要求,其实也是架构师常见的工作,就是比较不同产品或者框架的优缺点,做一些比较或者雷达图什么的。这时候就可以搜索关键字加“alternative”,比如想了解aws的竞争者就在谷歌搜“aws alternative”,然后点开结果页面看看。还有一个网站https://alternativeto.net/ 就是专门做同类型软件比较打分的,还算客观。

周末玩玩技术

我很久以前玩过一段时间的google appengine,用它来连接rss填补google reader的空白。前两天趁着周末又捡出来把玩了一下,现在的感觉却不怎么好用了,难怪google的云计算赶了个大早,却被后来者azure居上。

简单的crud应用,比如todo或者记事本甚至是proxy,appengine还是可以用一用的。但是它有几个问题,一个是standand环境的编程比较复杂,开发者要了解一大堆东西,总算把google datastore在nodejs上跑了起来,但是运行起来才发现javascript版本的rss parser不给力,不如python的feedparser成熟。而python想找一个在appengine稍微能用的blog或者cms几乎是不可能的,大多数的github项目都是11、12年甚至零几年的。另外的问题是如果不付费很多功能都阉割了,即使google cloud赠送了300美元一年的试用,可是我不想浪费时间在这个没落的平台上了。

于是又想到了新型的平台zeitnetlify,它俩都支持连接到github直接拽代码部署,命令行直接搞定,试用了一下部署一个coreui的angular demo棒棒哒。如果是纯js项目可以考虑这两个平台。

做toy project的开发者还可以考虑heroku,这个老牌平台虽然有着google cloud同样的缺点,但是它上面一些开源项目却很实用,比如部署了一个下载youtube视频的应用在上面,还可以支持v2,前面挂上cloudflare那就可以特殊情况下应应急。

Different methods in JavaScript to do deep clone for plain object without libraries

1, use JSON.parse and JSON.stringify, but it has many issues, major one is can’t handle Date type.

var cloned = JSON.parse(JSON.stringify(objectToClone));

2, refer to this, mainly ideas is loop the properties one by one, and go into children, also need to handle ‘Function’, ‘Symbol’, ‘WeakSet’, etc. all possible data types in JavaScript :

https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript

function clone(obj) {
    if (obj === null || typeof (obj) !== 'object' || 'isActiveClone' in obj)
        return obj;

    if (obj instanceof Date)
        var temp = new obj.constructor(); //or new Date(obj);
    else
        var temp = obj.constructor();

    for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            obj['isActiveClone'] = null;
            temp[key] = clone(obj[key]);
            delete obj['isActiveClone'];
        }
    }
    return temp;
}

also there are more examples could refer to, for example this version needs ES6 supports https://stackoverflow.com/questions/40291987/javascript-deep-clone-object-with-circular-references

function deepClone(obj, hash = new WeakMap()) {
    // Do not try to clone primitives or functions
    if (Object(obj) !== obj || obj instanceof Function) return obj;
    if (hash.has(obj)) return hash.get(obj); // Cyclic reference
    try { // Try to run constructor (without arguments, as we don't know them)
        var result = new obj.constructor();
    } catch(e) { // Constructor failed, create object without running the constructor
        result = Object.create(Object.getPrototypeOf(obj));
    }
    // Optional: support for some standard constructors (extend as desired)
    if (obj instanceof Map)
        Array.from(obj, ([key, val]) => result.set(deepClone(key, hash), 
                                                   deepClone(val, hash)) );
    else if (obj instanceof Set)
        Array.from(obj, (key) => result.add(deepClone(key, hash)) );
    // Register in hash    
    hash.set(obj, result);
    // Clone and assign enumerable own properties recursively
    return Object.assign(result, ...Object.keys(obj).map (
        key => ({ [key]: deepClone(obj[key], hash) }) ));
}

Anyway, I recommend to use JavaScript library ‘clone‘ for the deep clone, it could handle circular reference correctly.

If you need to create more complex feature to re-create (serialization) new instance from source object or JSON string (and use TypeScript), I prefer to use this https://github.com/typestack/class-transformer The ‘class-transformer’ doesn’t force you to describe the class with decorator (of course you could do it, and have to if you need rich features), it is good point I prefer to.