如果你想聊聊,可以给我发邮件,也可以直接 Twitter DM。
sofish / learn-js Goto Github PK
View Code? Open in Web Editor NEW老婆想学 js 这件事就是一个政治任务
老婆想学 js 这件事就是一个政治任务
如果你想聊聊,可以给我发邮件,也可以直接 Twitter DM。
觉得 angular 2 的文档太难懂,想自己写,写到 template 渲染基本就挂,工作量太大,得出一个结果就是该作死一定会死,不想做死其实就写写 demo 或者有空就给人打打 patch 好了,自己写真的有点麻烦。不过在开始的时候写了一个简单的路由。实现一个基本功能,基本走向是:
- constructor():
--> this.base = HTML_BASE || '/'
--> this.routes = [], this.routes.notfound = <rotue>
--> this.current = <route> || null
--> event: <history> `popstate`, <link> `click || touchstart`
--> CALL_AFTER_REFRESH: this.go(url)
- go(url): redirect to some path
- when(/url/with/:dynamic/params, createdCallback, destroyedCallback): listen to some path
所以如果功能这样,最终 api 看起来就是:
// notfound | 404
router.when(created, destroyed);
// route with dynamic param
router.when('/some/:id/path', created, destroyed);
// navigate to a specific url
// when pathname is /base/some/path and base is /base, url equal to /some/path
route.go(url);
<!-- if not listened, route to `HTML_BASE/404` -->
<a href="/some/internal/path">internal</a>
<!-- open in new window, then follow the rules like above -->
<p><a href="/some/internal/path" target="_blank">target="_blank"</a></p>
<!-- trigger default behavor -->
<p><a href="http://sofi.sh">external</a></p>
实在是太懒了,也懒得看流行框架代码如何撸,先现撸一个可用版本:
class Router {
constructor() {
try {
this.base = document.querySelector('base').getAttribute('href').replace(/(?:\/+)?$/, '');
} catch (e) {
this.base = '';
}
this.routes = [];
this.current = null;
// back
window.addEventListener('popstate', function() {
this.go(this._url());
}.bind(this));
// listen to links
this._navigate();
// wait for routes
setTimeout(function() {
this.go(this._url());
}.bind(this));
}
when(url, created, destroyed) {
var params = [];
// by default, route to /404
if(typeof url === 'function') {
return this.routes.notfound = {
url: '/404', created: url,
destroyed: created,
params
};
}
url = url.replace(/:([^\/]*)/g, function(match, param) {
params.push(param);
return '([^\\/]+)';
});
url = new RegExp(`${url}$`);
if(typeof created === 'function') {
this.routes.push({url, created, destroyed, params});
}
}
go(url, isExternalLink) {
if(typeof path === 'number') return history.go(url); // back or forward
if(isExternalLink) return location = url; // external links
if(url[0] !== '/') url = '/' + url;
if(url !== this.url) {
var isFound = this.exec(url);
// it's safe to set STATE and TITLE to null
this.url = isFound ? url : '/404';
history.pushState(null, null, this.base + this.url);
}
}
_url() {
return (location.pathname + location.search + location.hash).slice(this.base.length);
}
_navigate() {
let event = typeof window.ontouchstart === 'undefined' ? 'click' : 'touchstart';
document.addEventListener(event, function(e) {
var target = e.target;
if( // Exclude:
target.tagName !== 'A' || !target.href || // - non links
target.getAttribute('target') === '_blank' // - link open in new tab or window
) return;
var url = target.href.split(/\/+/);
var host = url[1];
url = '/' + url.slice(2).join('/');
if(host !== window.location.hostname) return; // - external link
e.preventDefault();
this.go(url);
}.bind(this), false);
}
// exec callback
exec(url) {
var count = 0;
url = this.parse(url);
if(this.current && this.current.destroyed) this.current.destroyed();
this.routes.forEach(function(route) {
if(!url.pathname.match(route.url)) return;
this.current = route;
var data = route.url.exec(url.pathname).slice(1);
var params = {};
route.params.forEach((key, i) => params[key] = data[i]);
route.created({params, search: url.search});
count++;
}.bind(this));
// 404
if(!count && this.routes.notfound && this.url !== '/404') {
this.current = this.routes.notfound;
this.routes.notfound.created({params: {}, search: url.search});
}
return count;
}
// parse url
parse(url) {
url = url.split('?');
var search = {};
var pathname = url[0];
url = url[1] ? url[1].split('&') : [];
url.forEach(function(pair) {
pair = pair.split('=');
let key = pair[0];
let value = pair[1];
if(!/\[\]$/.test(key)) return search[key] = value;
search[key] = search[key] || [];
search[key].push(value);
});
return {pathname, search};
}
}
对于函数体内,this
总是指向调用它的上下文,有时候我们希望调用的上下文是某个指定的对象,这里最简单的方法是使用用 call
和 apply
。每次面试,问到它俩有什么区别,这个非常简单的一个点,面试的时候屡试不爽,大部分人都不知道。
在此之前,我们先来看一段代码。
class Cat {
constructor(name) {
this.name = name;
}
meow() {
console.log(`meow, i'm ${this.name}`);
}
}
var fakeCat = { name: 'sofish' };
var ciao = new Cat('ciao');
ciao.meow(); // meow, i'm ciao
ciao.meow.call(fakeCat); // meow, i'm sofish
ciao.meow.apply(fakeCat); // meow, i'm sofish
同样是 Cat
的实例 —— ciao
在叫(meow
),为什么结果不一样?因为第二和三个调用使用 call
把 ciao.meow
的执行上下文改变成 fakeCat
,所以 this.name
相当于 fakeCat.name
,结果很明显。
上面简单演示了它俩的作用,它俩的使用一样,只是参数不同。
call(context, arg1, arg2...)
第一个参数是上下文,之后所有参数为调用函数的参数列表apply(context, [arg1, arg2...])
第一个参数与 call
一样,之后传入一个数组作为函数的参数列表apply
和 array
第一个字母都是 a
(如果这样就不会搞混参数了)来个代码片段看看:
function sum(a, b, c) {
console.log(a + b + c);
}
sum.call(null, 1, 2, 3); // 6
sum.apply(null, [1, 2, 3]); // 6
好困,看会电视就睡觉去~
几乎每次面试都问一个题:一群人出去玩,写一个程序随机分组可以如何分。最后简化成 10 个人出去玩,如何将人随机分配到 4 个组里,并保证每个组的人比较均匀。
var arr = [1,2,3,4,5,6,7,8,9,0];
var group = [[],[],[],[]];
(function split(arr, group) {
// you code here
console.log(group);
})(arr, group);
不想直接给答案,有订阅的大家思考一下,有兴趣的可以评论发自己的答案。也是留给老婆的一道题。考点包括随机、数组操作、基本的流程控制。
补:昨天又忘记了 ( 👎 )
最近有时差,昨天的任务今天来写 👎 。
昨天写了前天的任务「如何分组」。大家都会需要用到「洗牌」,而关于如何洗有多种算法,其中有一个简洁高效的算法就是 Fisher-Yates Shuffle https://bost.ocks.org/mike/shuffle/ ( 文章和演示都非常棒 )。
在「如何分组」里我写了一个思路,里面有一种重要的点是不断从原来的数组踢除数值,这会导致数组本身重排,而这是 Fisher-Yates 要避免的问题,他采用的是交换的方法来避免重排,如下中文注释。
function shuffle(array) {
var m = array.length, t, i;
// While there remain elements to shuffle…
while (m) {
// Pick a remaining element…
i = Math.floor(Math.random() * m--);
// And swap it with the current element.
t = array[m]; // 缓存当前的第 m 位
array[m] = array[i]; // 随机 m 之前某位放进 m
array[i] = t; // 第随机位换成 m 位的值(后续还会被取到)
}
return array;
}
感慨,算法总是逼死我们这些文科生。
之前教学的时候写的文章,现在老婆还没理解透,所以把旧文整理了一下,发到 饿了么前端博客。如下:
在处理字符串的时候,正则就非常有用。今天老婆又开始看文章了,所以我也开始更新了。举个例子,处理模板,在没有多行模板的时候,我通常会选择这样做:
var person = { name: 'sofish', bio: 'a programmer'};
var tmpl = 'This is {name} and he is {bio}.';
var output = tmpl.replace(/\{(\w+)\}/g, function(match, group1) {
return person[group1] || '';
});
console.log(output);
关于正式的基础,字面量、ExpReg 对象,exec
test
search
match
replace
等方面的使用,可以看这篇文章:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions
现在,用 jQuery 的公司还很多,但是拿出来说的已经很少了。还有 15 分钟就要去跑步,加上老婆还在整天跟 jQuery 打交道,写两句说明一下。拿官方的例子说明一下:
// 避免冲突,创建一个函数自执行函数,并把 `jQuery` 传入
(function( $ ) {
// 用 `$.fn.插件名` 来定义一个新的插件
// 关于插件为什么不用 `new`,`$.fn` 是什么意思,可以看:
// http://stackoverflow.com/questions/4083351/what-does-jquery-fn-mean
$.fn.showLinkLocation = function() {
// `this` 即 `$()` 选中的集合
// `filter` 筛选这个选中集合中的 `<a>`
// 通常插件都会用 `each` 来循环整个集合,因为每个 `$()` 都会返回一个 jQuery 集合
this.filter( 'a' ).each(function() {
var link = $( this );
link.append( ' (' + link.attr( 'href' ) + ')' );
});
// jQuery 其中一个最大的特点是链式调用,而 `return this` 返回一个 jQuery 对象让链式调用成为现实
return this;
};
}( jQuery ));
// 执行插件
$( 'a' ).showLinkLocation();
呃,这其实是一个很大的话题,虽然说起来只有几句话。拿简单的十进制数字 3
,如果转换成二进制:
3..toString(2) // '11'
呃,浏览器里如何显示二进制呢?我们这里忽略,跳出来说一下为什么是 3..toString()
而不是 3.toString()
,是不是也曾误了 Number
并没有 toString
方法?用 ..
只是一个 hack 避开了小数点。另外可以写成 (3).toString()
这样就明白多了。转回来。关于如何把二进制转成 10 进制,就更简单了:
parseInt('11', 2) // 3
同样的,可以用带上像 2
8
10
16
这几个参数,来进行 2、8、10、16 进制的转换。不过负数呢?考虑一下这个执行返回什么:
(-3).toString(2) // '-11'
看!起!来!好!像!是!对!的!不过负数通常是以补码来表示的,而什么叫补码?( # )补码形式是指一个数的负对应值( negative counterpart )( 如 5 和 -5 )为数值的所有比特位反转后,再加 1。那么,-3
的值应该是:
(-3 >>> 0).toString(2) // 11111111111111111111111111111101
而关于为什么要用无符号右移 >>>
呢?其实右移了 0 位,( # ) (right logical shift) coerces its arguments to unsigned integers`,然后再转成 2 进制。
再次逼死文科生。我睡觉去了。
又要来逼死文科生了:递归,在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。那我们用递归实现一个阶乘吧:
function factorial(n) {
return n ? (n * factorial(n - 1)) : 1;
}
好了,完美,不过《 JS 高级程序设计》里提及过一个问题,函数作为一个引用类型,在递归里,如果我们使用函数名来调用自身,可能会存在一个指向的问题(引用类型在有引用时,指向的对象是不会被回收(GC)的,对吧?!),看下面的代码:
var f = factorial;
factorial = null;
f(4); // TypeError: factorial is not a function
我们可以简单地使用一个 hack 来解决 —— 使用 arguments.callee
,它永远指向函数本身:
function factorial(n) {
return n ? (n * arguments.callee(n - 1)) : 1;
}
var f = factorial;
factorial = null;
f(4);
然后... 又要一脸懵逼了,因为有了 strict 模式,如下:
'use strict'
~function func() {
arguments.callee;
// TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
}();
所以有什么办法比较好呢?
让我们回到递归本身来。在面试的时候,我们经常问起一个问题 —— 如何拍平一个数组。如:
var arr = [1, [2, [3, [4], 5], 6], 7];
(function flatten(arr) {
var newArray = [];
// your code here
return newArray;
})(arr);
那么,如果使用递归的话,可以如何实现呢?
这篇文章写的相当好,对比 setTimeout
/ setInterval
和 requestAnimationFrame
的处理,讲解用 requestAnimationFrame
来做 hack,并讲解如何利用 composite layer 来提高渲染性能:http://www.infoq.com/cn/articles/javascript-high-performance-animation-and-page-rendering
4 篇关于 Generator 的文章,从入门到放弃(CSP),里面有几个重要的点:
1. 使用 yield
和 return
导致 for...of
结果的不同
function *foo() {
yield 1;
return 2;
}
var it = foo();
console.log( it.next() ); // { value:1, done:false }
console.log( it.next() ); // { value:2, done:true }
for(let value of foo()) {
console.log(value)
}
2. next()
可以传值
function *foo() {
var x = 1 + (yield "foo");
return x;
}
var it = foo();
console.log(it.next()); // 'foo'
console.log(it.next(1)); // 2
3. 在 Generator 嵌套 Generator 的顺序是怎样的?
当前迭代器会代理嵌套 Generator 的迭代器,流程如下:*bar()
的迭代器走到 yield *foo()
的点,进入 *foo()
完成迭代,再跳出来往下执行。
function *foo() {
var z = yield 3;
var w = yield 4;
console.log( "z: " + z + ", w: " + w );
}
function *bar() {
var x = yield 1;
var y = yield 2;
yield *foo(); // `yield*` delegates iteration control to `foo()`
var v = yield 5;
console.log( "x: " + x + ", y: " + y + ", v: " + v );
}
var it = bar();
it.next(); // { value:1, done:false }
it.next( "X" ); // { value:2, done:false }
it.next( "Y" ); // { value:3, done:false }
it.next( "Z" ); // { value:4, done:false }
it.next( "W" ); // { value:5, done:false }
// z: Z, w: W
it.next( "V" ); // { value:undefined, done:true }
// x: X, y: Y, v: V
4. 知名 Node.js 库 tj/co 的实现原理
基本上就是自执行 Generator 和 Promise 结合的结果。
// run (async) a generator to completion
// Note: simplified approach: no error handling here
function runGenerator(g) {
var it = g(), ret;
// asynchronously iterate over generator
(function iterate(val){
ret = it.next( val );
if (!ret.done) {
// poor man's "is it a promise?" test
if (typeof ret.value.then === 'function') {
// wait on the promise
ret.value.then( iterate );
}
// immediate value: just send right back in
else {
// avoid synchronous recursion
setTimeout( function(){
iterate( ret.value );
}, 0 );
}
}
})();
}
function *foo() {
var z = yield 3;
console.log(z);
var w = yield 4;
console.log(w);
}
function *bar() {
var x = yield 1;
console.log(x);
var y = yield 2;
console.log(y);
yield *foo();
var v = yield 5;
console.log(v);
}
runGenerator(bar);
结:昨晚忘记更新了,这四篇写的非常好,我猜老婆也看不懂。在 async / await 出来之前,乖乖用 tj/co 就可以了。
为啥不从0开始学es6?
今天终于正常了,当日事当日毕。
以前写宣传页面的时候,微信作为一个最大的流量平台,分享的「卡片」当然要做的好看一点,所以写了 sofish/wechat.js 来省时间。后来微信就变得很屎,分享竟然需要签名,而且对每个页面都要。
最近老婆的项目遇到一个情况需要自定义分享到朋友图/朋友的卡片 URL 和图片,所以找了一个实现 https://github.com/leecade/weixin-node ,简单好用(记得建一个 /tmp/
文件夹,别问我为什么)。
因为微信规定 ticket 时间是 7200,并且有次数限制,所以需要自己缓存 ticket,而这个库用了一个简单粗暴且有效的方法 —— 存文件。每次需要之后去读文件,判断是否过期,如果过期重新请求,没过期就用缓存。https://github.com/leecade/weixin-node/blob/master/index.js#L79
签名和用法参考:微信JS-SDK说明文档
依然是旧文,修改一波发出来。明天团队出去春游有可能没时间写:
一直很喜欢 Bootstrap 里用不 new
执行 js 的方式,写一个例子,阻止页面上任何 a[preventdefault]
标签的默认行为,并弹出它的链接:
(function($) {
$(document).on('click', 'a[preventdefault]', function(e) {
e.preventDefault();
alert(this.href)
});
})(jQuery);
这种方式不需要像 new PreventDefault('a[preventdefault]')
,在 html 中直接声明,比如 Tab 插件,只需要写 data-toggle="Tab" data-target="#someId"
即可(新版好像已经不需要这样),而不像大家习惯的 new Tab('#id')
,在不需要任何 options
的情况也能省就省,直接把 html 的标记当声明。当然,见仁见智,这种方式只是我自己比较喜欢。
$.fn.pluginName
自助实例化在 #13 中的注释也写了,$.fn === $.prototype
,为什么 $(selector).pluginName()
就可以直接用呢?有没有办法去掉 new
关键词呢?
function C(name) {
// node 里可以用 !new.target 来检测
if(!(this instanceof C)) return new C(name);
this.name = name || 'noname';
}
C.prototype.test = function() {
return 'i\'m ' + this.name;
}
var c = C();
console.log(c.name, c.test());
再一次,这也是见仁见智的事,我就不是很喜欢不 new
的情况,但实现 jQuery 的 $.fn
却又很喜欢。
对于球迷来说 NBA 是一种精神,对于 Benz 粉丝来说 AMG 是一种精神,对于黑客来说无依赖也是一种精神。前段时间因为 leftPad 在 npm unpublish 之后引发了一堆对 leftPad 的讨论,就如坐我旁边的同事,就叫我当场写个 leftPad,然后在内部分享会嘲笑我写的太差。
刚半夜看 GitHub Trending 发现了一个无依赖的对字符串操作的小库 dleitee/strman。喜欢看薄的书,看简单的代码。所以看了一波:
export const _isArray = value => {
return (value !== null &&
value.length >= 0 &&
Object.prototype.toString.call(value) === '[object Array]');
};
export const _pop = array => {
let newarray = [];
for(let i = 0; i < array.length-1; i++){
newarray[i] = array[i];
}
return newarray;
};
如果是我写的话,可能会是这样的:
export const _isArray = value => Object.prototype.toString.call(value) === '[object Array]';
export const _pop = array => array.slice();
所以我觉得这个库还是把东西写复杂了,随后发了一条微博轻吐槽(其实是反问),经提示,发现了一个坑(像 sort 要返回 true/false 一样的坑,以前注意过后来全忘记了):
let str = new String();
typeof str; // object
'' instanceof String; // false
真是一脸懵逼 😿 ,当然,像上面的 _isArray
还可以这样写:
export const _isArray = value => Array.isArray(value);
但这样写有一个不好的点 —— 不兼容老浏览器。似乎 Symbal 没这个问题?(这个留给大家)。所以总的来说 Object.prototype.toString.call
还是比较安全的办法。
var frame = document.createElement('iframe');
frame.src = window.location.href;
document.body.appendChild(frame);
var arr = new frame.contentWindow.Array();
console.log(arr instanceof Array); // false
console.log(Array.isArray(arr)); // true
这里我记错了,写一个例子来看看,多谢微博提醒的同学
要总结的话只有一句:我不喜欢看源代码,不过无依赖的看看也挺舒服;要啰嗦的是,突然想起百度 5、6 年前开源过一个叫 Tangram 的库,我就几乎全读了,后来发现当时了解的 js 技巧似乎比现在还多;要自黑的是,当隔三个月看自己的代码觉得是垃圾的时候说明一切都还好,当隔了一年了看自己的代码觉得当时自己很牛逼的话,那说明水平已经比较水 —— 说的就是我。
突然想起 LeetCode,这是一个我不喜欢的地方,不过既然想去就去刷一刷。刷了几题觉得有时候就是很偏见的的玩意,比如奖字符串中的元音反转,也就是 hello
会变成 holle
,也就玩玩。
准备结束刷新到 https://leetcode.com/problems/flatten-nested-list-iterator/ ,然后默默在编辑器写后运行,跑过自己本地的 Testcase,但是贴上去竟然出错。代码如下:
var NestedIterator = function(nestedList) {
this.nestedList = nestedList;
};
NestedIterator.prototype.flatten = function(arr, ret) {
if(!ret) ret = [];
for(var i = 0, j = arr.length; i < j; i++) {
Array.isArray(arr[i]) ? this.flatten(arr[i], ret) : ret.push(arr[i]);
}
return ret;
};
NestedIterator.prototype.hasNext = function() {
var tmp = this.nestedList[0];
while(tmp && tmp.toString() === '') {
this.nestedList.shift();
tmp = this.nestedList[0];
}
return this.nestedList.length;
};
NestedIterator.prototype.next = function() {
var ret = this.nestedList.shift();
if(Array.isArray(ret)) {
[].unshift.apply(this.nestedList, this.flatten(ret));
ret = this.nestedList.shift();
}
return ret;
};
var i = new NestedIterator([[1,1],2,[1,1]])
var a = [];
while (i.hasNext()) a.push(i.next());
console.log(a);
运行起来是没问题的,但是要求并!不!是!一!个!纯!数!组!所以呵呵哒,总的来说,就是一个奇怪的古老程序员喜欢的复杂的数据结构,所以实现起来也真是:垃圾结果导致垃圾代码。
重要的话已经吐了,再见~
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.