implement myCall, myApply, myBind

Show the code directly.

myCall:

1
2
3
4
5
6
7
8
Function.prototype.myCall = function(ctx) {
ctx = ctx || window;
ctx.fn = this;
let args = [...arguments].slice(1);
let result = ctx.fn(...args);
delete ctx.fn;
return result;
}

myApply:

1
2
3
4
5
6
7
8
Function.prototype.myApply = function(ctx) {
ctx = ctx || window;
ctx.fn = this;
let args = arguments[1];
let result = ctx.fn(...args);
delete ctx.fn;
return result;
}

myBind:

1
2
3
4
5
6
7
8
Function.prototype.myBind = function(ctx) {
let that = this;
let args = [...arguments].slice(1);
return function() {//currying
const newArgs = [...arguments];
return that.apply(ctx, args.concat(newArgs));
}
}

On a deeper understanding about proto and prototype

Last a few days, I just looked through some basic rules about prototype and __proto__. But today when I learnt how to implement the function call() by myself, I found that there’s something unclear.

1
2
3
4
5
6
7
8
Function.prototype.myCall = function(ctx) {
ctx = ctx || window;
ctx.fn = this;
let args = [...arguments].slice(1);
let result = ctx.fn(...args);
delete ctx.fn;
return result;
}

How to understand the third line? What does this refer to?

First, let’s recall how we use the function myCall.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Function.prototype.myCall = function(ctx) {
ctx = ctx || window;
ctx.fn = this;
let args = [...arguments].slice(1)
let result = ctx.fn(...args)
delete ctx.fn
return result;
}

function a(x,y) {
return x+y;
}

const obj = {
m: 1,
n: 2,
}

a.myCall(obj, 3,5)
  1. a is a function
  2. a uses its method myCall.

Wait. As far as I’m concerned, only Object can has a method. But how could a function use a method?

So I looked through some information, and a picture can conclude it well.

proto

  1. Everything in JS is Object. Function is an object, Function.prototype is an object. So they all share the same feature: they have a property: __proto__, which is a pointer to its constructor’s prototype.
  2. Function is a special object. Including general properties, it has an extra property: prototype, which has a property named constructor which is the Function itself.

So it’s easy for us to understand:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Function.prototype.myCall = function(ctx) {
ctx = ctx || window;
ctx.fn = this;
console.log(this===a)//true;
let args = [...arguments].slice(1)
let result = ctx.fn(...args)
delete ctx.fn
return result;
}

function a(x,y) {
return x+y;
}
console.log(a instanceof Function)//true

const obj = {
m: 1,
n: 2,
}

console.log(a.prototype.constructor===a)//true
console.log(a.__proto__===Function.prototype)//true
console.log(a.prototype.__proto__===Object.prototype)//true

a.myCall(obj, 3,5)

So how do we understand the total process of the code?

First we define a method myCall for Function.prototype, then we define a function a, which is inherited from Function.prototype.

In the last line, we make the function a (also an object) call the method myCall. First it look up in a‘s properties, but cannot match, so it backtracks to a.__proto__ AKA Function.prototype. So it finally finds the method, and this refers to the object & function a.

Finally we revise the basic knowledge of prototype chain.

Object.prototype(top of the prototype chain)

Function.prototype inherited from Object.prototype

Function and Object inherited from Function.prototype.

There are some thoughts on the topic which is interestring.

从探究Function.proto===Function.prototype过程中的一些收获

LCOF-62圆圈中最后剩下的数字

description

开始采用暴力法,run time error

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var lastRemaining = function(n, m) {
let nums=[];
for(let i=0; i<n; ++i) nums.push(i);
let start = 0;
while(nums.length>1) {
let i=start;
for(let j=0; j<m-1;j++, i=(i+1)%nums.length) {
}
nums.splice(i,1);
if(i===nums.length) start=0;
else start = i;
}
return nums[0];
};
  1. n个人编号0, 1, …, n-1, 每数m次删掉一个人
  2. 假设有函数f(n)表示n个人最终剩下的人的编号
  3. n个人删掉一个人后,可以看作是n-1的状态,不过有自己的编号
  4. n个人删掉第一个人的编号是(m-1)%n, 那么在被删除这个人后面的编号m%n,对应的是n-1个人时,编号为0的那个人。所以n时编号为m%n的那个人,n-1个人时编号为i的人就是n个人时(m+i)%n
  5. 所以f(n)=(m+f(n-1))%n
  6. f(1) = 0

在JavaScript语言中会爆栈,因此改写为迭代

1
2
3
4
5
6
7
var lastRemaining = function (n, m) {
let ans = 0;
for (let i = 2; i <= n; i++) {
ans = (ans + m) % i;
}
return ans;
};

github-demo-search-optimization

在项目进行搜索跳转优化时,有几点需要注意下。

SEO优化&非必要筛选条件不展示比如sort=,使用的href属性替代

这样在html文件中是可以直接看到该网址的。

选中无法点击,判断item是否selected

effect

采用FilterLink组件而非a标签以及memo方法可以保证在属性不变的情况下不需要重新渲染:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const FilterLink = memo(({name ,query, lang, sort, order}) =>{
const doSearch = (config) =>{
Router.push({
pathname: '/search',
query: {
query,
lang,
sort,
order
},
}
)}
let queryString = `?query=${query}`
if(lang) queryString +=`&lang=${lang}`
if(sort) queryString += `&sort=${sort}&order=${order|| 'desc'}`
return <Link href={`/search${queryString}`}><a>{name}</a></Link>
})

III. issue详情页优化

在展示仓库列表时,可以通过点击仓库的名字进入查看指定仓库的详细信息,而指定仓库的详细信息的头部基本信息展示是与仓库列表中信息的展示重合的,所以使用LRU Cache将仓库基本信息进行缓存,当浏览器端进入详情页时优先加载LRU Cache中的信息。

IV. 通过使用打包分析工具来优化模块的依赖关系

使用bundle-analyzer进行打包分析,优化moment.js, 将locale下面的语言包仅支持中文

lCOF-60

​ # 剑指 Offer 60. n个骰子的点数

description

At first use recursion:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var twoSum = function(n) {
let m = new Map();
dfs(m, 0, n,0);
let sum = 0;
let ret= [];
for(let [k, v] of m.entries()) {
sum+=v;
ret.push(v);
}
for( let i = 0; i< ret.length; ++i) {
ret[i] = ret[i] / sum
}
return ret;
};

var dfs = function(m, start, n, sum) {
if(start===n) {
if(m.has(sum)) {
m.set(sum, m.get(sum)+1);
} else {
m.set(sum, 1);
}
return;
}
for(let i=1; i<=6; ++i) {
sum+=i;
dfs(m, start+1, n, sum);
sum-=i;
}
}

Unfortunetely, rum time error.

Refer to the official answer, we could use iterative method.

1
2
3
4
5
6
7
8
9
10
11
var twoSum = function(n) {
if(n<1) return []
const res = [0, 1, 1, 1, 1, 1, 1];
for(let i = 1; i<n; ++i) {//control times for simulation
for(let j = 6*n; j>0; j--) {//control simulation
res[j] = res.slice(Math.max(0, j-6), j)
.reduce((acc, cur)=>acc+cur, 0);
}
}
return res.slice(1).map(num=>num/Math.pow(6,n)).filter((i)=>i!=0);
};

The time complexity is O(n2), and space complexity is O(n).

this, apply, call and bind

What this represents

this means where the current property is from.

What this points to

this points to the object which is the last to calls it.

1
2
3
4
5
6
7
8
9
10
var name = "windowsName";
function a() {
var name = "Cherry";

console.log(this.name); // windowsName

console.log("inner:" + this); // inner: Window
}
a();//window.a()
console.log("outer:" + this) // outer: Window
1
2
3
4
5
6
7
8
var name = "windowsName";
var a = {
name: "Cherry",
fn : function () {
console.log(this.name); // Cherry
}
}
a.fn();
1
2
3
4
5
6
7
8
9
10
11
var name = "windowsName";
var a = {
name : null,
// name: "Cherry",
fn : function () {
console.log(this.name); // windowsName
}
}

var f = a.fn;
f();
1
2
3
4
5
6
7
8
9
10
11
var name = "windowsName";

function fn() {
var name = 'Cherry';
innerFunction();
function innerFunction() {
console.log(this.name); // windowsName
}
}

fn()

Methods to change where this points

Arrow Function

this in arrow function points where the function is defined, instead of conducting.

If there is no this in arrow function, we must find the scope chain to find the value. if a non-arrow function contains the arrow function, this in the arrow function is the this in the non-arrow function, otherwise this is undefined.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var name = "windowsName";

var a = {
name : "Cherry",

func1: function () {
console.log(this.name)
},

func2: function () {
setTimeout( () => {
this.func1()
},100);
}

};

a.func2() // Cherry

_this = this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var name = "windowsName";

var a = {

name : "Cherry",

func1: function () {
console.log(this.name)
},

func2: function () {
var _this = this;
setTimeout( function() {
_this.func1()
},100);
}

};

a.func2() // Cherry

apply, call, bind

apply & call difference:

1
2
3
fun.apply(thisArg, [argsArray])
fun.call(thisArg[, arg1[, arg2[, ...]]])
fun.bind(thisArg[, arg1[, arg2[, ...]]])()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var a = {
name : "Cherry",

func1: function () {
console.log(this.name)
},

func2: function () {
setTimeout( function () {
this.func1()
}.apply(a),100);
}

};

a.func2() // Cherry

js-new

How to understand javascript new operation?

A easy example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function soldier(ID) {
var temp_obj = {};// ① new helps us a temp_obj
temp_obj.__proto__ = soldier.prototype;//② new helps us bind the prototype
temp_obj.ID = ID;
temp_obj.life = 42;

return temp_obj;//③ new helps us return the temp_obj;
}

soldier.prototype = {//④ new helps us define "prototype"
type: "Jiefangjun",
attack: 5,
walk: function(){},
run: function(){},
attack: function(){},
defend: function(){},
die: function(){},
}

Using new, we can prevent doing the following things:

  • Creating a temporary object (directly using this to visit the temporary object)
  • Binding prototype (new knows where your prototype is)
  • Returning temporary object
  • Giving a name to “prototype”

After we using new :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function soldier(ID) {
this.ID = ID;
this.life = 42;
}

soldier.prototype = {
type: "Jiefangjun",
attack: 5,
walk: function(){},
run: function(){},
attack: function(){},
defend: function(){},
die: function(){},
}

var soldiers = []
for(let i=0; i<100; ++i) {
soldiers.push(new soldier(i))
}

There is a property named constructor:

1
2
3
soldier.prototype = {
constructor: soldier
}

So we need to define the property seperatedly.

Implement a new by self

First let’s do a test:

1
2
3
4
5
6
7
8
9
function Test(name) {
this.name = name
}
Test.prototype.sayName = function () {
console.log(this.name)
}
const t = new Test('cxk')
console.log(t.name) // 'cxk'
t.sayName() // 'cxk'

Test if we return a value in the function:

1
2
3
4
5
6
function Test(name) {
this.name = name
return 1
}
const t = new Test('cxk')
console.log(t.name) // 'cxk'

So if we return a value in the function, there is no difference.

Test if we return an object in the function:

1
2
3
4
5
6
7
8
function Test(name) {
this.name = name
console.log(this) // Test { name: 'yck' }
return { age: 26 }
}
const t = new Test('yck')
console.log(t) // { age: 26 }
console.log(t.name) // 'undefined'

So if we return an object, the new doesnt effect.

1
2
3
4
5
6
function create(fun, ...args) {
let obj = {};//create a temp_obj
Object.setPrototypeOf(obj, fun.prototype)//bind the temp_obj to prototype
let result = fun.apply(obj, args)//bind obj to construction function
return result instanceof Object ? result: obj;
}

Then we test function create :

1
2
3
4
5
6
7
8
9
10
11
function Test(name, age) {
this.name = name
this.age = age
}
Test.prototype.sayName = function () {
console.log(this.name)
}
const a = create(Test, 'cxk', 21)
console.log(a.name) // 'cxk'
console.log(a.age) // 21
a.sayName() // 'cxk'

Closure in JS(闭包)

A Simple Demo

1
2
3
4
5
6
7
8
function A() {
function B(){
console.log("Hello closure!");
}
return B;
}
var C = A();
C();//Hello closure!
  • 定义普通函数A,
  • 在函数A中定义普通函数B,
  • 函数A返回普通函数B。
  • 在全局中定义C为A执行的结果
  • 执行C

函数A的内部函数B被函数A外的一个变量C所引用

当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包

A Less Simple Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
function A() {
var count = 0;
function B() {
count++;
console.log(count);
}
return B;
}

var C = A();
C();//1
C();//2
C();//3

在JavaScript中,如果一个对象不再被引用,那么这个对象就会被GC回收,否则一直保存在内存中

B在A中被定义,因此B依赖于A,而外部变量C引用了B,所以A间接被C引用。

A High-level Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(function (document) {
varviewport;
varobj = {
init: function(id) {
viewport = document.querySelector('#'+ id);
},
addChild: function(child) {
viewport.appendChild(child);
},
removeChild: function(child) {
viewport.removeChild(child);
}
}
window.jView = obj;
})(document);

(function(){})为一个匿名函数,而()执行。

全局对象window引用了obj,而obj依赖于匿名函数,且操作其viewport,故形成闭包。

让你分分钟理解 JavaScript 闭包

Inheritance and Prototype in js

When it comes to inheritance, there is only one data type in js: object. Every object has a private property named \_proto___ which points to its prototype. The top of the prototype is Object. Object.__proto__ === null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 让我们从一个函数里创建一个对象o,它自身拥有属性a和b的:
let f = function () {
this.a = 1;
this.b = 2;
}
/* 这么写也一样
function f() {
this.a = 1;
this.b = 2;
}
*/
let o = new f(); // {a: 1, b: 2}

// 在f函数的原型上定义属性
f.prototype.b = 3;
f.prototype.c = 4;

// 不要在 f 函数的原型上直接定义 f.prototype = {b:3,c:4};这样会直接打破原型链
// o.[[Prototype]] 有属性 b 和 c
// (其实就是 o.__proto__ 或者 o.constructor.prototype)
// o.[[Prototype]].[[Prototype]] 是 Object.prototype.
// 最后o.[[Prototype]].[[Prototype]].[[Prototype]]是null
// 这就是原型链的末尾,即 null,
// 根据定义,null 就是没有 [[Prototype]]。

// 综上,整个原型链如下:

// {a:1, b:2} ---> {b:3, c:4} ---> Object.prototype---> null
1
2
3
4
5
6
function doSomething(){}
console.log( doSomething.prototype );
// 和声明函数的方式无关,
// JavaScript 中的函数永远有一个默认原型属性。
var doSomething = function(){};
console.log( doSomething.prototype );
1
2
3
4
5
6
7
8
9
10
11
12
{
constructor: ƒ doSomething(),
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),//return a boolean
isPrototypeOf: ƒ isPrototypeOf(),//return a boolean on whether inherit
propertyIsEnumerable: ƒ propertyIsEnumerable(),//是否可枚举
toLocaleString: ƒ toLocaleString(),//可输出日期等
toString: ƒ toString(),//
valueOf: ƒ valueOf()
}
}

New

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function foo() {}
foo.prototype = {
foo_prop: "foo val"//add a property
}
var proto = new foo //proto.__proto__ = foo.prototype
proto.bar_prop = "bar val"//add a property on proto

function bar() {}
bar.prototype = proto//
var inst = new bar//
console.log(inst.__proto__ === bar.prototype)
console.log(inst.__proto__ === proto)
console.log(bar.prototype === proto)
console.log(proto.__proto__ === foo.prototype)
console.log(proto.__proto__ === foo.prototype)
// inst -> bar.prototype -> proto -> foo.prototype

Object.create

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function foo(){}
foo.prototype = {
foo_prop: "foo val"
};
function bar(){}
var proto = Object.create(
foo.prototype,
{
bar_prop: {
value: "bar val"
}
}
);
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);//foo val
console.log(inst.bar_prop);//bar val

IE8 unsupported

Object.setPrototypeOf

perform badly

__proto__

need abandoning

prototypeObject.getPrototypeOf

1
2
3
var a1 = new A(); 
var a2 = new A();
Object.getPrototypeOf(a1).doSomething == Object.getPrototypeOf(a2).doSomething == A.prototype.doSomething

OAuth

OAuth Procedure

令牌(token)与密码(password)的作用是一样的,都可以进入系统,但是有三点差异。

(1)令牌是短期的,到期会自动失效,用户自己无法修改。密码一般长期有效,用户不修改,就不会发生变化。

(2)令牌可以被数据所有者撤销,会立即失效。以上例而言,屋主可以随时取消快递员的令牌。密码一般不允许被他人撤销。

(3)令牌有权限范围(scope),比如只能进小区的二号门。对于网络服务来说,只读令牌就比读写令牌更安全。密码一般是完整权限。

上面这些设计,保证了令牌既可以让第三方应用获得权限,同时又随时可控,不会危及系统安全。这就是 OAuth 2.0 的优点。

Four ways to get the token

  • 授权码(authorization-code)
  • 隐藏式(implicit)
  • 密码式(password):
  • 客户端凭证(client credentials)

A simple demo about third-party login

In config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const GITHUB_OAUTH_URL = 'https://github.com/login/oauth/authorize'
const SCOPE = 'user'

const github = {
request_token_url: 'https://github.com/login/oauth/access_token',
client_id: '7719dcc449a12e7f83f4',
client_secret: '42771ea51b4a3afac6ab67cae5cd8b924f81c7fc',
}

module.exports = {
github,
GITHUB_OAUTH_URL,
OAUTH_URL: `${GITHUB_OAUTH_URL}?client_id=${github.client_id}&scope=${SCOPE}`,
}

In server/auth.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 处理github返回的auth code
const axios = require('axios')
const config = require('../config')

const { client_id, client_secret, request_token_url } = config.github

module.exports = (server) => {
server.use(async (ctx, next) => {
if (ctx.path === '/auth') {
console.log(ctx.path)
const { code } = ctx.query//获取授权码
if (code) {
// 请求令牌
const result = await axios({
method: 'POST',
url: request_token_url,
data: {
client_id,
client_secret,
code,
},
headers: {
Accept: 'application/json',
},
})

// github 可能会在status是200的情况下返回error信息
if (result.status === 200 && (result.data && !result.data.error)) {
ctx.session.githubAuth = result.data

const { access_token, token_type } = result.data
// 获取用户信息
const { data: userInfo } = await axios({
method: 'GET',
url: 'https://api.github.com/user',
headers: {
Authorization: `${token_type} ${access_token}`,
},
})

ctx.session.userInfo = userInfo
// 重定向到登录前的页面或首页
ctx.redirect((ctx.session && ctx.session.urlBeforeOAuth) || '/')
ctx.session.urlBeforeOAuth = ''
} else {
ctx.body = `request token failed ${result.data && result.data.error}`
}
} else {
ctx.body = 'code not exist'
}
} else {
await next()
}
})
}