我的一生是抗争的一生

“我的一生是抗争的一生!”,这是伍声2009在他的《沉默的独白》视频中说的一句话。如果回顾过去,我又何尝不是呢!

今天跟亚丽讨论学员回访需求时突然发现一个问题:就是对于产品的想法、思考虽没有停止,但是却渐渐的变得认为这些想法和思考已“不足为外人道哉”,不再轻易的将自己的想法展现给别人了。这是一个可怕的变化,我想这来自于我对工作、亦或对生活的变化。

回想2018年,查看整个2018年所有的工作日志,我的思考、反思非常多。从运营平台到招生小程序,再到微信端学员中心、驾校中心、运营中心,最后到新系统的开发。这些项目一步步走来,我从未停止。2019年时我开始思考18年我都做了什么,哪些有价值,哪些没有价值的时候发现大部分想法都没有任何起色或者价值,我知道这样的结果可能来自于多方面原因,但我最终还是否定了18年那么多的想法最终的价值——这是在18年年终总结时我对此下的一个定论。2019年后随着技术人员的减少,新系统开发速度愈发缓慢,最后变成我一个技术人员时,我开始变得沉沦。

沉沦就没有了反思,或者有思考有触动却不想说出来,思想不能讨论,就像现金失去了流动性,最终结局只会陷入时间的沙漠中。

回到现在的工作,我们从不缺乏机会,只是三年的路程走来,似乎什么路我们都走过,又似乎什么路我们都没有走完。这是最大的遗憾,人生有长短,但唯独不能留有遗憾。遗憾的事情出现一件就会再出现另一件,随之而来的便是遗憾的一生。

我想我的一生不能如此,我的一生只能是抗争的一生。

心有猛虎,细嗅蔷薇

我家的屋顶

已经记不清从哪个时候起,我停止了写心情笔记。

在记忆中,我还记得以前,刚毕业那会,甚至毕业前,我经常写日记。《音乐里的记忆》、《那人、那雨、那夜》等等文章依然记得。前者是毕业前对于整个大学里的人和事的回忆,而后者则是对那天从网吧回学校,得知我心里的女神早已心有所属的心痛,至今依然记得大雨滂沱……

时间真是可怕,想来已毕业快七年了吧。人都说“七年之痒”,呵呵,此刻也同样如此!

这段时间,发生的事情,比从神州泰岳出走后所发生的事情更复杂,更让我印象深刻,更难忘。我终于“弃暗投明”,做回了真正的自己。在现在这个社会,做自己并不是一件很容易的事,甚至有的人一辈子也做不到这一点!这需要勇气,需要时机,需要熬过那些不情愿扮鬼的漫长的日子,需要很多很多难得的条件……聚在一起,坚持到那一天,临门一脚。

是的,我终于这么做了,我勇敢坚持,担当起来,我做回了自己!

之后,也许会面对更多的人,更多的事情,更复杂纷繁。但那又有什么可怕的呢?我怎么想就怎么做就可以了。

我已成长,但我想我还是以前那个特立独行的孩子,不擅言谈,善良而敏感,坚韧而木讷,从不伤及他人,极力想看到一个和谐,皆大欢喜的世界——理想主义者!

理解 JavaScript Promise

Promise 与事件监听类似,除了以下几点:

  1. 一个 Promise 只能成功或者失败一次。它不能进行从成功到失败的状态转换,反之亦然。
  2. 如果在一个 Promise 成功或者失败后添加了相应的回调函数,那么该回调还是会被立即执行,尽管相关事件早已发生。

Promise 术语

一个 Promise 处于以下四种状态之一。

  • fulfilled:操作成功。
  • rejected:操作失败。
  • pending:操作进行中,还没有得到操作结果。
  • settled:Promise 已经被 fulfilled 或 rejected,且不是 pending。

JavaScript 中的 Promise

Promise 的构造器接受一个函数作为回调函数,同时给回调函数提供了两个参数:resolve 和 reject。

var promise = new Promise(function(resolve, reject) {
    // 类似异步操作的一些逻辑,然后...
    
    if(/*正常直行*/) {
        resolve('Stuff worked!);
    } else {
        reject(Error('It broke'));
    }
});

下面看下如何使用 Promise:

promise.then(function(result) {
    console.log(result); // 执行成功
}, function(err) {
    console.log(err); // Error: '执行失败'
});

与其它库的兼容性

jQuery 中的 Deferred 对象与 Promise/A+ 并不兼容。jQuery 中也有 Promise 类型,但它只是 Deferred 的一个子集。我们可以将它们转换成标准的 Promise,而且最好当它们一出现的时候就进行转换:

var jsPromise = Promise.reslove($.ajax('/whatever.json'));

// jQuery 的 Deferred 的 then 返回了更多的参数
var jqDeferred = $.ajax('/whatever.json');
jqDeferred.then(function(response, statusText, xhrObj) {
    // ...
}, function(xhrObj, textStatus, err) {
    // ...
});

// 转化为 Promise 后只会保留第一个参数,其它参数均会被忽略
jsPromise.then(function(response) {
    // ...
}, function(xhrObj) {
    // ...
});

将 Promise 用于 XMLHttpRequest

// 用 Promise 封装 XMLHttpRequest 实现的简单 Get 方法
function get(url) {
    return new Promise(function(reslove, reject) {
        var req = new XMLHttpRequest();
        req.open('GET', url);
        
        req.onload = function() {
            if (req.status == 200) {
                resolve(req.response);
            } else {
                reject(Error(req.statusText));
            }
        };
        
        req.onerror = function() {
            reject(Error('Network Error'));
        };
        
        req.send();
    });
}

下面是使用方法:

get('story.json').then(function(response) {
    
}, function(error) {
    
});

链式调用

var promise = new Promise(function(resolve, reject) {
    resolve(1);
});

promise.then(function(val) {
    console.log(val);
    return val + 2;
}).then(function(val) {
    console.log(val);
});

// 可以使用这种方式修改之前的代码
get('story.json').then(function(response) {
    return JSON.parse(response);
}).then(function(response) {
    console.log(response);
});
// 再简化
get('story.json').then(JSON.parse).then(function(response) {
    console.log(response); // json 格式的数据
});
// 更好的方式
function getJSON(url) {
    return get(url).then(JSON.parse);
}

异步操作队列

当在“then”方法的回调函数里有返回值的时候,这里有些重要的事情需要我们关注。如果你返回的是一个值,那么下一个“then”会立即调用,并把这个值传递给它的回调函数。如果你返回的是一个“类 Promise”对象,那么下一个“then”方法会等待执行,直到这个 Promise 被解决(成功或失败)后才执行。

getJSON('story.json').then(function(story) {
    return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
    console.log(chapter1);
});

现在同简单的回调模式相比,Promise 开始显示出自己的优势了。可以像下面这样写个获取章节内容的函数:

var storyPromise;

function getChapter(i) {
    // 闭包
    storyPromise = storyPromise || getJSON('story.json');
    return storyPromise.then(function(story) {
        return getJSON(story.chapterUrls[i]);
    });
}

getChapter(0).then(function(chapter) {
    console.log(chapter);
    return getChapter(1);
}).then(function(chapter) {
    console.log(chapter);
});

错误处理

// 注意 then(func1, func2) 和 then(func1).catch(func2) 的区别
// catch 类似于 try/catch ,能够捕获到上一步中的任何异常,其后的
// then 还可以继续执行
asyncThing1().then(function(){
    return asyncThing2();
}).then(function(){
    return asyncThing3();
}).catch(function(err){
    return asyncRecovery1();
}).then(function(){
    return asyncThing4();
}, function(err){
    return asyncRecovery2();
}).catch(function(err){
    console.log("Don't worry about it.");
}).then(function(){
    console.log('All done!');
});

并行和串行

这里是并行和串行的异步操作同时存在的例子:

var sequence = Promise.resolve();

story.chapterUrls.forEach(function(chapterUrl){
    // 在 sequence 后添加如下操作
    sequence = sequence.then(function() {
        return getJSON(chapterUrl);
    }).then(function(chapter){
        addHtmlToPage(chapter.html);
    });
});

Promise.resolve() 会根据传入的参数返回一个 Promise。与之对应的 Promise.reject(val) 方法,会返回一个根据你传入的参数值(或 underfined)为 reject 结果的 Promise。

getJSON('story.json').then(function(story) {
    addHtmlToPage(story.heading);
    
    return story.chapterUrls.reduce(function(sequence, chapterUrl) {
        // 当前一章节的 Promise 完成后
        return sequence.then(function(){
            // 接着获取下一章节
            return getJSON(chapterUrl);
        }).then(function(chapter){
            addHtmlToPage(chapter.html);
        });
    }, Promise.reslove());
}).then(function(){
    addTextToPage("All done.");
}).catch(function(err){
    addTextToPage("Argh, broken: " + err.message);
}).then(function(){
    document.querySelector('.spinner').style.display = 'none';
});

Promise.all 接受一个 Promise 数组作为参数,创建一个所有的 Promise 都 resolve 后才 resolve 的 Promise 的对象,resolve 结果是一个数组,包含了前面所有 Promise 的 resolve 结果,并且顺序和传入时的顺序是完全一致的。

getJSON('story.json').then(function(story) {
    addHtmlToPage(story.heading);
    
    return Promise.all(
        story.chapterUrls.map(getJSON)
    );
}).then(function(chapters){
    chapters.forEach(function(chapter) {
        addHtmlToPage(chapter.html);
    });
}).catch(function(err){
    addTextToPage("Argh, broken: " + err.message);
}).then(function(){
    document.querySelector('.spinner').style.display = 'none';
});

不想等到所有章节都获取到后才显示到页面上:

getJSON('story.json').then(function(story) {
    addHtmlToPage(story.heading);
    
    return story.chapterUrls.map(getJSON) // getJSON 后的 Promise 
        .reduce(function(sequence, chapterPromise){
            return sequence.then(function(){
                return chapterPromise;
            }).then(function(chapter){
                addHtmlToPage(chapter.html);
            });
        }, Promise.resolve());
}).then(function(){
    addTextToPage("All done.");
}).catch(function(err){
    addTextToPage("Argh, broken: " + err.message);
}).then(function(){
    document.querySelector('.spinner').style.display = 'none';
});

Promise 厉害的地方还远不止于此!

CSS预编译工具研究

一、前言

回顾计算机语言的进化史,似乎每隔几年都会诞生一个新的概念以推进计算机语言的发展。从二进制升级到指令集,从指令集再升级到编译语言。而像HTML、CSS和JavaScript这些语言都是通过浏览器来渲染的。HTML、CSS和JavaScript语言以前所未有的方式使WEB获得了巨大的成功。依靠它们,我们建立了更加庞大、更加复杂的网站,这是一件美好的事;但我们需要面对的一点是,我们要更进一步使得我们的代码易于管理和维护。其中,CSS对于易于管理和维护有着迫切的需求。CSS的简单造成了今天其代码的冗余。现在CSS准备华丽的转身了。

CSS预处理器(CSS Preprocessor),是一种构架于CSS之上的高级语言,可以通过脚本编译生成CSS代码,其目的是为了让CSS开发者的工作更简单有趣,当前已经进入了较为成熟的阶段,基本上新的WEB开发项目大都已普遍使用。

现在最主要的选择有:

  • SASS:2007年诞生,最早也是最成熟的CSS预处理器,拥有ruby社区的支持和compass这一最强大的css框架,目前受LESS影响,已经进化到了全面兼容CSS的SCSS。
  • LESS:2009年出现,受SASS的影响较大,但又使用CSS的语法,让大部分开发者和设计师更容易上手,在ruby社区之外支持者远超过SASS,其缺点是比起SASS来,可编程功能不够,不过优点是简单和兼容CSS,反过来也影响了SASS演变到了SCSS的时代,著名的Twitter Bootstrap就是采用LESS做底层语言的。
  • Stylus:2010年产生,来自Node.js社区,主要用来给Node项目进行CSS预处理支持,在此社区之内有一定支持者,在广泛的意义上人气还完全不如SASS和LESS。

以上就是CSS预编译工具的一些介绍,下面我将对SASS和LESS的使用进行讲解,并分别提供一个简单的示例。

二、LESS

1. 变量

注意,由于变量只能定义一次,其本质就是“常量”。

2. 混合(Mixin)

混合是一种将一个规则集混入另一个规则集的方法。

.bordered {
  border-top: dotted 1px black;
  border-bottom: solid 2px black;
}
#menu a {
  color: #111;
  .bordered;
}

.post a {
  color: red;
  .bordered;
}

3. 嵌套规则

#header {
  color: black;
}
#header .navigation {
  font-size: 12px;
}
#header .logo {
  width: 300px;
}
#header {
  color: black;
  .navigation {
    font-size: 12px;
  }
  .logo {
    width: 300px;
  }
}

还可以使用父选择器选择符:

.clearfix {
  display: block;
  zoom: 1;

  &:after {
    content: " ";
    display: block;
    font-size: 0;
    height: 0;
    clear: both;
    visibility: hidden;
  }
}

4. 运算

任何数字、颜色或者变量都可以参与运算。下面是一组案例:

@base: 5%;
@filler: @base * 2;
@other: @base + @filler;

color: #888 / 4;
background-color: @base-color + #111;
height: 100% / 2 + @filler;

5. 函数

Less 内置了多种函数用于转换颜色、处理字符串、算术运算等。这些函数在函数手册中有详细介绍。

函数的用法非常简单。下面这个例子将介绍如何将 0.5 转换为 50%,将颜色饱和度增加 5%,以及颜色亮度降低 25% 并且色相值增加 8 等用法:

@base: #f04615;
@width: 0.5;

.class {
  width: percentage(@width); // returns `50%`
  color: saturate(@base, 5%);
  background-color: spin(lighten(@base, 25%), 8);
}

6. 命名空间和访问器

#bundle {
  .button {
    display: block;
    border: 1px solid black;
    background-color: grey;
    &:hover {
      background-color: white
    }
  }
  .tab { ... }
  .citation { ... }
}

使用方法:

#header a {
  color: orange;
  #bundle > .button;
}

7. 作用域

Less中的作用域和其他语言的类似,如果在本身的作用域中找不到变量,将会从其父作用域中查找。

@var: red;

#page {
  @var: white;
  #header {
    color: @var; // white
  }
}

变量和混合不必在使用前声明,也可以放在后面:

@var: red;

#page {
  #header {
    color: @var; // white
  }
  @var: white;
}

8. 注释

/* 一个注释块
style comment! */
@var: red;

// 这一行被注释掉了!
@var: white;

9. 导入

你可以导入一个 .less 文件,此文件中的所有变量就可以全部使用了。如果导入的文件是 .less 扩展名,则可以将扩展名省略掉:

@import "library"; // library.less
@import "typo.css";

10. 其他

Mixin Guards、CSS Guards、Loop、Merge、函数式Mixin

三、SASS

1. 变量

$font-stack:    Helvetica, sans-serif;
$primary-color: #333;

body {
  font: 100% $font-stack;
  color: $primary-color;
}

2. 嵌套

nav {
  ul {
    margin: 0;
    padding: 0;
    list-style: none;
  }

  li { display: inline-block; }

  a {
    display: block;
    padding: 6px 12px;
    text-decoration: none;
  }
}

属性嵌套:

$fontsize: 12px
.speaker
  .name
    font:
      weight: bold
      size: $fontsize + 10px
  .position
    font:
      size: $fontsize 

3. 分离

可以将你的样式表分隔成很多个单独的文件。然后,你可以在主样式文件中通过@import引入你所需要的.scss文件。

4. 导入

// _reset.scss

html,
body,
ul,
ol {
   margin: 0;
  padding: 0;
}
/* base.scss */

@import 'reset';

body {
  font-size: 100% Helvetica, sans-serif;
  background-color: #efefef;
}

5. 混合(Mixins)

@mixin border-radius($radius) {
  -webkit-border-radius: $radius;
     -moz-border-radius: $radius;
      -ms-border-radius: $radius;
          border-radius: $radius;
}

.box { @include border-radius(10px); }

6. 扩展和继承

.message {
  border: 1px solid #ccc;
  padding: 10px;
  color: #333;
}

.success {
  @extend .message;
  border-color: green;
}

.error {
  @extend .message;
  border-color: red;
}

.warning {
  @extend .message;
  border-color: yellow;
}

7. 运算

.container { width: 100%; }

article[role="main"] {
  float: left;
  width: 600px / 960px * 100%;
}

aside[role="complimentary"] {
  float: right;
  width: 300px / 960px * 100%;
}

四、简单示例

JavaScript 高程设计——面向对象的程序设计(一)

理解对象

ECMA-262 把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。”

属性类型

ECMA-262 第5版在定义只有内部才用的特性(attribute)时,描述了属性(property)的各种特征。ECMA-262 定义这些特性是为了实现 JavaScript 引擎用的,因此在 JavaScript 中不能直接访问它们。

ECMAScript 中有两种属性:数据属性访问器属性

1.数据属性

数据属性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有4个描述其行为的特性。

  • [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true

  • [[Enumerable]]:表示能否通过 for-in 循环返回属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true

  • [[Writable]]:表示能否修改属性的值。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true

  • [[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为 undefined

使用 ECMAScript 5 的 Object.defineProperty() 方法修改属性默认的特性。这个方法接收三个参数:属性所在的对象、属性的名字和一个描述符对象。其中,描述符(descrip-tor)对象的属性必须是:configurableenumerablewritablevalue

var person = {};
Object.defineProperty(person, "name", {    
    writable: false,
    value: "Nicholas"
});

//"Nicholas"
alert(person.name);
person.name = "Greg";
//"Nicholas"
alert(person.name);

2. 访问器属性

访问器属性不包含数据值;它们包含一对儿 gettersetter 函数。在读取访问器属性时,会调用 getter 函数,这个函数负责返回有效的值;在写入访问器属性时,会调用 setter 函数并传入新值,这个函数负责决定如何处理数据。访问器属性有如下4个特性。

  • [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值为 true

  • [[Enumerable]]:表示能否通过 for-in 循环返回属性。对于直接在对象上定义的属性,这个特性的默认值为 true

  • [[Get]]:在读取属性时调用的函数。默认值为 undefined

  • [[Set]]:在写入属性时调用的函数。默认值为 undefined

创建对象

工厂模式

function createPerson(name, age, job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        alert(this.name);
    };    
    return o;
}
        
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

person1.sayName();   //"Nicholas"
person2.sayName();   //"Greg"

工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。

构造函数模式

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert(this.name);
    };    
}

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

person1.sayName();   //"Nicholas"
person2.sayName();   //"Greg"

alert(person1 instanceof Object);  //true
alert(person1 instanceof Person);  //true
alert(person2 instanceof Object);  //true
alert(person2 instanceof Person);  //true

alert(person1.constructor == Person);  //true
alert(person2.constructor == Person);  //true

alert(person1.sayName == person2.sayName);  //false 
// 以这种方式创建函数,会导致不同的作用域链和标识符解析,但创建 Function 新实例的机制仍然是相同的

任何函数,只要通过new操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过 new 操作符来调用,那它跟普通函数也不会有什么两样。

原型模式

使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。

function Person(){
}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};

var person1 = new Person();
person1.sayName();   //"Nicholas"

var person2 = new Person();
person2.sayName();   //"Nicholas"

alert(person1.sayName == person2.sayName);  //true

// // 内部都有一个指向Person.prototype的指针,因此都返回了true
alert(Person.prototype.isPrototypeOf(person1));  //true
alert(Person.prototype.isPrototypeOf(person2));  //true

//only works if Object.getPrototypeOf() is available
if (Object.getPrototypeOf){
    alert(Object.getPrototypeOf(person1) == Person.prototype);  //true
    alert(Object.getPrototypeOf(person1).name);  //"Nicholas"
}

prototype 属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。

通过使用 hasOwnProperty() 方法,什么时候访问的是实例属性,什么时候访问的是原型属性就一清二楚了。

要取得对象上所有可枚举的实例属性,可以使用 EC-MAScript 5 的 Object.keys() 方法。这个方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。

如果你想要得到所有实例属性,无论它是否可枚举,都可以使用 Object.getOwnPropertyNames() 方法。

var keys = Object.keys(Person.prototype);
//"name,age,job,sayName"
alert(keys);

var keys1 = Object.getOwnPropertyNames(Person.prototype); 
//"constructor,name,age,job,sayName" 
alert(keys1);

更简单的原型语法

function Person(){
}

Person.prototype = {
    constructor : Person, 
    name : "Nicholas",
    age : 29,
    job: "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

//重设构造函数,只适用于ECMAScript 5兼容的浏览器 
Object.defineProperty(Person.prototype, "constructor", {     
    enumerable: false,     
    value: Person 
});  

var friend = new Person();

alert(friend instanceof Object);  //true
alert(friend instanceof Person);  //true
alert(friend.constructor == Person);  //false
alert(friend.constructor == Object);  //true

原型对象的问题

原型模式的最大问题是由其共享的本性所导致的。对于包含引用类型值的属性来说,会导致创建的对象共享同一个属性,其中一个修改其他的实例也会被修改。