Giter Site home page Giter Site logo

blog's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

44个 Javascript 变态题解析 (上)

原题来自: javascript-puzzlers

读者可以先去做一下感受感受. 当初笔者的成绩是 21/44...

当初笔者做这套题的时候不仅怀疑智商, 连人生都开始怀疑了....

不过, 对于基础知识的理解是深入编程的前提. 让我们一起来看看这些变态题到底变态不变态吧!

第1题

["1", "2", "3"].map(parseInt)

知识点:

首先, map接受两个参数, 一个回调函数 callback, 一个回调函数的this值

其中回调函数接受三个参数 currentValue, index, arrary;

而题目中, map只传入了回调函数--parseInt.

其次, parseInt 只接受两个两个参数 string, radix(基数).

在没有指定基数,或者基数为 0 的情况下,JavaScript 作如下处理:

  • 如果字符串 string 以"0x"或者"0X"开头, 则基数是16 (16进制).
  • 如果字符串 string 以"0"开头, 基数是8(八进制)或者10(十进制),那么具体是哪个基数由实现环境决- 定。ECMAScript 5 规定使用10,但是并不是所有的浏览器都遵循这个规定。因此,永远都要明确给出radix参数的值。
  • 如果字符串 string 以其它任何值开头,则基数是10 (十进制)。

所以本题即问

parseInt('1', 0);
parseInt('2', 1);
parseInt('3', 2);

首先后两者参数不合法.

所以答案是 [1, NaN, NaN]

第2题

[typeof null, null instanceof Object]

两个知识点:

typeof 返回一个表示类型的字符串.

instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上.

这个题可以直接看链接... 因为 typeof null === 'object' 自语言之初就是这样....

typeof 的结果请看下表:

type         result
Undefined   "undefined"
Null        "object"
Boolean     "boolean"
Number      "number"
String      "string"
Symbol      "symbol"
Host object Implementation-dependent
Function    "function"
Object      "object"

所以答案 [object, false]

第3题

[ [3,2,1].reduce(Math.pow), [].reduce(Math.pow) ]

知识点:

arr.reduce(callback[, initialValue])

reduce接受两个参数, 一个回调, 一个初始值.

回调函数接受四个参数 previousValue, currentValue, currentIndex, array

需要注意的是 If the array is empty and no initialValue was provided, TypeError would be thrown.

所以第二个表达式会报异常. 第一个表达式等价于 Math.pow(3, 2) => 9; Math.pow(9, 1) =>9

答案 an error

第4题

var val = 'smtg';
console.log('Value is ' + (val === 'smtg') ? 'Something' : 'Nothing');

两个知识点:

简而言之 + 的优先级 大于 ?

所以原题等价于 'Value is true' ? 'Somthing' : 'Nonthing' 而不是 'Value is' + (true ? 'Something' : 'Nonthing')

答案 'Something'

第5题

var name = 'World!';
(function () {
    if (typeof name === 'undefined') {
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();

这个相对简单, 一个知识点:

在 JavaScript中, functions 和 variables 会被提升。变量提升是JavaScript将声明移至作用域 scope (全局域或者当前函数作用域) 顶部的行为。

这个题目相当于

var name = 'World!';
(function () {
    var name;
    if (typeof name === 'undefined') {
        name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();

所以答案是 'Goodbye Jack'

第6题

var END = Math.pow(2, 53);
var START = END - 100;
var count = 0;
for (var i = START; i <= END; i++) {
    count++;
}
console.log(count);

一个知识点:

在 JS 里, Math.pow(2, 53) == 9007199254740992 是可以表示的最大值. 最大值加一还是最大值. 所以循环不会停.

补充: @jelly7723

js中可以表示的最大整数不是2的53次方,而是1.7976931348623157e+308。
2的53次方不是js能表示的最大整数而应该是能正确计算且不失精度的最大整数,可以参见js权威指南。
9007199254740992 +1还是 9007199254740992 ,这就是因为精度问题,如果 9007199254740992 +11或者 9007199254740992 +111的话,值是会发生改变的,只是这时候计算的结果不是正确的值,就是因为精度丢失的问题。

第7题

var ary = [0,1,2];
ary[10] = 10;
ary.filter(function(x) { return x === undefined;});

答案是 []

看一篇文章理解稀疏数组

我们来看一下 Array.prototype.filter 的 polyfill:

if (!Array.prototype.filter) {
  Array.prototype.filter = function(fun/*, thisArg*/) {
    'use strict';

    if (this === void 0 || this === null) {
      throw new TypeError();
    }

    var t = Object(this);
    var len = t.length >>> 0;
    if (typeof fun !== 'function') {
      throw new TypeError();
    }

    var res = [];
    var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
    for (var i = 0; i < len; i++) {
      if (i in t) { // 注意这里!!!
        var val = t[i];
        if (fun.call(thisArg, val, i, t)) {
          res.push(val);
        }
      }
    }

    return res;
  };
}

我们看到在迭代这个数组的时候, 首先检查了这个索引值是不是数组的一个属性, 那么我们测试一下.

0 in ary; => true
3 in ary; => false
10 in ary; => true

也就是说 从 3 - 9 都是没有初始化的'坑'!, 这些索引并不存在与数组中. 在 array 的函数调用的时候是会跳过这些'坑'的.

第8题

var two   = 0.2
var one   = 0.1
var eight = 0.8
var six   = 0.6
[two - one == one, eight - six == two]

IEEE 754标准中的浮点数并不能精确地表达小数

那什么时候精准, 什么时候不经准呢? 笔者也不知道...

答案 [true, false]

第9题

function showCase(value) {
    switch(value) {
    case 'A':
        console.log('Case A');
        break;
    case 'B':
        console.log('Case B');
        break;
    case undefined:
        console.log('undefined');
        break;
    default:
        console.log('Do not know!');
    }
}
showCase(new String('A'));

两个知识点:

switch 是严格比较, String 实例和 字符串不一样.

var s_prim = 'foo';
var s_obj = new String(s_prim);

console.log(typeof s_prim); // "string"
console.log(typeof s_obj);  // "object"
console.log(s_prim === s_obj); // false

答案是 'Do not know!'

第10题

function showCase2(value) {
    switch(value) {
    case 'A':
        console.log('Case A');
        break;
    case 'B':
        console.log('Case B');
        break;
    case undefined:
        console.log('undefined');
        break;
    default:
        console.log('Do not know!');
    }
}
showCase2(String('A'));

解释:
String(x) does not create an object but does return a string, i.e. typeof String(1) === "string"

还是刚才的知识点, 只不过 String 不仅是个构造函数 直接调用返回一个字符串哦.

答案 'Case A'

第11题

function isOdd(num) {
    return num % 2 == 1;
}
function isEven(num) {
    return num % 2 == 0;
}
function isSane(num) {
    return isEven(num) || isOdd(num);
}
var values = [7, 4, '13', -9, Infinity];
values.map(isSane);

一个知识点

此题等价于

7 % 2 => 1
4 % 2 => 0
'13' % 2 => 1
-9 % % 2 => -1
Infinity % 2 => NaN

需要注意的是 余数的正负号随第一个操作数.

答案 [true, true, true, false, false]

第12题

parseInt(3, 8)
parseInt(3, 2)
parseInt(3, 0)

第一个题讲过了, 答案 3, NaN, 3

第13题

Array.isArray( Array.prototype )

一个知识点:

一个鲜为人知的实事: Array.prototype => [];

---> 对JS原型的一些思考 by renaesop

答案: true

第14题

var a = [0];
if ([0]) {
  console.log(a == true);
} else {
  console.log("wut");
}

解析:

  • Boolean([0]) === true
  • [0] == true
    • true 转换为数字 => 1
    • [0] 转化为数字失败, 转化为字符串 '0', 转化成数字 => 0
    • 0 !== 1

答案: false

第15题

[]==[]

[] 是Object, 两个 Object 不相等

答案是 false

第16题

'5' + 3
'5' - 3

两个知识点:

+ 用来表示两个数的和或者字符串拼接, -表示两数之差.

请看例子, 体会区别:

> '5' + 3
'53'
> 5 + '3'
'53'
> 5 - '3'
2
> '5' - 3
2
> '5' - '3'
2

也就是说 - 会尽可能的将两个操作数变成数字, 而 + 如果两边不都是数字, 那么就是字符串拼接.

答案是 '53', 2

第17题

1 + - + + + - + 1

这里应该是(倒着看)

1 + (a)  => 2
a = - (b) => 1
b = + (c) => -1
c = + (d) => -1
d = + (e) => -1
e = + (f) => -1
f = - (g) => -1
g = + 1   => 1

所以答案 2

第18题

var ary = Array(3);
ary[0]=2
ary.map(function(elem) { return '1'; });

稀疏数组. 同第7题.

题目中的数组其实是一个长度为3, 但是没有内容的数组, array 上的操作会跳过这些未初始化的'坑'.

所以答案是 ["1", undefined × 2]

这里贴上 Array.prototype.map 的 polyfill.

Array.prototype.map = function(callback, thisArg) {

        var T, A, k;

        if (this == null) {
            throw new TypeError(' this is null or not defined');
        }

        var O = Object(this);
        var len = O.length >>> 0;
        if (typeof callback !== 'function') {
            throw new TypeError(callback + ' is not a function');
        }
        if (arguments.length > 1) {
            T = thisArg;
        }
        A = new Array(len);
        k = 0;
        while (k < len) {
            var kValue, mappedValue;
            if (k in O) {
                kValue = O[k];
                mappedValue = callback.call(T, kValue, k, O);
                A[k] = mappedValue;
            }
            k++;
        }
        return A;
    };

第19题

function sidEffecting(ary) {
  ary[0] = ary[2];
}
function bar(a,b,c) {
  c = 10
  sidEffecting(arguments);
  return a + b + c;
}
bar(1,1,1)

这是一个大坑, 尤其是涉及到 ES6语法的时候

知识点:

首先 The arguments object is an Array-like object corresponding to the arguments passed to a function.

也就是说 arguments 是一个 object, c 就是 arguments[2], 所以对于 c 的修改就是对 arguments[2] 的修改.

所以答案是 21.

然而!!!!!!

当函数参数涉及到 any rest parameters, any default parameters or any destructured parameters 的时候, 这个 arguments 就不在是一个 mapped arguments object 了.....

请看:

function sidEffecting(ary) {
  ary[0] = ary[2];
}
function bar(a,b,c=3) {
  c = 10
  sidEffecting(arguments);
  return a + b + c;
}
bar(1,1,1)

答案是 12 !!!!

请读者细细体会!!

第20题


var a = 111111111111111110000,
    b = 1111;
a + b;

答案还是 111111111111111110000. 解释是 Lack of precision for numbers in JavaScript affects both small and big numbers. 但是笔者不是很明白................ 请读者赐教!

第21题

var x = [].reverse;
x();

这个题有意思!

知识点:

The reverse method transposes the elements of the calling array object in place, mutating the array, and returning a reference to the array.

也就是说 最后会返回这个调用者(this), 可是 x 执行的时候是上下文是全局. 那么最后返回的是 window.

补充:

@stellar91 这个笔者实践了一下 发现 firefox 是 window, chrome 报错 VM190:2 Uncaught TypeError: Array.prototype.reverse called on null or undefined(…) 可能是实现不同, 在 chrome 中应该是对调用者做了检查.

答案是 window

第22题

Number.MIN_VALUE > 0

true

@10081677wc
MIN_VALUE 属性是 JavaScript 中可表示的最小的数(接近 0 ,但不是负数),它的近似值为 5 x 10-324。

今天先到这里, 下次我们来看后22个题!

44个 Javascript 变态题解析 (下)

蚂蚁金服-微贷事业群 招前端

职位描述

参与微贷事业部 花呗、借呗、网商银行 等金融业务产品线研发,参与移动端 H5、React、Node.js 应用框架体系建设以及前端工程体系建设。

前端方向:

  1. 熟练掌握移动端 H5 开发、熟悉主流移动浏览器的技术特点;

  2. 熟练运用 JavaScript 语言与 HTML5、CSS3 等技术;

  3. 熟悉模块化、前端编译和构建工具,熟练运用主流的移动端 JS 库和开发框架,并深入理解其设计原理,例如:Zepto、React 等;

  4. 能提供完善的 WebApp 技术方案,了解 native 移动应用开发,有类 react native 开发经验者优先;

  5. 对技术有强烈的进取心,具有良好的沟通能力和团队合作精神、优秀的分析问题和解决问题的能力。

Node 方向:

  1. 熟悉 Node.js Web 应用开发,有大型 Node.js 项目的开发经验;

  2. 熟悉 Node.js 异步编程,对 koa/co/async 等模块原理机制了解透彻;

  3. 熟悉 Node.js 以及 V8 的性能和稳定性优化,能对系统整体性能进行评估,解决内存瓶颈;

  4. 熟悉 Web 安全相关知识,并能使用相关技术防范安全漏洞;

  5. 关注业内动态,有开源社区贡献者优先;

  6. 个性乐观开朗,逻辑性强, 具有良好的沟通能力和团队合作精神

互动游戏方向:

  1. 熟练掌握 Javascript,有移动端 H5 研发基础

  2. 精通 Canvas/WebGL/CSS3,并能够使用原生 API 绘制图形动画

  3. 良好的前端架构能力和项目管理能力,良好的编码习惯,掌握图形图像学知识基础

  4. 有 H5 互动游戏开发经验、3D 游戏开发经验优先

  5. 对技术有强烈的进取心,具有良好的沟通能力和团队合作精神、优秀的分析问题和解决问题的能力

联系方式

可以发简历到 [email protected]

邮件标题:简历-姓名-前端-github

附上你的简历

可以在邮件中写明你希望 base 的地点(北京、杭州)和你目前所在的城市。

使用 Gulp 来构建你的 workflow

Gulp是一个非常棒的自动化构建工具, 笔者在自己的实践和团队分享过程中, 总结整理一系列的有关 gulp 的资料. automating-your-workflow-with-gulp 就是这个么一个 repo.

这个 repo 中包括了:

  • 关于Gulp的一个Slide
  • (计划) 将 上述 slide 整理成一篇(一个系列) tutorial.
  • 一个基于 gulp 4.0 的小 demo (具体的讲解在上面这个 slide 当中)
  • 一些 gulp 插件的简单介绍
  • 一份 recipes, 包括笔者在实践的时候常用的一些任务片段, 还在持续更新当中, 计划包含以下内容
    • 使用 babel 编译 ES2015 的语法
    • 不中断Gulp任务且弹出系统通知
    • 使用命令行参数(yargs) 来控制任务逻辑 (gulp-if) (选择性压缩文件和生成sourcemap)
    • gulp-ignore 选择性过滤某些文件
    • 监听文件变化 (删除不需要的文件, 利用 chalk 打印出漂亮的相关信息)
    • 增量编译 (gulp-cached) (只修改改变的文件)
    • gulp-inject 注入 js, css 资源 (以及如何注入 CDN 等外链资源)
    • gulp-order 给文件排序
    • gulp-replace 用正则做出好玩的事~
    • event-stream 合并多个 stream
    • gulp 与 webpack 相结合
    • 监听 gulpfile 本身, 在 gulpfile 发生变化后 自动gulp.
    • 拆分 gulpfile 为多个文件
    • 其他 欢迎补充
  • 一些参考的资料合集(包括一些视频资料)

如果你对 gulp 感兴趣, 可以和我一起整理相关资料

automating-your-workflow-with-gulp <-- 戳他 戳他 😃

<带人的技术> 读书笔记

这两天读了公司的一本书 <带人的技术>

读罢做一些抄录和记录一些自己的看法, 欢迎大家就管理, 团队建设, Tech Lead 等话题进行讨论 😄

书中观点摘录及体会

教之前需要知道的事情

  • 管理 的基础是 行为分析学
  • 解决问题的关键是行为, 而不是心
  • 什么是教? --- 引导对方做出你希望他做的行为
  • 无论大人小孩, 都希望获得认可
  • 不要一开口就是工作, 闲话家常也很重要
  • 离职率和沟通的程度成反比

主管应该做的事

  • 掌握下属的工作动机和目标
  • 让对方了解你的人性化一面
  • 讨论自己的失败故事, 而不光是成功经验
  • 养成询问的习惯
    • 开放性的问题
      • 做完了么 vs 做的怎么样了?
      • 明白了么 vs 你是怎么理解的?
    • 多听
  • 员工的成长不如预期, 先反省自己
    • 教的太快?
    • 太抽象?
    • 从基础开始?

你能为下属做的事

  • 提前对教的内容进行整理
  • 将教的内容分为知识和技术
  • 针对下属从来没有做过的行为, 教授的步骤要详细
  • 总结优秀员工的工作特点
  • 了解下属知道什么, 能够做什么, 不要想当然

怎么教?

  • 以具体的语言指示或指导下属
    • 什么是具体的?
      • 明确的(无歧义)
      • 可测量(数据指标)
  • 优秀的领导者擅长翻译
    • 将抽象的来自高层的指标转化成具体的可采取行动的行为分配给下属
  • 将目标转化成具体的行为
  • 长期目标拆分成短期目标
  • 无论是教导或指示下属, 每次仅限三件事
  • 制作'不必做'清单
  • 除了分内的工作, 也要教导下属工作的意义和全貌
  • 不要相信'我懂了'这句话
    • 让下属重复一次
    • 请下属根据你指导的内容写出学习心得

称赞很重要

  • 让员工积累'考满分'的成功经验
  • 培养人才的目标有两个
    • 让他们理解不懂得事, 学会原本不会的事
    • 做好本来就会的事

训斥和生气是两码事

  • 愤怒的定义
    • 因为自己制定的目标和现状之间有极大的差距, 在找不到拉近这个距离方法是所产生的情绪
  • 生气是无法解决问题的
  • 愤怒使得行为消失 (适得其反)
  • 训斥一个人, 要着眼这个人的行为 绝不可以就下属的人格和个性做文章, 一定要就事论事
  • 比训斥更重要的是告知下属改变行为的方法, 且改变后及时称赞
  • 记录下属的积极行为的次数, 及时进行正确的评价, 不超过两周

教导人员很多的时候

  • 明确说话的内容, 捋清除自己的思路, 以地图的形式引导他人跟上自己的节奏

读书会/分享会的流程安排

  • 基础:应用:发挥=6:3:1
  • 过程不超过90分钟
  • 20分钟就要改变一次分享/交流形式
  • 每8分钟让学员有互动的机会
  • 活用图片

个人感悟

笔者坚信任何技能都是可习得的, 虽然笔者是技术出身, 但是喜欢分享,
希望以后可以管理一支自己的团队.
所以未来也会看一些管理类的书籍, 做一些理论积累.

也希望未来有了管理经历之后再总结自己的一套管理方法论.

欢迎交流!

44个 Javascript 变态题解析 (下)

承接上篇 44个 Javascript 变态题解析 (上)

第23题

[1 < 2 < 3, 3 < 2 < 1]

这个题也还可以.

这个题会让人误以为是 2 > 1 && 2 < 3 其实不是的.

这个题等价于

 1 < 2 => true;
 true < 3 =>  1 < 3 => true;
 3 < 2 => false;
 false < 1 => 0 < 1 => true;

答案是 [true, true]

第24题

// the most classic wtf
2 == [[[2]]]

这个题我是猜的. 我猜的 true, 至于为什么.....

both objects get converted to strings and in both cases the resulting string is "2" 我不能信服...

第25题

3.toString()
3..toString()
3...toString()

这个题也挺逗, 我做对了 :) 答案是 error, '3', error

你如果换一个写法就更费解了

var a = 3;
a.toString()

这个答案就是 '3';

为啥呢?

因为在 js 中 1.1, 1., .1 都是合法的数字. 那么在解析 3.toString 的时候这个 . 到底是属于这个数字还是函数调用呢? 只能是数字, 因为3.合法啊!

第26题


(function(){
  var x = y = 1;
})();
console.log(y);
console.log(x);

答案是 1, error

y 被赋值到全局. x 是局部变量. 所以打印 x 的时候会报 ReferenceError

第27题

var a = /123/,
    b = /123/;
a == b
a === b

即使正则的字面量一致, 他们也不相等.

答案 false, false

第28题

var a = [1, 2, 3],
    b = [1, 2, 3],
    c = [1, 2, 4]
a ==  b
a === b
a >   c
a <   c

字面量相等的数组也不相等.

数组在比较大小的时候按照字典序比较

答案 false, false, false, true

第29题

var a = {}, b = Object.prototype;
[a.prototype === b, Object.getPrototypeOf(a) === b]

知识点:

只有 Function 拥有一个 prototype 的属性. 所以 a.prototypeundefined.

Object.getPrototypeOf(obj) 返回一个具体对象的原型(该对象的内部[[prototype]]值)

答案 false, true

第30题

function f() {}
var a = f.prototype, b = Object.getPrototypeOf(f);
a === b

f.prototype is the object that will become the parent of any objects created with new f while Object.getPrototypeOf returns the parent in the inheritance hierarchy.

f.prototype 是使用使用 new 创建的 f 实例的原型. 而 Object.getPrototypeOf 是 f 函数的原型.

请看:


a === Object.getPrototypeOf(new f()) // true
b === Function.prototype // true

答案 false

31

function foo() { }
var oldName = foo.name;
foo.name = "bar";
[oldName, foo.name]

答案 ['foo', 'foo']

知识点:

因为函数的名字不可变.

第32题

"1 2 3".replace(/\d/g, parseInt)

知识点:

str.replace(regexp|substr, newSubStr|function)

如果replace函数传入的第二个参数是函数, 那么这个函数将接受如下参数

  • match 首先是匹配的字符串
  • p1, p2 .... 然后是正则的分组
  • offset match 匹配的index
  • string 整个字符串

由于题目中的正则没有分组, 所以等价于问

parseInt('1', 0)
parseInt('2', 2)
parseInt('3', 4)

答案: 1, NaN, 3

第33题

function f() {}
var parent = Object.getPrototypeOf(f);
f.name // ?
parent.name // ?
typeof eval(f.name) // ?
typeof eval(parent.name) //  ?

先说以下答案 'f', 'Empty', 'function', error 这个答案并不重要.....

这里第一小问和第三小问很简单不解释了.

第二小问笔者在自己的浏览器测试的时候是 '', 第四问是 'undefined'

所以应该是平台相关的. 这里明白 parent === Function.prototype 就好了.

第34题

var lowerCaseOnly =  /^[a-z]+$/;
[lowerCaseOnly.test(null), lowerCaseOnly.test()]

知识点:

这里 test 函数会将参数转为字符串. 'nul', 'undefined' 自然都是全小写了

答案: true, true

第35题

[,,,].join(", ")

[,,,] => [undefined × 3]

因为javascript 在定义数组的时候允许最后一个元素后跟一个,, 所以这是个长度为三的稀疏数组(这是长度为三, 并没有 0, 1, 2三个属性哦)

答案: ", , "

第36题

var a = {class: "Animal", name: 'Fido'};
a.class

这个题比较流氓.. 因为是浏览器相关, class是个保留字(现在是个关键字了)

所以答案不重要, 重要的是自己在取属性名称的时候尽量避免保留字. 如果使用的话请加引号 a['class']

第37题

var a = new Date("epoch")

知识点:

简单来说, 如果调用 Date 的构造函数传入一个字符串的话需要符合规范, 即满足 Date.parse 的条件.

另外需要注意的是 如果格式错误 构造函数返回的仍是一个Date 的实例 Invalid Date.

答案 Invalid Date

第38题

var a = Function.length,
    b = new Function().length
a === b

我们知道一个function(Function 的实例)的 length 属性就是函数签名的参数个数, 所以 b.length == 0.

另外 Function.length 定义为1......

所以不相等.......答案 false

第39题

var a = Date(0);
var b = new Date(0);
var c = new Date();
[a === b, b === c, a === c]

还是关于Date 的题, 需要注意的是

  • 如果不传参数等价于当前时间.
  • 如果是函数调用 返回一个字符串.

答案 false, false, false

第40题

var min = Math.min(), max = Math.max()
min < max

知识点:

有趣的是, Math.min 不传参数返回 Infinity, Math.max 不传参数返回 -Infinity 😆

答案: false

第41题

function captureOne(re, str) {
  var match = re.exec(str);
  return match && match[1];
}
var numRe  = /num=(\d+)/ig,
    wordRe = /word=(\w+)/i,
    a1 = captureOne(numRe,  "num=1"),
    a2 = captureOne(wordRe, "word=1"),
    a3 = captureOne(numRe,  "NUM=2"),
    a4 = captureOne(wordRe,  "WORD=2");
[a1 === a2, a3 === a4]

知识点:

通俗的讲

因为第一个正则有一个 g 选项 它会‘记忆’他所匹配的内容, 等匹配后他会从上次匹配的索引继续, 而第二个正则不会

举个例子

var myRe = /ab*/g;
var str = 'abbcdefabh';
var myArray;
while ((myArray = myRe.exec(str)) !== null) {
  var msg = 'Found ' + myArray[0] + '. ';
  msg += 'Next match starts at ' + myRe.lastIndex;
  console.log(msg);
}
// Found abb. Next match starts at 3
// Found ab. Next match starts at 9

所以 a1 = '1'; a2 = '1'; a3 = null; a4 = '2'

答案 [true, false]

第42题

var a = new Date("2014-03-19"),
    b = new Date(2014, 03, 19);
[a.getDay() === b.getDay(), a.getMonth() === b.getMonth()]

这个....

JavaScript inherits 40 years old design from C: days are 1-indexed in C's struct tm, but months are 0 indexed. In addition to that, getDay returns the 0-indexed day of the week, to get the 1-indexed day of the month you have to use getDate, which doesn't return a Date object.

a.getDay()
3
b.getDay()
6
a.getMonth()
2
b.getMonth()
3

都是套路!

答案 [false, false]

第43题

if ('http://giftwrapped.com/picture.jpg'.match('.gif')) {
  'a gif file'
} else {
  'not a gif file'
}

知识点:

String.prototype.match 接受一个正则, 如果不是, 按照 new RegExp(obj) 转化. 所以 . 并不会转义
那么 /gif 就匹配了 /.gif/

答案: 'a gif file'

第44题

function foo(a) {
    var a;
    return a;
}
function bar(a) {
    var a = 'bye';
    return a;
}
[foo('hello'), bar('hello')]

在两个函数里, a作为参数其实已经声明了, 所以 var a; var a = 'bye' 其实就是 a; a ='bye'

所以答案 'hello', 'bye'

全部结束!

总结

由于笔者水平有限, 如果解释有误, 还望指出 😄

通过整理, 笔者发现绝大部分题目都是因为自己对于基础知识或者说某个 API 的参数理解偏差才做错的.

笔者的重灾区在原型那一块, 所以这次被虐和整理还是很有意义呀.

笔者相信 坚实的基础是深入编程的前提. 所以基础书还是要常看啊 🐹

最后这些变态题现在看看还变态嘛?

Array.prototype.forEach(callback) 的 callback 到底执行了几次?

原文链接

事情的起源是这样的, 同事发给我两段代码, 如下:

var a = [1, 2, 3, 1, 2, 3];
a.forEach((item, index) => {
    console.log(index, item);
    if (item === 1) {
    	a.splice(index, 1);
    }
});
// 输出
// 0 1
// 1 3
// 2 1
// 3 3



var a = [1, 2, 3, 1, 2, 3];
a.forEach((item, index) => {
    console.log(index, item);
    if (item === 1) {
    	a.push(1);
    }
});
// 输出
// 0 1
// 1 2
// 2 3
// 3 1
// 4 2
// 5 3

为什么第一个输出四次, 第二个不输出8次呢?

其实这样的事情在我们平常写代码的时候也经常发生,
如果这个改成 for 循环, 或许完全不一样. 那么 forEachcallback 到底执行了多少次呢?

这样的事情当然要看规范了, Array.prototype.forEach() 中文

forEach 方法按升序为数组中含有效值的每一项执行一次callback 函数,那些已删除(使用delete方法等情况)或者未初始化的项将被跳过(但不包括那些值为 undefined 的项)(例如在稀疏数组上)。

forEach 遍历的范围在第一次调用 callback 前就会确定。调用forEach 后添加到数组中的项不会被 callback 访问到。如果已经存在的值被改变,则传递给 callback 的值是 forEach 遍历到他们那一刻的值。已删除的项不会被遍历到。如果已访问的元素在迭代时被删除了(例如使用 shift()) ,之后的元素将被跳过

这里面感觉最重要的是:

  • forEach 遍历的范围在第一次调用 callback 前就会确定
  • 如果已经存在的值被改变,则传递给 callback 的值是 forEach 遍历到他们那一刻的值

看不懂? show me the code

// Production steps of ECMA-262, Edition 5, 15.4.4.18
// Reference: http://es5.github.io/#x15.4.4.18
// 如果 Array.prototype.forEach 没有定义的话
if (!Array.prototype.forEach) {

    Array.prototype.forEach = function (callback/*, thisArg*/) {

        // T 为 callback 的指向, 如果指定的话, 看 step-5
        // k 为 循环的索引
        var T, k;

        if (this == null) {
            throw new TypeError('this is null or not defined');
        }

        // 1. Let O be the result of calling toObject() passing the
        // |this| value as the argument.
        // @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object
        // Object构造函数为给定值创建一个对象包装器。如果给定值是 null 或 undefined,将会创建并返回一个空对象,否则,将返回一个与给定值对应类型的对象。
        // 当以非构造函数形式被调用时,Object 等同于 new Object()。
        var O = Object(this);

        // 2. Let lenValue be the result of calling the Get() internal
        // method of O with the argument "length".
        // 3. Let len be toUint32(lenValue).
        // @see https://stackoverflow.com/questions/8286925/whats-array-length-0-used-for
        // 保证 len 为一个小于 2^32 的整数
        // 这里需要注意, step-7 的终止条件是 k < len;
        // 所以, forEach 遍历的范围在第一次调用 callback 前就会确定
        var len = O.length >>> 0;

        // 4. If isCallable(callback) is false, throw a TypeError exception.
        // See: http://es5.github.com/#x9.11
        // 保证 callback 是个函数
        if (typeof callback !== 'function') {
            throw new TypeError(callback + ' is not a function');
        }

        // 5. If thisArg was supplied, let T be thisArg; else let
        // T be undefined.
        if (arguments.length > 1) {
            T = arguments[1];
        }

        // 6. Let k be 0.
        k = 0;

        // 7. Repeat while k < len.
        while (k < len) {

            // 第 k 项
            var kValue;

            // a. Let Pk be ToString(k).
            //    This is implicit for LHS operands of the in operator.
            // b. Let kPresent be the result of calling the HasProperty
            //    internal method of O with argument Pk.
            //    This step can be combined with c.
            // c. If kPresent is true, then
            // 保证 k 这个索引是 O 的属性
            if (k in O) {

                // i. Let kValue be the result of calling the Get internal
                // method of O with argument Pk.
                // 赋值
                kValue = O[k];

                // ii. Call the Call internal method of callback with T as
                // the this value and argument list containing kValue, k, and O.
                // 调用 callback, T 为 callback 绑定的 this, 参数分别是 item, index, 和 array 本身
                callback.call(T, kValue, k, O);
            }
            // d. Increase k by 1.
            k++;
        }
        // 8. return undefined.
    };
}

刚刚说的两条分别对应
step-3

 // 3. Let len be toUint32(lenValue).
 var len = O.length >>> 0;

和 step-7-c

if (k in O) 

回到第一题

var a = [1, 2, 3, 1, 2, 3];
a.forEach((item, index) => {
    console.log(index, item);
    if (item === 1) {
    	a.splice(index, 1);
    }
});
1. a = [1,2,3,1,2,3]; len = 6
2. k = 0; console.log(0, 1);
3. splice(0, 1) ---> a = [2,3,1,2,3]
4. k = 1; console.log(1, 3);
5. k = 2; console.log(2, 1);
6. splice(2, 1) ---> a = [2,3,2,3];
7. k = 3; console.log(3, 3);
8. k = 4; k not in a;
9. k = 5; k not in a;

第二题比较简单, 新添加的两个 1 都不会遍历

所以两种情况的 while 循环都是 6 次

但是第一种由于 '4' '5' 都不在 array 里面, 所以 callback 只执行了 4 次

第二种情况 callback 执行了 6 次

好啦, 你听明白了嘛~

参考资料

2016回顾 --- 易企秀H5项目重构总结

易企秀H5项目重构总结

重构的缘由

易企秀(http://www.eqxiu.com/)在2015年12月, 我入职的时候, 整个项目是一个大型的单页应用. 包括了首页, 作品列表页, 编辑器, 作品详情, 秀场等. 由于业务的急剧膨胀, 大项目组已经无法满足敏捷开发的需要, 产品会变得冗长低效, 产品层面上整个主站需要进行剥离, 工程项目的拆分及重构也势在必行. 易企秀的核心业务是 H5编辑和预览. 由于迭代迅速, 功能往往以实现为主, 缺乏设计, 代码略显混乱. 前端组认为为了未来支持更为丰富的组件和编辑功能, 需要对预览还有编辑器项目进行一次’重构’.

image
(上图: 场景预览)

image
(上图: 场景编辑)

回头看这次决定, 出发点是非常好的, 但是笔者认为重构的安排略显仓促.

一, 项目的拆分, 重构和优化是不应该一起进行的. 三者想要达到的目的各不相同. 而最初的目的是不清晰的, 只是希望完整实现一个更好的版本, 这样势必导致重构人员会发散, 最终的结果是重写了整个项目… 造成修改的代价及其高昂, 而且开发周期会很长, 测试的压力会很大.

二, 项目重构的前提是对项目有了充足的了解, 但是当初无论是文远还是我都对核心业务知之甚少(作为当初资历最浅的两个人且无大型项目重构经验). 所以在重构的整个过程中, 对业务理解所花费的时间非常多, 需要频繁的与同事进行交流, 不可避免的会出现很多理解的偏差, 也造成了很多业务逻辑上的 bug.

不过, 重构的大幕在2016年3月正式开启了.

阶段

为了好理解, 先将 H5预览项目称之为 view 项目, 将编辑器项目称之为 editor 项目.

整个重构分成5个阶段

  1. view 项目核心功能拆分, 技术选型及实践: 面向对象**的第一次验证, ES6, gulp 引进, 第一版实现所有功能
  2. view 灰度及 bug 修复
  3. 编辑器功能边界划分, view 项目模块化改造为 core 项目
  4. 编辑器项目核心框架搭建完成, 多人参与, 完成90%功能重构
  5. 编辑器灰度, 重构工作全部完成
  6. 合并 view, core 项目 (未来工作)

第一阶段: 技术选型及实践

文远在一开始进行核心功能重写时, 选择了 OO 进行重构, 经过讨论, 我们认为 OO 非常适合 H5 多组件的情形. 非常方便未来扩展新的功能. 而且由于 view 项目是一个非常独立的项目, 所以我们希望其不依赖任何框架, 这样才有机会融入到其他框架中去(当时的情况是编辑器是用 ng1.2, 并且当时认为可能会出现手机编辑器等多种 H5编辑器).

在讨论到 jQuery 的时候, 由于项目中大量使用了 jQuery 的选择器等语法也使用了 jQuery 的相关插件, 从开发工作量上考虑, 我们决定保留.

在是否使用 ES6 语法上, 团队产生了较大分歧, 笔者当时更为激进, 希望采用 typescript 来进行编写, 理由是 view 项目将作为种子项目, 为未来的多编辑器进行服务, 希望保证质量, 团队其他成员认为 ts 或者 es6 的使用增加了学习成本, 最后妥协的结果是我们选择了部分 es6 语法. 对于这个妥协, 我个人是满意的, 事实证明, es6 在当前前端开发中大行其道, 应该会在未来的开发中成为默认语言.

由于当初笔者正好对项目组分享了 gulp4.0(http://slides.com/xy2/gulp-1#/) 的使用, (当初项目组还在使用 grunt 进行构建), 所以我们采用了 gulp4.0 作为构建工具.

我们决定将 less 转化成 sass.

在是否使用模块化开发问题上, 团队也发生了分歧, 由于当初没有人有模块化开发的经验, 所以模块化开发的提议被否决了, 一个理由是当初的方案就是采用挂载全局变量的方式, 比较好理解, 不过当初全局变量非常随意混乱. 最后的结果是 项目使用命名空间的形式, 将所有的组件都按照功能挂载到 EQX 这样一个全局变量上. 最终形成了 EQX.init, EQX.util, EQX.const, EQX.API, EQX.HOST, EQX.tpl, EQX.effect 等子模块, 场景类, 组件类, 辅助类等则直接挂载到 EQX上.

image

(上图为EQX全局变量的部分属性)

我们设计了大量的辅助类来解决复杂问题, 例如 EQX.formManager(处理表单提交逻辑), commentManager(处理表单逻辑), bgm(处理背景音乐), pageScroll(处理翻页逻辑), progressbar(处理进度条逻辑) 等

除此之外,

为了避免在业务逻辑中兼容各式各样的老数据, 我们增加了一层数据改造层(adapter), 来集中对发现的老数据, 坏数据做一次转换(但是因为对项目本身了解不深, 在改造数据时, 无法预计到所有的数据结构, 所以导致改造逻辑简单, 无法真正做到所有数据的兼容, 甚至改造错误), 减轻业务逻辑.

我们统一了异步处理的方式: 全部使用 Promise, 原则上不再使用回调函数.

我们规范了初始化场景的逻辑, 分为以下步骤

  1. 后端渲染场景数据
  2. 前端根据页面来源区分 H5预览, APP 应用内预览, PC 预览, 是否需要密码登录,初始化页面布局 或者进行微信授权等操作.
  3. 获得页面数据(异步获取尾页, 广告页等). adapter 进行数据改造(更换微信用户数据等)
  4. 页面渲染

对整个 view 项目中出现的业务概念我们做了如下抽象.

EqxScene(场景)
EqxPage(页面), EqxLongPage(长页), EqxXiuPage(秀版, 广告页)…
EqxBackground(背景)
EqxBgm(背景音乐)
EqxComp(组件)
EqxText, EqxImage, EqxInput, EqxInputPhone...
EqxPageEffect(页面特效)
EqxSnowEffect ...
EqxPageScroll(翻页器)
book, card ... (采用策略模式, 抽象出不同的翻页算法)
EqxProgressBar(进度条)
EqxManager(辅助类)
EqxEventManager, EqxSoundManager, EqxStatManager, EqxCommentManager, EqxFormManager
EqxAgent
EqxSoundAgent…

大致关系图如下.

image

按照这样的规划, 我们完成了第一个里程碑, 实现了现有功能的全部改造.

灰度及 bug 修复

由于 view 项目是公司最核心的项目, 我们经过广泛讨论确定了逐步灰度的方案: 按照场景发布时间进行划分逐步进行灰度, 且灰度策略随时进行调整.

在历时一个月的时间内, 我们累计处理了几百个 bug. 这也反映出对业务理解不深. 不过感谢测试团队, 也感谢我们的项目管理方式(jira), 可以让我们有序的进行 bug 修改.

编辑器功能边界划分及模块化实践

在灰度进行中期, 笔者开始了第三阶段的开发, H5编辑器的拆分 和 view 项目的模块化改造, 升级成 core (核心组件库) 项目

笔者在初期定下了如下重构理念

  1. 数据驱动
  2. 依赖组件库.
    为了保证编辑器和预览时组件渲染一致, 编辑器将所有和渲染相关的逻辑全部交由组件库.
  3. 全指令 + 模块化
    笔者考虑到未来的团队合作, 希望尽早开始模块化的尝试, 参考了以下文章
    xufei/blog#29 Angular 1.x和ES6的结合
    kuitos/kuitos.github.io#34 Angular1.x + ES6
    笔者决定, 升级 ng1.2 至 1.5. 整个项目采用 ES6 + (webpack + gulp)+ 全指令的形式进行模块化的重构.
  4. service 做到 简易统一的 API; 职责单一; 少依赖; 可复用
  5. 编辑区鼠标操作全部重写

经过项目拆分, 笔者划定了项目的整个边界
包括 模板, 素材, 页面管理, 编辑, 场景设置, 设计师功能等.
其中涉及到编辑组件的功能边界如下
image

在第四个阶段, 笔者负责了搭建整个项目框架 (保证项目可以最小化运行 + 核心编辑能力) 基本框架如下
image

编辑区基本框架如下
qq20170116-3

随后, 我们按照模块化的方式进行了项目划分和开发. 一个典型的模块及子模块(样式编辑-触发)的代码组织如下
qq20170116-4

经过近两月的努力, 我们小组(四人)完成了编辑器的重构工作, 随后进行了第二次灰度和 bug 修改(又是上百个 bug…..), 这次我们采用了用户注册时间和权限的策略进行灰度.

code review 实践

在这段时间, 组内成员进行了 code review 实践, 效果比较好. 特此记录.

笔者认为 code review 是一个集合了知晓同伴工作, 协调工作进度, 讲解代码, 提高团队成员水平, 统一风格等........诸多好处的软件开发辅助活动...... code review 是一种形式, 绝不是目的.

在实践中, 我们每天至少花一小时的时间进行代码交流. 鼓励团队成员每天进行有意义的代码提交 (至少1-2次), 这样可以及时的 review, 及时的给出建议, 逐步改善编码习惯. 具体在 code review 进行过程中

  1. 每个人简要展示可交付的内容
  2. 选择性对代码进行讲解
  3. 针对代码中的问题进行分析和讲解, 如果能带出一些知识点来, 可以进行展开
  4. 对明日的工作讨论, 如果有技术上的难点可以和大家探讨交流.
    如果功能确实比较少, 那么 code review 会主要以技术分享为主. 分享的内容包括但不局限于 angular 的各种中高级用法, Promise, webpack, 设计模式, 软件构建的优秀实践等.

毕竟团队开发, 大家好才是真的好 :D

现在重构已经到一段落. 不过现在仍然存在 view, core, editor 三个项目. 目前 core 项目已经非常稳定的工作, 提供渲染和编辑场景的内部逻辑. core 项目目前支持 PC 编辑器, 广告项目场景渲染 以及 安卓混合模式的开发.

下一阶段需要将 core, view 进行代码的合并形成真正意义上的 核心组件库.

总结

经过这次历时接近一年的’重构’, 笔者认为既有良好的实践, 也有诸多可以改进的地方.

  1. 在项目初期确定了技术选型(ES6, OO, 模块化等), 编码规范等, 保证了开发过程中大方向的一致性.
  2. 需要感谢团队协作和敏捷实践, 可以让团队保持较好的开发节奏
  3. 需要感谢测试人员的持续跟进, 灰度后项目虽有 bug, 但基本都在掌握之中
  4. 当然最需要感谢的是重构团队兢兢业业, 任劳任怨 :D, 虽然加班是常态, 但是长期的 code review 实践, 对于笔者和团队在技术和协作上都有很大的提高.

当然, 总结重构, 我们也有很多不足:

  1. 前期对于重构的定位不清, 重构没有时刻表, 整体进度不好把握.
  2. 重构前对重构项目的业务了解不够深入
  3. 重构前对重构项目的数据结构了解不全
  4. 成员对 OO 及设计模式, NG 中高级概念 的理解不深, 打铁还需自身硬啊!

对于最后的重构结果笔者是较为满意的, 运行了近两个月的时间, 在新需求的开发过程中, 由于前期的顶层设计, 每一个组件都’理所当然’有他的位置, 整个架构很好的支撑了业务的发展. 笔者希望, 未来的重构最好不要伤筋动骨而应该是渐进增强. 总之, 感谢这次经历 :D

导出微信通讯录到 Excel

导出微信通讯录到 Excel

原文发自我的 github 博客

缘由

小 N 同学通讯录太多,希望可以导出到 Excel 中,网上大部分做法都需要安装软件或者还有自己整理数据,太麻烦。

我们来试一试可不可以通过前端的思路来解决这个问题。

思路

  1. 拿到通讯录
  2. 导出到 Excel

既然是前端工程师,那么最简单的方式就是登录微信 web 版, 直接去看微信活动通讯录的接口。

我们看一下

image

可以看到,由于微信后端返回数据中中文出现了乱码,暂且没有想好如何处理这些乱码。

但是界面中的中文确实正常的,我们看一下有没有其他别的方法呢?

image

通过看网页结构,我们发现这是一个用 angularjs 1.x 编写的网页应用(出现了ng-style ng-repeat等关键词)

我们知道 angularjs 中的双向绑定,一般变量都会挂在 scope 上

既然所有的联系人都出现在了 $('.scroll-wrapper .J_ContactScrollBody')中,那我们看看这个列表关联的 scope 中是否含有这些信息

var scope = angular.element($('.scroll-wrapper .J_ContactScrollBody')).scope();
var allContacts = scope.allContacts;

果不其然,在 $('.scroll-wrapper .J_ContactScrollBody') 关联的 scope 上挂载有 allContracts

(其实在观察的过程中,发现微信的工程师把所有的联系人信息放到了 window._contracts 上了,这也是一种方法。)

拿到数据之后接下来就是导出到 Excel 了,这里选用了 js-xlsx 库,其中的细节不再赘述

直接看一下源码

源码+注释

// 点击通讯录 tab
$('.web_wechat_tab_friends').click();

// 等待几秒钟...

// 获取通讯录列表
// 方法一
var scope = angular.element($('.scroll-wrapper .J_ContactScrollBody')).scope();
var allContacts = scope.allContacts;

// 方法二
// var allContacts = Object.keys(window._contacts).map(k => window._contacts[k]);

// 过滤真实的用户
var contacts = allContacts.filter(c => c.UserName);

// 下载 excel 脚本
loadScript('https://oss.sheetjs.com/js-xlsx/xlsx.full.min.js')
    .then(() => {
        console.log('download js-xlsx successful ');

        var config = {bookType: 'xlsx', bookSST: false, type: 'binary'};//这里的数据是用来定义导出的格式类型
        var wb = {SheetNames: ['Sheet1'], Sheets: {}, Props: {}};
        // 通过json_to_sheet 转成单页(Sheet)数据
        wb.Sheets['Sheet1'] = XLSX.utils.json_to_sheet(formatContacts(contacts));
        var fileName = '微信通讯录' + '.' + (config.bookType == "biff2" ? "xls" : config.bookType);
        saveAs(new Blob([s2ab(XLSX.write(wb, config))], {type: 'application/octet-stream'}), fileName);

    });

// ---- helper functions -----

/**
 * 将 contacts 转化成你需要的格式
 * 这里可以任意发挥
 * @param contacts
 * @returns {*}
 */
function formatContacts(contacts) {
    return contacts.map(({NickName, Sex, RemarkName}) => {
        return {
            '昵称': NickName,
            '备注': RemarkName
        }
    })
}

/**
 * 加载 script
 * @param url
 * @returns {Promise}
 */
function loadScript(url) {
    return new Promise((resolve) => {
        var head = document.getElementsByTagName('head')[0];
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.onload = resolve;
        script.src = url;
        head.appendChild(script);
    })

}

/**
 * 下载文件
 * @param obj
 * @param fileName
 */
function saveAs(obj, fileName) {
    var a = document.createElement('a');
    a.download = fileName || '下载';
    a.href = URL.createObjectURL(obj);
    a.click(); // 模拟点击实现下载
    setTimeout(function () {
        URL.revokeObjectURL(obj); // 释放 objectURL
    }, 100);
}

/**
 * 字符串转字符流
 * @param s
 * @returns {ArrayBuffer}
 */
function s2ab(s) {
    var buf = new ArrayBuffer(s.length);
    var view = new Uint8Array(buf);
    for (var i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
    return buf;
}

参考文章

100*100的 canvas 占多少内存?

题目

100*100的 canvas 占多少内存?

三年前端,面试思考 中提到了一个题目,非常有新意,这里分享一下当时面试的思考过程。

解题思路

其实真正的答案是多少我并不清楚,面试过程中面试官也不期待一个准确的答案,而是看你的思考过程。

如果了解过 Canvas 且做过滤镜相关的工作,可能调用过 imageData = ctx.getImageData(sx, sy, sw, sh); 这个 API。我记得这个 API 返回的是一个 ImageData 数组,包含了 sx, sy, sw, sh 表示的矩形的像素数据。

而且这个数组是 Uint8 类型的,且四位表示一个像素。

我在面试的时候只能想起来这些信息。猜想一下,我们在定义颜色的时候就是使用 rgba(r,g,b,a) 四个维度来表示,而且每个像素值就是用十六位 00-ff 表示,即每个维度的范围是 0~255,即 2^8 位,即 1 byte, 也就是 Uint8 能表示的范围。

所以 100 * 100 canvas 占的内存是 100 * 100 * 4 bytes = 40,000 bytes。

声明

这里的答案并不一定准确。

关于 alpha 的争论

有同学指出,alpha 不是 0-100 么?我起初也有这样的疑问,不过这篇文章中 https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas

The data property returns a Uint8ClampedArray which can be accessed to look at the raw pixel data; each pixel is represented by four one-byte values (red, green, blue, and alpha, in that order; that is, "RGBA" format). Each color component is represented by an integer between 0 and 255.

也就是说即便是 alpha 也是 0-255

那么如何表示 alpha 呢?

接下来这段代码中

可以看出,只需要用 0-255 表示 0-100 就可以啦~

参考资料

CanvasRenderingContext2D.getImageData() 返回一个ImageData对象,用来描述canvas区域隐含的像素数据,这个区域通过矩形表示,起始点为(sx, sy)、宽为sw、高为sh。

Uint8ClampedArray 描述了一个一维数组,包含以 RGBA 顺序的数据,数据使用 0 至 255(包含)的整数表示。

彻底搞懂 async & defer

彻底搞懂 async & defer

TL;DR

两者都会并行下载,不会影响页面的解析。

defer 会按照顺序在 DOMContentLoaded 前按照页面出现顺序依次执行。

async 则是下载完立即执行。

兼容性

普通 script

先来看一个普通的 script 标签。

<script src="a.js"></script>

浏览器会做如下处理

  • 停止解析 document.
  • 请求 a.js
  • 执行 a.js 中的脚本
  • 继续解析 document

defer

<script src="d.js" defer></script>
<script src="e.js" defer></script>
  • 不阻止解析 document, 并行下载 d.js, e.js
  • 即使下载完 d.js, e.js 仍继续解析 document
  • 按照页面中出现的顺序,在其他同步脚本执行后,DOMContentLoaded 事件前 依次执行 d.js, e.js。

async

<script src="b.js" async></script>
<script src="c.js" async></script>
  • 不阻止解析 document, 并行下载 b.js, c.js
  • 当脚本下载完后立即执行。(两者执行顺序不确定,执行阶段不确定,可能在 DOMContentLoaded 事件前或者后 )

其他

  • 如果 script 无 src 属性,则 defer, async 会被忽略
  • 动态添加的 script 标签隐含 async 属性。

结论

  • 两者都不会阻止 document 的解析
  • defer 会在 DOMContentLoaded 前依次执行 (可以利用这两点哦!)
  • async 则是下载完立即执行,不一定是在 DOMContentLoaded 前
  • async 因为顺序无关,所以很适合像 Google Analytics 这样的无依赖脚本

Reference

实现一个 LazyMan

实现一个 Lazyman:

LazyMan(“Hank”)输出:
Hi! This is Hank!

LazyMan(“Hank”).sleep(10).eat(“dinner”)输出
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner~

LazyMan(“Hank”).eat(“dinner”).eat(“supper”)输出
Hi This is Hank!
Eat dinner~
Eat supper~

LazyMan(“Hank”).sleepFirst(5).eat(“supper”)输出
//等待5秒
Wake up after 5
Hi This is Hank!
Eat supper

function LazyMan(name) {
    return new _LazyMan(name);
}

class _LazyMan {
    constructor(name) {
        this.tasks = [];
        this.canExecute = true;

        var task = function lazyMan(cb) {
            console.log(`Hi This is ${name}`);
            cb();
        };

        this.addTask(task);
    }

    eat(food) {
        var task = function eat(cb) {
            console.log(`Eat ${food}~`);
            cb();
        }
        this.addTask(task);
        return this;
    }

    sleep(sec) {
        var task = function sleep(cb) {
            setTimeout(() => {
                console.log(`Wake up after ${sec}`);
                cb();
            }, sec * 1000);
        };
        this.addTask(task);
        return this;
    }

    sleepFirst(sec) {
        var task = function sleepFirst(cb) {
            setTimeout(() => {
                console.log(`Wake up after ${sec}`);
                cb();
            }, sec * 1000);
        };
        this.addTask(task, true);
        return this;
    }

    addTask(task, first) {
        if (first) {
            this.tasks.unshift(task);
        } else {
            this.tasks.push(task);
        }
        this.executeTasks();
    }

    executeTasks() {
        setTimeout(() => {
            if (!this.canExecute) return;
            this.canExecute = false;
            var task = this.tasks.shift();

            if (task) {
                // console.log(task.name);
                task(() => {
                    this.canExecute = true;
                    this.executeTasks();
                });
            }
        }, 0);

    }
}

// LazyMan('Hank');
// LazyMan('Hank').eat('dinner').eat('supper')
// LazyMan('Hank').sleep(2).eat('dinner');
// LazyMan('Hank').eat('dinner').eat('supper');
LazyMan('Hank').sleepFirst(2).eat('supper');

送给产品经理一段代码 (!(~+[]) + {})[--[~+""][+[]] * [~+[]] + ~~!+[]] + ({} + [])[[~!+[]] * ~+[]]

送给产品经理一段代码 让他放到 console 去

(!(~+[]) + {})[--[~+""][+[]] * [~+[]] + ~~!+[]] + ({} + [])[[~!+[]] * ~+[]]

其实这段代码是我的同事发给我的, 我定睛一看一定有坑, 于是准备破解一番

其实这里面涉及到的知识点无非三个

  • 数据类型的转换
  • 位运算(按位非)
  • 运算符的优先级

分析之前我推荐大家看几篇文章

首先我们把代码进行拆分

(!(~+[]) + {})[--[~+""][+[]] * [~+[]] + ~~!+[]]
+
({} + [])[[~!+[]] * ~+[]]

简单来看就是
(A)[B] + (C)[D]

首先来看 A !(~+[]) + {}

+[] -(数据类型转换)-> 0
~+[] --> ~0 -(位运算)-> -1
!(~+[]) --> !(-1) -(类型转换)-> false
(!(~+[]) + {}) --> false + {} -(类型转换)-> 'false[object Object]'

再来看 B --[~+""][+[]]*[~+[]] + ~~!+[]


[~+""] --> [~0] --> [-1]
+[] --> 0
--[~+""][+[]] --> --[-1][0] --> --(-1) --> -2

[~+[]] --> [~0] --> [-1]

~~!+[] --> ~~!0 --> ~~true --> ~-2 -> 1

B --> -2 * [-1] + 1 --> 2 + 1 --> 3

那么 (A)[B] --> 'false[object Object]'[3] --> 's'

再来看 C ({} + [])

这里的 {} 其实是个代码块

所以等价于 +[][object Object]

再来看 D [~!+[]] * ~+[]

[~!+[]] --> [~!0] --> [!1] --> -2
~+[] --> ~0 --> -1
D -> -2 * -1 --> 2

所以 (C)[D] --> '[object Object]'[2] --> 'b'

那么

(!(~+[]) + {})[--[~+""][+[]] * [~+[]] + ~~!+[]] + ({} + [])[[~!+[]] * ~+[]]
--> (A)[B] + (C)[D) 
--> 's' + 'b' 
--> 'sb'

谢谢观看 😄

现在可以发给产品经理了

让他打开 chrome, (windows: F12, mac: command+shift+j)

粘贴 (!(~+[])+{})[--[~+""][+[]]*[~+[]] + ~~!+[]]+({}+[])[[~!+[]]*~+[]]

这就是我们前端一直想对你说的话

参考文章

文中对产品经理的冒犯仅为了行文效果 😅 前端和产品是相亲相爱的一家人 😆

易企秀H5项目架构梳理

回顾

在上一篇易企秀H5项目重构总结 中, 笔者详细记录了重构项目的缘由, 技术选型及实践, 灰度及 bug 修复, 编辑器功能边界划分及模块化实践, code review 实践 及 总结.

距离上次总结已经过去了一年的时间, 在这段时期, 笔者又基于当初的项目架构, 横向扩展了 wap 编辑器和 混合模式编辑器, 其中 wap 编辑器在线上正常运营, 而混合模式则碰到了很多技术瓶颈, 虽达到测试上线标准, 但是最终夭折腹中, 没有正式上线; 除此之外, 我与明纯 共同重构了另外一项复杂业务: 组合, 图层和功能模板. 最近, 有关组合, 图层的设计和一些复杂的算法明纯会在他的博客分享, 敬请期待 :D

8661515316159_ pic_hd
上图为 wap 编辑器

group3
上图是组合的操作

回过头看上一篇总结, 有些**和方法是非常具有前瞻性的, 有些决定则看起来缺乏魄力, 显得略显保守. 接下来, 笔者将会就第一次重构进行复盘, 对目前项目的构建过程, 核心组件库, 编辑器架构等方面做最后一次易企秀 H5 项目的架构梳理.

第一次重构复盘

第一次重构的正确选择:

  • 淘汰 grunt, 采用 gulp + webpack, 大大提高了构建速度
  • 引入面向对象**, 构建核心组件库, 至今组件库都是整个项目的基石. Core + 多编辑器实例构成了目前的多编辑器框架.
  • 引入 ES6, 面向下一代语言标准
  • 编辑器全指令+模块化开发
  • 积极 code review
  • 灰度方案

第一次重构时保守的选择:

  • 没有一开始就拥抱模块化标准, 采用了挂载全局变量的方式
  • 没有彻底的 OO 化, 对于触发, 动画, 事件系统等仍然采用面向过程式的编程
  • 没有做好是否使用新框架的调研 保守的采用了当时的 ng1 + jquery 的方案

重新来过, 有哪些可以做的更好

  • 明确目标, 拆分项目, 灰度方案, 技术选型, 重构和优化 应该分步走. 避免时间上无法量化的重构
  • 数据结构的梳理和规范应该是重构的第一步
  • 数据规范后应该采用 typescript 来开发项目, 从而规范和提升开发效率
  • 做好是否使用新框架的调研 或许一开始采用 vue 或者 react 可以解决现数据流动和 dom 操作的诸多问题
  • 积极做项目的抽象
    • 抽象出样式库 (避免后续的黑白模式修改起来费劲)
    • 抽象出基础组件库
  • 坚持 code review

架构梳理

构建方案

  • H5编辑器: webpack
  • H5预览: gulp + webpack

编辑器构建方案

所有编辑器都是相对独立的单页 app, 使用 webpack 进行构建

  • util.js 提供了对本地构建的版本号 (格式: ${git-commit-hash}_${time})
  • webpack.helper.js 提供 webpack 公共的构建逻辑, 其中使用了
    • loaders
      • file-loader 处理静态文件
      • html-loader 处理 html 文件的压缩
      • scss-loader, less-loader, css-loader 处理样式文件
      • babel-loader, awesome-typescript 处理 ts 文件
      • ng-annote-loder, babel-loader 处理 js 文件, ng-annote 处理 ng 的依赖注入
    • plugins
      • CleanWebpackPlugin 清理构建文件
      • WebpackZipPlugin 生成 zip 打包文件
      • ExtractTextPlugin 内联 CSS 抽出文件
      • UglifyJsPlugin 压缩 js 文件
      • HtmlWebpackPlugin 根据模板生成 index.html
  • 使用 webpack-dev-server 进行热更新构建
  • webpack.config.js 通过 yargs.argv 判断构建的编辑器入口, 调用 webpack.helper.js 构建

H5 预览构建方案

H5 浏览项目构建流程相对复杂, 采取 webpack 处理程序逻辑打包 + gulp 处理流程 的方式 进行构建

  • 使用 webpack.view.js 打包所有资源
  • 使用 gulpfile 处理其余流程
    • build:inject index.html 注入静态资源链接
    • index:ftl, index:mobile, 根据 index.html 生成后端模板和移动端模板
    • css:http 移动端模板中对 css 中的资源文件, 需要把相对路径转变成绝对路径
    • check 检查生成的资源是否完整
    • zip-all, zip-ftl 打包静态资源

项目梳理

  • core: 核心组件库
  • view: H5 预览
  • editor: H5 编辑器
  • wapEditor: wap 编辑器
  • metaEditor: 标签编辑器
  • appEditor: 混合模式编辑器

目录结构梳理

顶级目录
├── README.md 项目说明
├── build 构建目录
├── docs 文档目录
├── env 环境目录
├── eqxiu.json 自动化部署配置
├── gulpfile.view.js H5预览 gulp 配置
├── node_modules
├── package.json
├── src 源文件目录
├── tsconfig.json typescript 配置
├── types
├── util.js 构建工具的 util
├── webpack.config.js webpack
├── webpack.helper.js 公共 webpack 配置
├── webpack.view.js H5 预览 webpack 配置
源码目录 
src
├── appWpEditor app 混合模式
├── common js common
├── core 核心组件库
├── editor 编辑器
├── metaWpEditor 贺卡编辑器
├── ngcommon ng common
├── view H5 view
└── wpeditor wap 编辑器

Core 核心组件库

核心组件库维护了所有易企秀H5的所有核心组件及其内在逻辑

核心类及继承关系

  • EqxScene 场景
  • EqxPage 页面
    • EqxLongPage 长页面
    • ...
  • EqxLayer 图层
  • EqxPageEffect 页面特效
    • FingerEffect 指纹特效
    • ...
  • EqxItem 组件基类
    • EqxGroup 组合
    • EqxComp 组件
      • EqxText
      • EqxImage
      • ...
      • EqxForm
        • EqxCheckbox
        • EqxRadio
        • EqxDropDownList
        • EqxInput
          • ....
      • wx 微信组件
        • EqxWxImage
      • ...

辅助类

  • ProgressBar 进度条
  • EqxArrow 翻页箭头
  • ...
  • EqxPageScroll
    • 策略模式
    • book, card, cube, fadeIn, fall, flip, horizontal, horizontalinertia, horizontalPush, horizontalSingle, scale, swap, vertiaclinertia, vertical, verticalPush, verticalSingle
  • 工厂
    • PageFactory 页面工厂
    • CompMap 组件工厂
    • PageEffectLoader 特效异步加载工厂
  • manager
    • EqxCommentManager 评论管理
    • EqxFormManager 表单管理
    • EqxSoundManager 音效管理
    • EqxStatManager 统计数据管理
    • EqxScreenManager 幕管理
    • EqxGroupManager 组合管理
    • EqxFtManager 功能模板管理
      • EqxFtRule 功能模板规则
      • EqxFtTerm 功能模板条款
      • EqxFtEditPropManager 功能模板组件可编辑元素管理

H5 预览流程梳理

view预览步骤

  1. 获取场景的基本信息(id, code...)
  2. 根据用户类型调整 API 域名
  3. 获取加载页的配置
  4. 获取用户微信里的信息
  • 从缓存里取用户信息 如果有就直接用
  • 如果没有 请求用户授权
  1. 获取场景列表的数据, 矫正数据 (adapter)
  2. 微信信息获取后 pv打点和微信组件设置
  3. 渲染场景

易企秀 JavaScript 风格指南

编辑器架构

参考 易企秀H5项目重构总结 编辑器功能边界划分及模块化实践, 核心**并未发生变化, 摘录如下

编辑器理念

  1. 数据驱动
  2. 依赖组件库.
    为了保证编辑器和预览时组件渲染一致, 编辑器将所有和渲染相关的逻辑全部交由组件库.
  3. 全指令 + 模块化
    笔者考虑到未来的团队合作, 希望尽早开始模块化的尝试, 参考了以下文章
    xufei/blog#29 Angular 1.x和ES6的结合
    kuitos/kuitos.github.io#34 Angular1.x + ES6
    笔者决定, 升级 ng1.2 至 1.5. 整个项目采用 ES6 + (webpack + gulp)+ 全指令的形式进行模块化的重构.
  4. service 做到 简易统一的 API; 职责单一; 少依赖; 可复用
  5. 编辑区鼠标操作全部重写

经过项目拆分, 笔者划定了项目的整个边界
包括 模板, 素材, 页面管理, 编辑, 场景设置, 设计师功能等.
其中涉及到编辑组件的功能边界如下
image

编辑器基本框架如下
image

编辑区基本框架如下
qq20170116-3

一个典型的模块及子模块(样式编辑-触发)的代码组织如下
qq20170116-4

后记

从2016年6月开始, 整个项目规模已经达到20w 行代码的规模, 作为易企秀的核心业务, 日 pv 稳定在1亿左右, 是个非常值得骄傲的成绩.

自2017年1月开始, 整个项目就按照当初设想的那样, core+多编辑器实例, 没有太大的变化, 至此笔者有些欣慰, 也有些忧伤.

欣慰的是一年多前的重构架构可以支撑后来一年的迭代, 说明笔者架构的整体思路没有太大问题. 忧伤的也正在此, 笔者知道, 这个架构还远远不够, 还有很多地方可以优化调整. 然而并没有很多大的改动.

当你看到这篇文章时, 笔者已经离开易企秀了, 希望后继者可以提出更加健壮的方案, 助力易企秀再上新台阶.

送给自己的话

在天地之间找一个自己的位置

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.