Hawei

Say Hi.


  • Home

  • About

  • Tags

  • Categories

  • Archives

Iterators & Generators.md

Posted on 2020-04-12 | In JavaScript_The Recent Parts

Iterators & Generators

Iterators

一个迭代器对象 ,知道如何每次访问集合中的一项, 并跟踪该序列中的当前位置。在 JavaScript 中 迭代器是一个对象,它提供了一个next() 方法,用来返回序列中的下一项。这个方法返回包含两个属性:done和 value。
迭代器对象一旦被创建,就可以反复调用next()

来看个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function makeIterator(array){
var nextIndex = 0;
return {
next: function(){
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{done: true};
}
};
}
// 初始化后, next方法可以用来依次访问对象中的key
var it = makeIterator(['yo', 'ya']);
console.log(it.next().value); // 'yo'
console.log(it.next().value); // 'ya'
console.log(it.next().done); // true

来看Kyle的例子

1
2
3
4
5
6
7
8
var str = 'Hello';
var world = ['W', 'o', 'r', 'l', 'd'];

var it1 = str[Symbol.iterator]();
var it2 = world[Symbol.iterator]();

it1.next(); // {value: 'H', done: false}, 到最后一个的时候 {value: undefined, done: true}
it2.next(); // {value: 'W', done: false}

Declarative Iterators

1
2
3
4
5
6
7
8
var str = 'Hello';

for(let it = str[Symbol.iterator](), v, result;
(result = it.next()) &&
!result.done &&
(v = result.value || true); ) {
console.log(v);
}

在ES6我们新增了for-of循环, 替换上面的结构:

1
2
3
4
5
6
7
8
9
10
var str = 'Hello';
var it = str[Symbol.iterator]();

for(let v of it) {
console.log(v);
}

for(let v of str) {
console.log(v)
}

这里说一个解构小知识, …运算符运用了iterator

1
2
3
4
var str = 'Hello';

var letters = [...str];
console.log(letters);

自定义迭代器让我们定义自己的迭代规则。这种结构特别适用于我们自己创建的数据结构。

Data Structure without lterators

不是所有的数据结构都有iterator, 比如对象

1
2
3
4
5
6
7
8
9
10
11
var obj = {
a: 1,
b: 2,
c: 3
}

for (let v of obj) {
console.log(v);
} // Type Error

[...obj] // Type Error

在这种情况下, 我们可以自定义自己的iterator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var obj = {
a: 1,
b: 2,
c: 3,
[Symbol.iterator]: function() {
var keys = Object.keys(this);
var index = 0;

return {
// 在这里使用箭头函数的原因是因为, 需要使用到this关键字取到对象本身
next: () => (index < keys.length) ?
{done: false, value: this[keys[index++]]}:
{done: true, value: undefined}
}
}
};

[...obj] // [1, 2, 3]

Generators

虽然自定义的迭代器是一个有用的工具,但由于需要显式地维护其内部状态,因此需要谨慎地创建。Generators提供了一个强大的选择:它允许你定义一个包含自有迭代算法的函数, 同时它可以自动维护自己的状态。
GeneratorFunction 是一个可以作为迭代器工厂的特殊函数。当它被执行时会返回一个新的Generator对象。 如果使用function*语法,则函数将变为GeneratorFunction。

来看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function* idMaker(ids = []) {
let len = ids.length;
if(len) {
for(let i = 0; i < len; i++) {
yield i;
}
} else {
yield undefined;
}
}

let gen = idMaker([0, 1, 2]);

console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
// ...

OK, 我们来看一下Kyle的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function *main () {
yield 1;
yield 2;
yield 3;
return 0;
}
// 明确一点, 当我们调用Generator的时候,其实就是实例化了一个iterators实例, 他的行为和我们上面说的iterator很像。只不过我们在iterator上再写一层语法糖, 就是generator
var it = main();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: false}
it.next(); // {value: 0, done: true}

[...main()]

好的, 我们用Generator重写一次上面obj没有默认iterator的例子

1
2
3
4
5
6
7
8
9
10
11
12
var obj = {
a: 1,
b: 2,
c: 3,
*[Symbol.iterator]() {
for(let key of Object.keys(this)) {
yield this[key];
}
}
}

[...obj]; // [1, 2, 3]

Object Dstructuring.md

Posted on 2020-04-12 | In JavaScript_The Recent Parts

Object Destructuring

Object Destructuring

1
2
3
4
5
6
7
8
9
10
11
12
// before
const data = () => {
return {
a: 1,
b: 2,
c: 3
}
}
var tmp = data();
var first = tmp.a;
var second = tmp.b;
var third = tmp.c;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// after
const data = () => {
return {
a: 1,
b: 2,
c: 3,
d: 4,
e: 5
}
}
var tmp = data();
var {
a: first,
b: second,
c: third,
...fourth
} = data();

Object Assignment Destructuring

1
2
3
4
5
6
7
8
9
10
11
const data = () => {
return {
a: 1,
b: 2,
c: 3,
d: 4,
e: 5
}
}
var first, second;
{first, second} = data();

Object Default Assignment

说到默认值就要惊醒自己,要防止错误, 而防止错误的措施就是, 给数据源默认值

1
2
3
4
5
6
7
8
// before
const data = () => {
return null;
}
var tmp = data() || {};
var first = tmp.a; // Throw Type Error
var second = tmp.b; // Throw Type Error
var third = tmp.c; // Throw Type Error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// after
const data = () => {
return {
a: 1,
b: 2,
c: 3,
d: 4,
e: 5
}
}
var tmp = data();
var {
a: first = 42,
b: second,
c: third,
...fourth
} = data() || {};

Nested Object Destructuring

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// before
const data = () => {
return {
a: 1,
b: {
c: 2,
d: 3
}
};
}
var tmp = data() || {};
var first = tmp.a;
var second = tmp.b.c;
var third = tmp.b.d;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// after
const data = () => {
return {

a: 1,
b: {
c: 2,
d: 3
}
}
}
var {
a: first = 42,
b: {
c: second,
d: thrid,
},
...fourth
} = data() || {};

params arguments object

1
2
3
4
5
6
7
8
9
10
11
12
function data (tmp = {}) {
var {
a,
b
} = tmp;
}

function data ({
a,
b
} = {}) {
}

Nested Object & Array Destructuring

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var obj = {
a: 1,
b: {
x: 2
},
c: [3, 4]
}

var {
a,
b,
b: {
x: w
},
c: [d, e]
} = obj;

Regular Expressions.md

Posted on 2020-04-12 | In JavaScript_The Recent Parts

Regular Expressions

Look Ahead & Behind

后置断言

1
2
3
4
5
6
7
8
9
10
11
12
var msg = 'Hello world';

msg.match(/(l.)/g);
// ['ll', 'ld']
msg.match(/(l.)$/g);
// ['ld']
msg.match(/(l.)(?=o)/g);
// 我要匹配l.的字符串,但是我希望这个字符串后面有'o'
// ['ll']
msg.match(/(l.)(?!o)/g);
// 我要匹配l.的字符串,但是我希望这个字符串后面没有'o'
// ['lo', 'ld']

前置断言

1
2
3
4
5
6
7
8
9

var msg = 'Hello World';

msg.match(/(?<=e)(l.)/g);
// 我要匹配l.的字符串,但是我希望这个字符串前面有'e'
// ['ll']
msg.match(/(?<!e)(l.)/g);
// 我要匹配l.的字符串,但是我希望这个字符串前面没有'e'
// ['lo', 'ld']

Named Capture Groups es2018

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
var msg = 'Hello World';

// 使用括号的意思就是,我按这些匹配,但是最后出结果的时候也把我括号内的单独抽离出来, 成为结果之一
msg.match(/.(l.)/);
// ['ell', 'll']
msg.match(/([jkl])o Wor\1/);
// ['lo Worl']
// 让我们用捕获组重新写第一个例子
msg.match(/(?<groupName>l.)/);
// 把输出展开后, 我们得到了以下的内容:
// 0: "ll"
// 1: "ll"
// groups:-> 这个就是我们捕获组的, 自然,在这个下方是我们对捕获组的命名和匹配结果, 真的很实用, 我们的正则不再是一串又一串没有意义的数字了
// groupName: "ll"
// index: 2
// input: "Hello World"
// length: 2
msg.match(/(?<cap>[jkl])o Wor\k<cap>/);
// 对于这个正则,我没看懂的地方就是 \k 表达的是什么?

// 捕获组的应用场景: 替换
// 使用模板语法替换
msg.replace(/(?<cap>l.)/g, '-----------$<cap>----------');
// "He-----------ll----------o Wor-----------ld----------"
// 使用callback函数处理
msg.replace(/(?<cap>l.)/g, function re(...args) {
debugger
var [,,,,{cap}] = args;
return cap.toUpperCase();
});
// "HeLLo WorLD"

dotAll Mode ES2018

MDN内的描述

dotAll 属性表明是否在正则表达式中一起使用”s”修饰符(引入/s修饰符,使得.可以匹配任意单个字符)。dotAll 是一个只读的属性,属于单个正则表达式实例。

简而言之, 我使用了s的修饰符后, .可以匹配到

  1. U+000A 换行符(”\n”)
  2. U+000D 回车符(”\r”)
  3. U+2028 行分隔符(line separator)
  4. U+2029 段分隔符(paragraph separator)

为什么提出这个特性呢?因为历史因素, js中,正则表达式中的‘.’, 是元字符,本来是匹配所有的字符,但是就是不能匹配新的一行.

注意浏览器支持情况, 保留特性,暂时不要使用.

来看个例子:

1
2
3
4
5
6
7
8
9
var msg = `
The quick brown fox
jumps over the
lazy dog`;

msg.match(/brown.*over/);
// null
msg.match(/brown.*over/s);
// ["brown fox↵jumps over", index: 11, input: "↵The quick brown fox↵jumps over the↵lazy dog", groups: undefined]

Strings.md

Posted on 2020-04-12 | In JavaScript_The Recent Parts

String

Template Strings

  1. 字符串模板改变了链接字符串的方式
    • 使用方式: ``符号
    • 在字符串中插入变量${}
    • 字符串模板函数
    • 会保留字符串内的空格, 换行, 但是如果设计到转义字符,必须要使用\

Tagged Templates

劫持原有的字符渲染,由函数接管渲染。

1
2
3
4
5
6
7
8
9
10
11
12

const sum = 12.5;
const msg = format`sum is: ${sum}`;
console.log(msg);
// 然后解读一下入参:
// str: 非变量分割的字符串数组, 在这个例子中为['sum is: ', '']
// ...value: 变量以数组的形式传入第二个参数
function format (str, ...value) {
debugger
// 这里返回的就是最终的字符串了
return `${str[0]}$${value[0]}`;
}

知道吗, 这个函数最大的作用就是国际化.在我们$t(‘sayHi’)这样的国际化里, 其实本质上也是一个函数,这个函数通过检测用户语言,然后根据翻译字典去显示对应的内容.

还有就是金额格式化.

Applying Tagged Templates

  1. 定制log函数, 检测字符串变量, 如果是object就把他转成json字符串
  2. 写一个正则表达式的翻译工具, 允许换行和切分正则,最后使用tagged tamplates转成符合格式的正则即可。增加正则的可读性。
  3. 所以,在你的模板字符串里, 你可以写任意你像写的语法,使用好tagged templates就想当于有一个小型的模板引擎。

String padding and String Trimming—–ES2017/ES2019

  1. String padding
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var str = 'Hello';

    str.padStart(5);
    str.padStart(8);
    str.padStart(8, '-');
    str.padStart(8, '*');

    str.padEnd(5);
    str.padEnd(8);
    str.padEnd(8, '-');
    str.padEnd(8, '*');
  2. String trim
    1
    2
    3
    4
    var str = '  Hello    ';
    str.trim();
    str.trimStart();
    str.trimEnd();

array destructuring.md

Posted on 2020-04-12 | In JavaScript_The Recent Parts

array destructuring

destructuring

decomposing a structure into its individual parts

解构解决了两件事

  1. 从结构化的数据批量提取对应的数据, 并且直接把值赋予变量(在我们处理接口返回值的时候尤为好用)
  2. 便捷的给我们的数据赋予初始值, 不用再写类似这样的三元表达式了: a = a === undefiend ? defaultValue: value;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var [
    {
    name: firstname,
    email: firstemil = 'nobody@none.com'
    },
    {
    name: secondname,
    email: secondemil = 'nobody@none.com'
    }
    ] = getSomeRecords();

Refactoring Code Using Destructuring

1
2
3
4
5
6
7
// before
var getData = () => [1, 2, 3];
var data = getData()

let a = data[0];
let b = data[1];
let c = data[2];
1
2
3
4
5
// after
var getData = () => [1, 2, 3];
var data = getData()

let [a, b, c] = [...data];

Spread Operator & Declaring Destrutured

…运算符解决了什么问题?我们不需要去管我们没有显式的指示出来的变量。只要它传了, 那么剩下的我都用…运算符去收集和处理。把不确定性可以更好的管理起来。

1
2
3
4
5
6
7
// before
var getData = () => [1, 2, 3];
var data = getData()

let a = data[0];
let b = data[1];
let c = data[2];
1
2
3
4
5
6
7
// after
var getData = () => [1, 2, 3];
var data = getData();

let temp;
// 先把data-> temp -> 执行解构
let [a, b, c] = temp = [...data];

Declaration & Assignment

1
2
3
4
5
6
7
8
9
// before
var getData = () => [1, 2, 3];
var data = getData()

var o = {};

o.a = data[0];
o.b = data[1];
o.c = data[2];
1
2
3
4
5
6
7
8
// after
var getData = () => [1, 2, 3];
var data = getData();

let temp;
var o = {};
// 先把data-> temp -> 执行解构
[o.a, o.b, o.c] = temp = [...data];

comma separation

解决的问题是: 我需要跳过其中一个元素,完成解构

1
2
3
4
5
6
7
// before
var getData = () => [1, 2, 3];
var data = getData()

let a = data[0];
// let b = data[1];
let c = data[2];
1
2
3
4
5
6
7
// after
var getData = () => [1, 2, 3];
var data = getData();

let temp;
// 先把data-> temp -> 执行解构
let [a, , c] = temp = [...data];

交换两个元素的值

1
2
3
4
5
6
7
8
9
//before
var x = 10;
var y = 20;

{
let temp = x;
x = y;
y = temp;
}
1
2
3
4
var x = 10;
var x = 20;

[y, x] = [x, y];

Parameter Arrays

解构同样可以使用在函数的参数中, 当然,在ES6中,直接支持了函数参数的默认值

1
2
3
4
5
6
7
8
// before
function data (tmp = []) {
var [
first,
second,
third
] = tmp;
}
1
2
3
4
// before
function data ([first, second, third] = []) {

}

Nested Array Destructuring

使用解构赋值的时候,一定要注意,是否会触发类型错误, 给一个默认值是一个很棒的方法。

1
2
3
4
5
6
7
8
9
// before
var getData = () => [1, [2, 3], 4];
var data = getData() || [];
var data2 = data[1]

let a = data[0];
let b = data2[0];
let c = data2[1];
let b = data[2];
1
2
3
// after
var getData = () => [1, [2, 3], 4];
var data = getData()

further destructuring.md

Posted on 2020-04-12 | In JavaScript_The Recent Parts

further destructuring

Named Arguments

像之前翻译的文章里提到的命名函数参数。里面使用了python的命名参数的例子。在ES6中,我们可以通过解构来实现这个命名函数参数.

1
2
3
4
5
6
7
8
function sum ({
a = 1,
b = 2
}) {
return a + b;
}

sum({b: 3});

建议:

  1. 如果函数超过三个参数,那么我们应该一直使用对象传参
  2. 形成习惯:如果你的参数是callback=>cb, array=> arr, value => v

Destructuring & Restructuring

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var default = {
url: 'http://some.base.url/api',
method: 'post',
headers: [
'Content-Type: text/plain'
]
}

console.log(default);
var setting = {
url: 'http://some.base.url/api/1',
data: 42,
callback: funciton() {}
}

// 合并两个对象,带默认值
// 但是这样的作法存在问题
// 1. 依赖外部库
// 2. 需要文档才能看得懂
// 我们可以使用解构重构这部分的代码
ajax( _.extend({}, default, setting));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function ajaxOption({
url = 'http://some.base.url/api',
methods = 'post',
data,
callback,
headers: [
header0 = 'Content-Type: text/plain',
...otherHeaders
] = []
} = {}) {
return {
url, method, data, callback,
headers: [
header0,
...otherHeaders
]
}
}

1.Type.md

Posted on 2020-04-12 | In deep-js-foundations-v2-noteBook

Deep-js-fundations

Type

js类型的描述

语言特性: 动态语言, 在运行时确定类型, 所以与静态语言不同的是,在初始化的时候并不需要我们指定变量的类型.

分类:

  1. 基本类型
    • undefined
    • string
    • number
    • boolean
    • symbol
    • null
  2. 引用类型
    • object
    • function
    • array
  3. 草案阶段的类型
    • bigInt

bigInt: 可以表示无限大的数, 例如:

1
2
var n = 42n;
typeof n === 'bigint';

typeof

使用typeof可以判断的类型有

  • undefined
  • number
  • string
  • boolean
  • object
  • symbol
  • function

这里要注意一个特殊的例子:

1
typeof null === 'Object'

请写一个增强的判断js类型的函数.要求可判断引用类型(arr, funciton, Data)

1
2
3
4
5
const isType = (value) => {
return Object.prototype.toString.call(value).match(/(?<= )\w+/)[0]
// String Number Null Undefined Object Date RegExp Symbol Boolean Function
}
// 这里涉及了正则的一个知识,就是匹配以xxx开头的字符串(不包含xxx), 那么就要使用正则的前置断言(?<=[这是xxx的内容])

undefined vs undeclared

这是两个不相等的关系.undeclared:在我可以触及的作用域从未创建过。undefined: 我肯定这是一个变量, 只是现在还没有值.

而在ES6语法中,我们在let定义一个变量之前去使用它,那么就会报错,这是临时死区的概念: 在定义这个变量之前的scoped都是临时死区, 无法访问。

1
2
typeof a; // referentceError.
let a;

NaN 与 isNaN

NaN 并不是指“Not A Number”, 虽然它的翻译确实如此.

NaN指的是一个无效的number, Invalid number.

我认为就是经过运算后无法转换成数字类型的都是NaN.

1
2
3
4
5
6
let myAge  = Number(42) // 42
let myCode = Number('tx123') // NaN
myAge - 'Some' // NaN
NaN === NaN // false
isNaN(myCode) // true
Number.isNaN(myCode) // false

negative Zero

让我们看一下-0的特性

1
2
3
4
5
6
7
8
let a= -0
a === -0
a.toString() // '0'
a === 0 // true
a > 0 // false
a < 0 // false
Object.is(a, -0) // true
Object.is(a, 0) // false
  1. -0 等于0
  2. -0 不大于0
  3. -0 不小于0
  4. 只能用Obect.is进行判断

应用:

1
2
3
4
5
6
7
Math.sign(-3) // -1
Math.sign(3) // 1
Math.sign(0) // 0
// 判断正负的函数
function sign (v) {
return v !== 0 ? Math.sign(v) : Object.IS(V, -0) ? -1 : 1;
}

Object.is polyfill

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// TODO: define polyfill for `Object.is(..)`
if (Object.is || true) {
Object.is = (a, b) => {
// 工具函数
const isNegZero = v => v == 0 && (1 / v) == -Infinity
const isNaN = v => v !== v
const xNegZero = isNegZero(a)
const yNegZero = isNegZero(b)

if (xNegZero || yNegZero) {
return xNegZero && yNegZero
} else if (isNaN(a) && isNaN(b)) {
return true
} else {
return x === y
}
}
}

fundational Object

use New Keyword

  1. object
  2. array
  3. function
  4. data
  5. regexp
  6. error

don’t use new Keyword

  1. string
  2. number
  3. boolean
1
2
3
4
var sum = new Function('a', 'b', 'return a + b');
console.log(sum(2, 6));
// expected output: 8
Function.length // 数组参数的长度

抽象运算符(Abstract Operation:ToPrimitive)

如果我们要使用到基础类型, 那么我们就会通过一个算法去转换它, 成为我们需要的类型

number

  1. valueOf()
  2. toString()

->to number
原则: 把所有值转换为number后返回

  1. Object: 调用valueOf方法
  2. String: 调用toPrimitive
  3. 调用顺序是: valueOf()->toString()
  4. 但是要注意, 所有的valueOf都是返回this
    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
    // 最普遍的例子
    “” -> 0
    “0” -> 0
    “-0” -> -0
    “009” -> 9
    “3.1415926” -> 3.1415926
    “0.” -> 0
    “.0” -> 0
    “.” -> NaN
    “0xaf” -> 176
    // 特殊字符
    false-> 0
    true-> 1
    null-> 0
    undefind-> 0
    // 特殊例子
    [''] -> 0
    [0''] -> 0
    ['-0'] -> -0
    [null] -> 0
    ['undefind'] -> 0
    [1, 2, 3] -> NaN
    [[[]]]-> 0
    // 如果你重写了valueOf()方法,那么会应用到强制转换上
    {
    valueOf() {
    return 3;
    }
    }
    num = {valueOf() {return 3}}
    num + 1 // 4

String

  1. toString()
  2. valueOf()

->TO String:
原则: 把所有的值强制转化为String后返回

  1. Object: 调用toString方法
  2. String: 调用toPrimitive
  3. 调用顺序是: toString()->valueOf()

看个例子:

1
2
3
4
5
6
7
8
[]->""
[1, 2, 3]-> '1, 2, 3'
[null, undefined]-> '.'
[[[], [], []], []]-> '...'
[...]->'...'

{}-> ‘[Object Object]’
{toString() {return 'X'}}-> "X"

->toBoolean

  1. false
    • “”
    • 0/-0
    • null
    • NaN
    • false
    • undefined
  2. true: !false

coercion rules

大概有以下规则

  1. ‘+’: 如果为加号, 只要有一边是字符串, 则会把两边作为字符串处理
  2. ‘-‘: 只会把运算符两边作为数字来处理
  3. 布尔值:
    • !!
    • 0: false/ !0: true
    • Boolean(value)

常见的触发场景:

  1. 使用模板字符串
  2. 字符串拼接(使用+号)
  3. 对对象的某个属性进行数学运算的时候, 会触发
    1
    2
    3
    4
    5
    6
    const addAStudent = number => number + 1;
    addAStudent(student.count) // 171
    // solution1
    addAStudent(+student.count)
    // solution2
    addAStudent(Number(student.count))

    隐式转换

    访问字符串的长度值,就是一种隐式转换:
    string是基本类, 而这个基本类并不是一个对象,那么js会判定你想要把string当做一个对象访问,以便获取到String的长度值。
    “someText”.length

在所有语言, 我们都要处理类型转换。而在js中,我们遵循的基本原则就是aka(讲道理,我理解就是上面说的三种转换)

边界转换事件

  1. ‘’ -> number
  2. boolean -> number
  3. 3>2>1 || 1>2>3
    边界转换发生在字符串和数字转换之间

强制转换的应用

每个语言都要处理类型转换, 而每个语言都存在边际转换的情况,我们要做的是掌握这些边际问题,然后应用起来。

但是我们可以通过一些编码风格防止这些边界情况,例如使用typescript.以及,在你的代码中使用全等===。

营造学习文化

通过code review的方式, 让团队形成这样一种文化, 有助于帮助成员更好的了解他手中的工具, 当成员收到了代码审查,应该认真的去看,如果你发现了一些愚蠢的事,那么你应该让他过来,认真的说出这件事,让他避开那个边界情况做这样的事,是让大家成为更好的开发者。
你写的代码就像你在跟代码阅读者沟通, 他们应该可以理解。不然你在做的东西就是shit.浪费时间。

code communication

  1. 好的代码本身就是注释
  2. 但是注释本身也是必要的,特别是在一些复杂的业务逻辑上,在入口函数就说明它的入参结构, 功能, 代码组织方式,会减少很多维护者阅读代码的时间

隐式转换

  1. 隐式转换在一些写静态语言的人看来,是一种魔法,他们无法理解,为什么要做这样的事,所以认为这是js语言的弱点。
  2. 隐式转换是一种抽象概念。不是所有抽象都是好的,但是有些抽象是必要的。
  3. 提升代码清晰度的一个方法是,抽象不必要的细节,让阅读者关注我们需要他关注的地方。最常见的例子是: 比较运算符
  4. 我希望你是拥有分析能力的工程师,而不是写代码的猴子

理解特性

原则1: 有用的判断依据是, 你需要读者专注的点是重要的

原则2: 危险的是, 你写的东西如果有让人看不懂的地方,那么这就是危险的

原则3: 让读者能看得懂你的代码,并理解它

== 与 ===

两者区别:
=== 会检测类型与值, == 只会检测value, 那么意味着会触发强制转换

== 算法

  1. 优先转换成数字进行比较
  2. 若为非数字的原语(string, boolean, object, array), 那么会优先转换为原语

边际问题:

1
2
[] == false;
[] == true;

你应该避免进行以下的比较

  1. 使用 == 比较 “”/0/“ ”
  2. 使用 == 比较非原语(boolean/string/number)
  3. 如果要使用到 == true or == false,那么我更建议你去使用 === 做比较

优先使用 ==的情况

遵循两个原则

  1. 明确类型,比不知道类型好
  2. 静态类型(Typescript)不是唯一的方式让你去明确你的类型的方法

如果了解 == 双方的类型

  1. 场景1: == 不适用与不知道类型的比较场景, 当你不确定比较元素的类型的时候,不要使用==
  2. 场景2: == 适用于你知道两者的类型,并且期望它进行转换的场景(当你确定类型一样的情况下 == 与 === 是一样的情况)
  3. 场景3: 两者元素类型不一样的时候,使用 === 这样的逻辑注定错误

总结: 如果类型确定,那么优先使用 == 将是你最好的选择

如果不了解 === 双方的类型

  1. 建议: 如果你不能确认比较双方的类型是什么,那么你不够了解你的代码,建议重构, 基准是你能明白类型是什么
  2. 类型不能确定会让你代码的阅读者困惑, 所以当你需要给代码阅读者明显的信号,让他注意这个地方的时候,请使用 ===
  3. 如果类型处于不确定的状态下, 使用 === 真的是一个保护措施

总结: 如果你代码中,不能确定类型,那么使用 === 保护你的代码时必要措施
当然,如果你代码里都是 === 那么,你要表达的东西是,我不相信(不知道)每一个比较两边的类型

我们写的代码应该是类型确定和明显会让代码质量更高,如果类型确定,那么优先使用== ,如果类型不确定,那么优先使用 === 保护你的代码

3. advanced_scoped.md

Posted on 2020-04-12 | In deep-js-foundations-v2-noteBook

Lexical & Dynamic Scoped

lexical scoped: 指的是编译器, 解析器, 处理器执行之前,进行了一此预编译, 这就是词法作用域。词法作用域不是在运行时决定的, 是在编译的时候决定的。

Dynamic Scoped: 像bash是一种解释型语言, 他的作用域是动态作用域。因此它没有词法作用域, 不是编译型语言。

Lexical Scoped

  1. 词法作用域,其实映射的概念就是预编译,js在运行之前进行了预编译,把作用域, 和作用域内的变量以及函数信息都保存起来, 供引擎运行时使用。
  2. 词法作用域定下来就确定了, 一次编译,运行时就可以对此进行优化。
  3. 作用域之间是层级关系。

Dynamic Scoped

  1. 动态作用域指的是, 黑箱, 我只有打开它(运行的时候)才知道是怎么回事。
  2. 动态作用域下, 你对一个变量引用的结果取决于你的上下文环境。
  3. 所以, 动态作用域是在运行时候确定的, 而词法作用域是在编写代码时确定的。但是js有一种机制, 提供了类似的动态作用域的效果。

function scoped

  1. 函数作用域给了我们隔离外部作用域的机会(提供了命名空间), 我们应该尽量的保持函数内操作的变量私有。
  2. 并且持续遵循最小变量原则。函数内的变量函数内自己去创建和消化(object记得要深拷贝)。

IIFE Pattern(立即执行函数表达式模式)

  1. 使用函数表达式,创建一个作用域, 立刻调用它, 产生了作用域后, 执行函数体内容, 作用域消失。只运行一次,就消失了, 不会留下副作用。比如变量污染。
  2. IIFE是表达式, 而不是函数定义式。
  3. 如果你需要一个函数表达式,那么你完全可以使用IIFE去做这件事。比如try-catch语句
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let a = (() => {
    try {
    someFn() // return value.
    }catch() {
    return 'defaultValue'
    }
    }())

    let value = "some";
    ((value)=> {
    console.log(value);
    value = "big";
    })(value);
    console.log(value); // some

Block Scoping

块作用域的特点: 只有在使用let/const定义时的作用域有效, 出了定义的作用域就无效.

块作用域的产生:

  1. 使用IIFE
    1
    2
    3
    4
    5
    6
    let value = "some";
    ((value)=> {
    console.log(value);
    value = "big";
    })(value);
    console.log(value); // some
  2. 在花括号内使用let/const定义
    1
    let a = 2; {let a = 1; console.log(a)};console.log(a)

let vs var (个人认为: const > let > var)

有作用域好过没有作用域。不用考虑边界情况.

当然,除非你想利用hosting(提升), 那么可以使用var.

一个有趣的冷知识:

1
2
3
4
var a = 1;
var a = 2;

// a在内存中中是同一个地址, 因为在第一次定义的时候在内存中已经有a这个变量了, 第二次定义做了一次查询而已.

explicit let block

1
2
3
4
5
6
7
8
9
(sum) => {
{ // 大胡子推荐这种写法,显示的去告诉大家我需要let的块级作用域, 明确变量的范围
let a = 1,
b = 2,
c = 3;
sum = a + b + c;
}
return sum;
}

const

你应该优先使用const, 之后再是let.

当时,const也存在一定的问题, 使用const, 不可以二次赋值。但是如果const定义的不是基础类型(string/boolea/number), 而是数组的话, 那么const的行为就有些怪异了。历史因素导致的, 当我们使用object类型(数组)的时候, 变量内存的是一个地址,而,这个地址中存的是对象属性中的地址。而我们修改属性的地址时, 实际上, 变量的地址是没有改变的。来看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const arr = [1, 2, 3]
arr = [2, 3, 4] // Throw Error
arr[0] = 2;
arr[1] = 3;
arr[2] = 4;

const obj = {
a: 1
}

obj = {} // Throw Error
obj.a = 2;

// 解决方案: freeze 但是注意, 分浅冻结和深冻结
Object.freeze();

从语义上理解, const是常量,意味着我不会再给他重新赋值。

遵循两个使用原则: 1. 原语类型 2. 只赋值一次, 不会再次赋值

Hoisting

在es的规范中,没有提及提升,我们所说的提升其实是一个比喻。就像我们说的词法作用域,都是一种比喻,一种理解这个概念的介质, 来看个例子:

1
2
3
4
a; // undefined
b; // undefined
var a = 1;
var b = 2;

实际上,js引擎把以上的代码转换成了这样: 把变量定义,函数定义自动的放在函数执行的顶部。

1
2
3
4
5
6
7
8
var a;
var b;


a; // undefined
b; // undefined
a = 1;
b = 2;

提升的特性: 变量定义提升,函数定义提升, 函数表达式不提升。

1
2
3
4
5
6
7
8
a(); // a
function a () {
console.log("a");
}

b(); // b is not a function
var b = () => {console.log("b");};
b(); // b

关于函数提升这一点,我们能理解到, 函数提升, 其实本质上就是做了C语言中,提前把要用的函数暴露出来,这一点他们是一致的。

同时,我们也可以用预编译来解释这件事, 预编译只对LSH做处理,也就是只处理收集了var b这个信息,而没有获取到RSH的信息,所以在使用b()的时候,没能获取到函数体本身.获取到函数体本身要到执行阶段才能获取到.

Hoisting explames

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = 'a';
function b () {
console.log(a); // undefined
var a = 'b';
}

// 实际上,js引擎做了以下处理
var a = 'a';
function b () {
var a; // 初始化未赋值, 那么所有的初始值都是 undefined
console.log(a); // undefined
a = 'b';
}

其实得出这个结果一点也不怪异, 想想之前说的, 预编译阶段,在预编译阶段,function b 中就存有自己的变量a, 那么在执行阶段,console.log语句拿a变量其实是从,b函数的作用域中拿a。

let doesn’t hoist? No.

1
2
3
4
{
a = 'a'; // TDZ error
let a;
}

这有个概念叫临时死区, 在用let定义之前的区域都为死区,不可以在该区域使用a变量, 否则会报错。

大胡子说, 其实var和let/const都进行了提升,只是提升的行为不一样, var提升了变量定义,并且初始化了这个变量, 赋予初始值undefined.但是let/const也进行了提升, 只是没有进行初始, 这里没有进行初始化等价于不可以去使用它(是不是很像临时死区)

2. scope.md

Posted on 2020-04-12 | In deep-js-foundations-v2-noteBook

Scope 作用域

overview

  1. Nested Scope
  2. Hosting
  3. Closure
  4. Modules

当你遇到作用域相关的问题,回答两个问题

  1. What position is it in? (该变量在哪个位置)
  2. What scope does it belong to? (该变量在那个作用域)

js是一种编译型(解析型)语言: 当在逐行执行代码之前,js引擎会先扫描一次代码, 如果你有任何的语法错误,js会抛出错误。(所以它在执行语句的时候就编译了一次?)

在编译理论中: 编译是高级语言编写转换成机器可执行代码的处理过程

  1. lexing and tokenization.(就是把代码转成抽象语法树AST: abstract syntax tree)

  2. code generation(把AST转换成计算机可执行的代码)

    很多观点认为, js是即时编译的, 就像薛定谔的猫,如果你在第十行有个语法错误,那么你在运行到第十行才发现。这个观点是错误的, 因为,如果我们第十行有错误,那么1-9行都不会执行。并且抛出错误.

所以,在js执行之前,会先走一次预编译, 这次编译会生成AST.其实这和那些静态语言类似, 比如java.js并不是运行时编译的即时编译语言.

函数和块作用域一起,组成了js的scope.

compilation & Scope

在编译阶段,我们有个scope管理器和一个编译器。
编译过程中,scope管理器中会维护一个作用域, 每个作用域里存放变量,以及变量的初始值undefined。在处理完所有的作用域时, 我们处理后的信息移交给js引擎, 用于执行。

所以,js所获得关于scope的信息,是在第一次编译的时候获得的,然后在运行时使用这些信息, 但是这些信息是在编译的时候确定的.

这样有一个好处,就是我在编译阶段获取到的信息可以有很多优化的空间。

excute code

编译阶段与执行阶段是两个不同的过程。

在编译阶段,我们更多的去处理的是定义语句(LHS): var/let/const/funcion

除此之外,剩下的都是执行阶段要做的事.

let a = 1;

let a is LHS
= 1 IS RHS

在执行阶段,js引擎获得了变量的作用域信息(这些引用信息其实就是每个变量现在的内存地址, 代码执行的时候其实也就是不断的往内存里放东西),就开始执行, 关于变量,还有定义的信息,都从编译阶段获取的scope信息中获取.(我们写完代码后,作用域是不会变的)

查找顺序, 本级作用域-> 父级作用域->父级的父级作用域-> …… -> 全局作用域 -> undefined

所以在js运行的时候分两个阶段,1. 获取变量(函数也是一种特殊的变量)信息, 也就是预编译的过程 2. 根据这些信息执行运算(执行代码内容)

lexical scope review

js不是interpreted(解释型)语言和逐行执行的语言。逐行执行只是在他运行的第二阶段的执行顺序。js是编译型语言,编译过程分为两个阶段: 1. 在LHS切分作用域, 获取变量信息(对应的东西放进对应的篮子里), 在这个阶段我们可以提前发现错误, 完成信息收集后,把收集到的信息传递给js引擎(由于我们提前获取到了信息,在我们的执行阶段可以进行进一步的优化) 2. 拿到变量信息,开始执行RHS, 执行阶段使用阶段1获取的信息,进行运算

基于它的编译模型, 也就不奇怪为啥所有变量一开始的初始值都是undefied了。

现在我认为,我们的代码就是往内存中放东西。

Dynamic global variables

1
2
3
4
5
6
7
8
function a() {
c = 0;
}
c // throw error

a()

c // 0

Js的历史原因导致了这种动态全局变量的产生.尽可能的避免产生这种全局变量.

nested scope

嵌套scope的分析和单层scope分析一样,但是多层嵌套还是会减小代码的可读性,让代码不清晰.

error type

  1. reference error: 函数调用产生
  2. undefined error: 变量没有定义产生

undefined and undeclared

undefined: 你定义了一个变量而为赋值, 那么这个变量就是undefined

undeclared: 你使用了一个变量(访问了也算),但是这个变量在内存中找不到(也就是在编译的第一阶段,收集变量信息的时候就报错了)

5.closure.md

Posted on 2020-04-12 | In deep-js-foundations-v2-noteBook

Closure

闭包已经是计算机编程领域中很流行的概念。

Brendan Eich: js的作者
一开始是想把scheme放在浏览器跑的。(scheme: 一种旧的函数式编程语言)
但是网景公司想要一种类似java的语言。所以….结合两者, 出现了javascript。

其实我们都在使用闭包的概念进行编程, 像异步ajax.

What’s Closure?

Closure是函数有能力记住和访问变量的词法作用域, 在函数本身已经执行完成后。(数据持久性)

这是因为,js引擎的垃圾回收机制, 执行我们的代码的时候,js维护着一个调用栈。在函数执行完成的时候,由函数的垃圾回收机制去处理这个调用栈(调用栈内包含函数的词法作用域), 要销毁的时候,发现还存在引用。那么垃圾回机制就不处理它。这就导致这个词法作用域保留了下来,也让该函数具有了数据持久性。

注意了,学院派倾向于把闭包的概念应用在单个的变量上,以变量为单位。而在js引擎里,闭包是基于词法作用域的。因此,如果你有个变量, 存在大量的数据, 那么该变量是不会被垃圾收集机制收集的。

1
2
3
4
5
6
const timer = (text) => {
setTimeout(() => {
console.log(test)
}, 1000)
}
timer('Hello World.')
1
const printer = x => () => console.log(x);

Closure var

1
2
3
4
5
6
7
8
9
10
var teacher = 'Kyle';

var myTeacher = () => {
console.log(teacher);
// 这里的变量引用是软链接,不是自己创建一个封闭的作用域
// 它并没有把这个变量抓住, 生成一个副本
}

teacher = 'Suzy';
myTeacher(); // Suzy
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
for(var i = 0; i < 4; i++) {
setTimout(() => {
console.log(i)
}, i * 1000);
}

// 解决方案1: let
for(var i = 0; i < 4; i++) {
let j = i;
setTimout(() => {
console.log(j);
}, j * 1000);
}
// 解决方案2: 匿名函数
var main = () => {
for(var i = 0; i < 4; i++) {
((i)=> { // 在setTimeout外面写一个匿名函数,新建作用域, 形成闭包变量
setTimeout(() => {
console.log(i)
}, i * 1000);
})(i);

}
}
main();
// 解决方案3: 匿名函数
var main = () => {
for(var i = 0; i < 4; i++) {
setTimeout(((i) => {
return () => {
console.log(i)
}
})(i), i * 1000);
}
}
main();

Module Pattern

要了解什么是Module, 让我们先看什么不是Module.

1
2
3
4
5
6
7
var workspace = {
name: 'Kyle',
ask (quetion) {
console.log(quetion);
}
}
// 这是命名空间,并不是模块化

模块的化的概念中有一个封装的概念, 其实指的是隐藏数据和行为, 只暴露出单一的接口供使用者使用.而封装内部的东西都是私有的,对于外部来说是不可见和不可操作了。唯一可以交流的只有暴露出来的接口。

Modules encapsulate data and behavior(Method) together.The state(data) of a module is held by its methods via closure.

1
2
3
4
5
6
7
8
9
10
11
var workspace = (function Module(teacher) {
var publicAPI = { ask };
return publicAPI;

function ask (quetion) {
console.log(teacher, quetion);
}

})('Kyle');

workspace.ask('what?');
1
2
3
4
5
6
7
8
9
10
11
12
13
 
function Module(teacher) {
var publicAPI = { ask };
return publicAPI;

function ask (quetion) {
console.log(teacher, quetion);
}

};

var workspace = Module('Kyle');
workspace.ask('what?');

在早期, js中很多框架和工具中,都使用这种方式去模块化。

module and node.js

ES6 module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// file is workspace.mjs
var teacher = 'Kyle';

// 暴露单个ask
export default function ask (quetion) {
console.log(teacher, quetion);
}

// 或者使用多个
export default {
ask,
}

// main.js
import ask from 'workspace.mjs';

ask('what?');

// 或者你可以给你模块赋予别名

import * as workspace from 'workspace.mjs';

workspace.ask('what?');
<123…7>

Hawei

Fullstack Engineer

61 posts
16 categories
3 tags
RSS
© 2022 Hawei
Powered by Hexo
|
Theme — NexT.Muse v5.1.4