You-Dont-Know-JS-学习笔记-Book2-ch2

Chapter 2: How Objects Work

对象是怎么工作的, 对象不仅仅是一些属性的容器,尽管这是对象最常用的使用场景。

新知识点: “metaobject protocol” MOP。 [wiki](https://en.wikipedia.org/wiki/Metaobject,
MOP 对于理解对象行为有很大帮助

Property Descriptors 属性描述

一个对象都会有一个属性的描述对象,这个对象也有一些属性来控制对应属性的行为
Object.getOwnPropertyDescriptor(..) 是 ES5 提供的一个方法,可以获取属性的描述对象

1
2
3
4
5
6
7
8
9
10
11
12
const obj = {
name: "tom",
age: 10
}

Object.getOwnPropertyDescriptor(obj, "name")
// {
// configurable : true,
// enumerable : true,
// value : "tom",
// writable : true
// }

同样的还有另一个方法 Object.defineProperty(..) 可以来定义这个属性的行为、值。这个方法不仅可以添加,也可以覆盖已经存在的属性。即使存在的属性 configurable = false 的情况。

1
2
3
4
5
6
7
8
const obj = {}
Object.defineProperty(obj, "name", {
value: "Tom",
enumerable: true, // 默认 false
writable: true, // 默认 false
configurable: true // 默认 false
})

以上两个方法都有对应的 ”复数“ 方法,Object.getOwnPropertyDescriptors, Object.defineProperties(),

Accessor Properties 访问类型(访问器)属性

就是指 getter 、setter 类型的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let user = {
name: "John",
surname: "Smith"
};

Object.defineProperty(user, 'fullName', {
get() {
return `${this.name} ${this.surname}`;
},

set(value) {
[this.name, this.surname] = value.split(" ");
}
});

alert(user.fullName); // John Smith

for(let key in user) alert(key); // name, surname

Enumerable, Writable, Configurable

对象属性(properties),除 value 外,还有三个特殊的特性(attributes),也就是所谓的“标志”:

  • writable — 如果为 true,则值可以被修改,否则它是只可读的。
    是否可修改,控制这属性是否可以赋值修改(”=“),不过即使是fasle,依然可以通过 Object.defineProperty(..)来重新赋值

  • enumerable — 如果为 true,则会被在循环中列出,否则不会被列出。
    是否可枚举属性,控制这属性是否可以展示,例如在 Object.keys Object.entries for ...in循环 ,以及在复制、赋值中: ...解构Object.assign,都会忽略,不可枚举的属性

  • configurable — 如果为 true,则此属性可以被删除,这些特性也可以被修改,否则不可以。
    是否可配置, 这里的配置指的是 重新定义、删除, 这里和 writable 区别就在于,configurable: false 之后,即使使用 Object.defineProperty 也无法修改,相当于 ”锁定“了,也就是说 可以从 true/false -> false, 但是无法从 false -> true.

Object Sub-Types 子类型

子类型中,最常见就是 array 和 function了。 function 在js 是比较特殊的一个类型

通过 “子类型”,我们指的是一个派生类型的概念,它继承了父类型的行为,但又专门化或扩展了这些行为。换句话说,这些子类型完全是对象,但也不仅仅是对象。

Arrays 数组

通过数字索引存储值的对象,通过访问 数字下标 来获取指定的值。像是一个所有属性都是数字的对象,而且黑一个数字定义一个非数字的属性也是合法的,但是非常不推荐这样做。

语法为 [...] ,例如: const arr = [1,2,3] , 起始的下标为 0,

有一些细节点主要注意:

  1. 使用非数字类型访问
    一般来说。普通对象会将 属性名 作为字符串对待,而 数组会将 他们当做 数字 对待
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

const arr = [1, 2, 3, 4]

const obj = {
valueOf(){
return 3
}
}
// 正常操作
console.log(arr[0]) // 1

// 非常规操作
console.log(arr["1"]) // 2

// 对能完全转成数字的值。对转成数字 然后取对应的值,当然仅限于原始类型
// 大家知道在一些运算中,可能会调用 对象 valueOf 方法,进行转换,然后计算

  1. 空元素
    数组中可以存在“空元素”, 既不是null 也不是undefined, 虽然获取时返回 的是 undefined. 但实际上并不是。 在 each、map 方法去就可可以看到明显的区别,“空元素”是不会参与循环的, 会被忽略。

Function 函数

函数有两个属性,name、length, name 是函数的名字,length 是参数的数量,指该函数期望传入的参数数量,即形参的个数。也不报错剩余参数个数, 如果是匿名函数,name 就是个空的

函数也可以添加自定属性,应为他也是一个 Object。

1
2
3
4
5
6
function myFunc(arg1, arg2, ...moreArgs){
//
}

myFunc.name = "myFunc"
myFunc.length = 2

作者不推荐在函数上使用自定义属性,推荐使用 Map/WeakMap 来替代。不过有一个库也会给函数增加一些属性,方便实现一些功能,仁者见仁智者见智把。

Object Characteristics 对象特征

  • extensible 扩展
  • sealed 密封
  • frozen 冻结

Extensible

一个对象可否添加新的属性,默认所有对象都是可扩展的。Object.preventExtensions() 可以让一个对象变的不可扩展,也就是永远不能再添加新的属性。

1
2
3
4
5
6
7
8
9
const obj = {
name :"Tom"
}

Object.preventExtensions(obj)

obj.name = "Cat"

obj.size = 100 // 静默失败 , 严格模式下会抛出错误

Sealed

???

Frozen

一个对象是冻结的是指它不可扩展,所有属性都是不可配置的,且所有数据属性(即没有 getter 或 setter 组件的访问器的属性)都是不可写的。

一个不可扩展的空对象同时也是一个冻结对象。

Extending The MOP

???

to be continued

作者

Fat Dong

发布于

2022-10-08

更新于

2022-10-08

许可协议

You-Dont-Know-JS-学习笔记-Book2-ch1-2

Accessing Properties 访问属性

使用 . (点) 、[](方括号)访问已经存在的属性, 通常使用 “.” 就可以了,但是如果属性中包含一些在语法上造成解析错误的字符等,必须用 “[]”。例如包含空格、中横线、 数字开头等等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 推荐使用
const obj = {
name:"name",
age: 10
"a name" :"a name",
"a-name":"dsd"
"123a":234
}
obj.name
obj.age


obj['a-name']
obj['1234']

当然属性还可以使用动态的表达式,

1
2
3
4
5
6
function name(){
return 20
}
const obj = {
name : name() + 1
}

Object Entries

获取 一个对象的属性,返回一个 array, Object.entries() 也是在ES6 提供的新API,可以返回其可枚举属性的键值对的对象。
ES 2019 提供了另一个方法 Object.fromEntries 用来反向创建一个对象

Destructuring 解构

ES6 添加的新语法, 对一个对象解构,就相当于声明一或者多个变量,然后把这个对象的同名的属性的值赋值给这些变量。如果源对象没有对应名字的属性,则赋值 undefined。

1
2
3
4
5
6
7
8
9
10
11
// 手动解构
const obj = {
name: "xx",
age: 10,
school: false
}
const { name, age, school: inSchool , from = "beijing"} = obj

// 如果没有吗,也可以传一个默认值,例如:from,没有from 所以 赋值"beijing"
// 也可以给 某个属性“重命名”(看起来像是), 例如: 把school 的值赋值给 inSchool

通过上面的例子,都是使用 const 其实 letvar 同样是可以的, 结构出来的变量,和原目标没啥关系, 解构其实就是从源对象 到 目标对象 访问 和赋值的一个过程

1
2
3
4
5
6
7
let fave;

// surrounding ( ) are required syntax here,
// when a declarator is not used
({ favoriteNumber: fave } = myObj);

fave; // 42

Conditional Property Access 条件访问

这个目前不支持, 除非使用 typescript , obj?.a , 如果 obj不为 undefined,怎么返回 obj.a 。也可以链式调用 obj?.a?.c 等等。。。

Accessing Properties On Non-Objects 访问非对象的属性?

按照正常思维,原始数据类型没有属性,不过也照样可以访问 属性、方法在非对象类型的变量。

1
2
3
4
const fave = 42
fave // 42

fave.toString() // "42"

关于 访问原始数据类型的属性,通过对应的隐含的实例化对象来访问, 这个过程通常称为“对象包装”,就相当于一个“盒子”,把原始值放入这个盒子(对象容器),就可以访问“盒子”的属性, 例如上面的例子,访问 42 其实就是将 42 转为 Number 对象,然后访问属性。

需要注意的是 null 和 undefined 也可以被对象化, 通过 Object(null) Object(undefined) 来实例化。通常情况下, JS并不会自动去对象化这些空值,所以 访问控制的属性、方法会报错

对象包装 还有一个对应相反的过程,就是获取一个对象的原始值,以 Number 为例, 42 -> Number(42) 、Object(42), 也可以 把 Number(42) -> 42,最简单的就是通过 * 、- 等运算符来实现

Assiging Properties 分配属性? 指定属性?

对象可以在申明的时候指定属性,也可以在申明之后再添加,

1
2
3
4
const obj = {}

obj.num = 20

= (等号赋值)有时候会失败,或者静默、抛出错误, 也可以不用直接指定值,通过 setter 函数来 实现某些操作,
严格模式下,对原始类型添加属性对抛出错误

ES6 添加了一个新的API,Object.assign 一次来指定多个属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
Object.assign(anotherObj, myObj)

Object.assign(
{
prop1:"value1",
prop2:"value2"
}
{
newprop1: "new value1",
newprop2: "new value2"
}
)

Object.assign 将第一个对象作为目标对象,将第二个、第三个等对象 复制到 目标对象上。复制方式就像 对象解构 一样。

Deleting Properties 删除属性

一个属性的值,只能通过 delete 关键词 来删除,

与通常误解相反, JS delete 操作, 不直接通过 GC 解除分配、释放内存,做的唯一一件事就是删除某个属性,如果这个属性是另一个对象的引用, 以及没有其他已存在的引用,这个值可能在未来的GC扫描中被移除。

如果在其他非对象属性、或者是不可删除的属性 的情况下使用 delete ,,会静默失败,严格模式下抛出错误

删除一个属性, 和给这个属性赋值 undefined 、null 是有区别的。一个属性被赋值 undefined 的,或者是稍后被初始化,都会在这个对象上,仍然可以被枚举显示。

Determining Container Contents 确定容器内容

确定这个对象上时候有某些属性, 这个方式比较多了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

"attr" in obj
obj.hasOwnPropterty("attr")


// 一个属性时undefined 和 不存在的区别
const obj = {
name:"name"
}

obj.age // undefined
"age" in obj // false

obj.name // name
"name" in obj // true


delete name
obj.name // undefined
obj.hasOwnPropterty("name") // false

检测自身属性

in 和 hasOwnProperty 区别 :in 会查找原型链, hasOwnProperty 只判断本身的属性

ES2022 提供的一个新的API, Object.hasOwn,如果指定的属性是该对象的直接属性——Object.hasOwn() 方法返回 true,即使属性值是 null 或 undefined。如果属性是继承的或者不存在,该方法返回 false。它不像 in 运算符,这个方法不检查对象的原型链中的指定属性。

建议使用此方法替代 Object.hasOwnProperty(),因为它适用于使用 Object.create(null) 创建的对象以及覆盖了继承的 hasOwnProperty() 方法的对象。尽管可以通过在外部对象上调用 Object.prototype.hasOwnProperty() 解决这些问题,但是 Object.hasOwn() 更加直观。
来自MDN

列出所有内容

Object.getOwnPropertyNames(..) ,返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组。 而 Object.keys 则是返回可枚举的属性

要想获取 Symbol 类型属性,可以使用 Object.getOwnPropertySymbols(..)

临时容器

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
 function formatValues({ one, two, three }) {
// the actual object passed in as an
// argument is not accessible, since
// we destructured it into three
// separate variables

one = one.toUpperCase();
two = `--${two}--`;
three = three.substring(0,5);

// this object is only to transport
// all three values in a single
// return statement
return { one, two, three };
}

// destructuring the return value from
// the function, because that returned
// object is just a temporary container
// to transport us multiple values
const { one, two, three } =

// this object argument is a temporary
// transport for multiple input values
formatValues({
one: "Kyle",
two: "Simpson"
three: "getify"
});

one; // "KYLE"
two; // "--Simpson--"
three; // "getif"

一个对象 是一个临时传输的容器??而不是一个有意义的值!!!

Containers Are Collections Of Properties 容器是属性的集合

属性其实是有两部分组成: 名(键)、 值,对象最常见的用法就是作为存储许多值(或者是属性)的容器。我们通过以下方式管理一个容器对象:

  • 定义属性, 可以在定时时赋值,或者之后再赋值
  • 给属性赋值
  • 访问(使用)属性
  • 删除属性(delete)
  • 判断属性是否存在
作者

Fat Dong

发布于

2022-10-02

更新于

2022-10-02

许可协议

You Don't Know JS Yet: Objects & Classes-chapter1

面向对象 & 类

基础

Everything in JS is an object. 一切皆对象

最普及、最不正确的,一直在流传的关于JS的“事实”,辟谣开始,让“神话”破灭。

JS 绝对是有对象的,但是并不意味着所有的值都是对象类型,可以说对象是最重要也是最多样化的数据类型,掌握它对学习JS至关重要。

为啥原型(prototypes)、this等等作为三大基础之一,是JS非常重要的核心?以对象开始,对原型(prototypes)有全面的理解,解开this神秘面纱,探索 class.

最常见的将多个值聚集在一起的方式就是使用一个对象,简单来说,对象就是多个 键值对(key/value) ”数据对“ 的集合。当然,还有一些具有特殊行为的子类型的对象,例如数组(数字索引/下标),甚至函数(可以执行调用)。

键(key)通常被称为 ”属性名“, 与之匹配的值称之为 ”属性“

创建对象可以用 myObj = new Object() 这种语法,但是这不是首选,也不是最合适的方法,建议还是使用字面量的方式创建。

先从语法层面说明下一 {...} 大括号对的作用

  1. 划分值,例如:创建字面量对象
  2. 对象解构模式
  3. 模板字符串,例如 ${name}
  4. 定义块作用域, if for
  5. 定义函数体, function demo() {}

Defining Properties 定义属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 直接定义
let myObj = {
name : "Tom",
age : 0
isDeveloper: false
}

// 计算属性
function num(){
return 10
}
let myObj = {
luckyNum : num() + 5,
getLuckyNum: num
}

顺带说下和JSON的区别, 虽然看起来和JSON挺像,但是最大区别是:

  1. JSON的 key 必须要用 双引号" 包起来
  2. JSON的 value 必须是字符,其他原始值:字符串(string)、数值(number)、true、false、 null,以及对象(object)或者数组(array) ), 不可以是一个JS的表达式, 也没有undefined,

在JS的程序中,属性名 可以使有双引号、单引号,或者不写引号也行(除了属性名包含特殊符号),

1
2
3
4
5
6
const myObj = {
name :"战三",
isStudent: true,
"nick name":"老三"
}

还有一些不同点就是, 如果是严格的JSON语法,是不允许注释的,例如: //... /*...*/ ,还有末尾的逗号,也是不允许的。不过JSON 是允许空白字符出现。

关于注释,你可能在某些软件、工具中发现可以注释,那是因为这些软件、工具解析的时候帮你去掉了注释,严格来说,JSON中不能有想JS的注释语法的字符串出现(我的理解)

Property Names 属性名

1
2
3
4
5
6
7
8
9
10
const myObj = {a :"a"}
const anotherObj = {
42: "<-- this property name will be treated as an integer",
"41": "<-- ...and so will this one",

true: "<-- this property name will be treated as a string",
[myObj]: "<-- ...and so will this one"
};

//

如果十几种要用一个对象作为属性名,不要依赖这种隐式的字符串转换,他的行为是不可预期的,强烈建议使用 Map结构

属性也可以是一个表达式

1
2
3
const myObj ={
["x" + (21 * 2)]: true
}

这种写法,必须包含在 [..] 两个方括号之内,会被立即计算求值,然后将结果作为属性名

Symbols As Property Names Symbol 作为属性名

Symbol是 ES6新出的特性, 一个原始值类型。 常用的使用场景就是作为属性名。每一个新创建的Symbol 都是唯一的。就可以很大程度避免属性被覆盖重写。

1
2
3
4
5
const symbolKey = Symbol("可选的描述")

const myObj = {
[symbolKey] :"这是一个symbol属性名"
}

Concise Properties 属性简写

属性名 和属性值的变量名一样的话就可以省略,属性值

1
2
3
4
5
6
7
8
9
10
11
const myname = "xxxxx"

const myObj = {
myname : myname
}

// 简写
const myObj = {
myname
}

Concise Methods 方法简写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const muObj = {
geet :function(){
console.log("hello")
},
// 简写方法
greet2(){
console.log("hello again")
},
// generator也可以
greet3: function*(){
yield "hello 3"
},
*greet3(){
yield "hello 3"
},
// 方法也可以是计算属性名
["gre"+"e4"](){
console.log("hello 4")
},
*["gree6".toUpperCase()](){
yield "hello 5"
}
}

Object Spread 对象展开 - 解构

ES6新语法

1
2
3
4
5
6
7
8
9
10
const obja = {
a:1,
b:2,
}

const objb = {
a:22,
...obja,
c:3
}
  1. 解构是浅复制。可以简单的看做是, for 循环源对象,然后执行了赋值操作 (=),
  2. 解构只赋值了顶层的属性,如果属性是引用类型,只是把引用复制过去了,
  3. 请注意解构的位置,如果有同名属性、方法,而且解构在这些属性之后,就会覆盖已有的同名属性、方法。
  4. 解构值赋值可枚举的自身属性,

... 无法解构到一个现有对象中,解构语法只能是 {...} 字面对象中,也就是创建一个新对象中,如果是像要浅复制,请使用JS提供的API, 如果想要复制对象到一个现有对象中,可以使用Object.assign.

Deep Object Copy 深克隆 copy

JS常见的复制, 例如:解构、Object.assign等,都是浅复制,只适用简单、原始类型的值,

深度克隆是一个比较复杂的问题,例如数字、字符串等方式非常明显直接复制过去的,但是对于引用类型,例如是一个DOM,或者一个对象有循环引用呢?对与如果处理各种边界情况有各种不同意见,所有深度克隆没有单一的标准。

常见的处理方式有:
1、使用一个库,自己配置设置如何处理重复行为(重复引用)、忽略某些情况
2、使用 JSON.parse(JSON.stingify(...)),这种仅限于 这个对象没有循环引用,以及不需要无法被JSON转换的属性,例如:函数

不过最新还有一个方式可以选择,就是 structuredClone(), MDN翻译为 结构化克隆 这个不是JS的标准,这个是由宿主环境提供的API,例如:网络平台(浏览器)

1
const newObj = structuredClone(oldObj);

这个内置的工具底层算法支持循环引用,以及更多的数据类型,但是仍然有局限性,例如:不支持克隆函数、DOM元素。

具体类型可参考:https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#%E6%94%AF%E6%8C%81%E7%9A%84%E7%B1%BB%E5%9E%8B

You Don't Know JS Yet: Objects & Classes-chapter1

https://www.md7.top/2022/08/22/You-Dont-Know-JS-学习笔记-Book2-ch1-1/

作者

Fat Dong

发布于

2022-08-22

更新于

2022-08-23

许可协议

You-Dont-Know-JS-学习笔记-ch4(二)

Chapter 4: The Bigger Picture 大局观 or 更广阔的前景

书接上回

With the Grain 顺着纹理

Grain 这个词翻译为:谷物、纹路,微粒等,在这里指的是 “纹路”。 直译就是,顺着纹路。

顺着纹理 ?像是一句俚语啊, 意思就是:顺其自然,按照正常的规律去做某件事。来源于砍木头,顺着木头纹理去砍,肯定是很顺利,而且不费力,反着来当然也可以,但是费时费力。

具体可参考:https://wiki.c2.com/?WithTheGrain

这一小节是一些理论知识,和语言本身没有太大关系,作者写了一些他自己的观点和建议。

我个人理解的点,总结了几点(可能有误,望指正):

  1. 不要一味的遵守经验教条,例如:某些高级开发者就是这样做的。而是根据实践、结果表现去做决定,是否适合自己或者自己的项目

  2. 敢于逆向思维,你如果想与众不同,就得做与众不同的事情,没人能知道什么方式是最佳的,你得通过自己的能力做决定,无论结果是什么

  3. 一些语言层面的基本常识、实践应该遵守,通常这些实践或者方法、方式会让你更便捷的工作

  4. 学习一些新知识之后,去迭代自己的项目,不要尝试一口气吃一个胖子,应该是去一点一点的迭代,每次去做一小块,然后和你的工作伙伴去讨论、对比这样做是否合适。

这些建议不止在学习 JS 上,任何语言、知识其实都应该如此。

In Order

读此书的顺序,作者建议是:按照正常顺序记录, Book1 -> Book 6,当然你可以有自己的顺序

  1. Book1
    入门,一些JS的基础(当然并非所有,此书不是小白入门书籍)
  2. Book2
    JS支柱之一:词法范围,代码组织方式:模块化,作用域 & 闭包等等
  3. Book3
    JS支柱之二:对象 、类,例如:this如何工作,原型链怎么支持代理,原型链怎么支持 ”类“ 风格的编码方式。
  4. Book4
    JS支柱之三:类型和类型强制,以及JS的语法如何定义我们的代码编写方式。
  5. Book5
    有了三大基础的知识, 异步(Sync)、同步(Async) 控制机制,包括:立刻同步、按时间异步
  6. Book6
    本书结尾,对JS的现状、未来进行了展望,以及未来将要出现在JS的新功能

不过作者还是建议: 第二册、第三册、第四册 可以按任意顺序度,取决你的兴趣,不建议你略过,即使你觉得自己已经掌握了, 第五册的话建议对js更有经验时再来阅读, 最后一册呢,相对独立一点,如果你想找一条捷径来扩大你对JS的了解,也可以在《入门》之后阅读。

作者的一些观点,我还是比价赞同的,好比说的都对,能不能做到是一码事了。。

共勉

待续。。。

作者

Fat Dong

发布于

2022-08-04

更新于

2022-08-04

许可协议

You-Dont-Know-JS-学习笔记-ch4(一)

Chapter 4: The Bigger Picture 大局观 or 更广阔的前景

这一章就是一些心得建议吧:
不要着快速读完一本书,然后着急去读下一本书,而是花点时间回顾温习一下当前这本,然后根据所学到的内容与自己项目中的代码对比一下。

把 JS 语言分为了三大支柱(重点):

Pillar 1: Scope and Closure 作用域、闭包

这个其实是任何语言的基础特性之一,或许没有其他他特性能有更大的影响。

打个比喻: Scope好比桶,variables(变量)就像弹珠,Scope(作用域)就是那些规则,来决定那种颜色的“弹珠”放在对应颜色的”桶里“。

作用域一般是各种嵌套,但是一般情况下,对于任何表达式、语句,仅比该作用域更高或者外层的变量是可以访问,更低、内部的是隐藏、不可见的。

这是作用域模式种被叫做:”词法作用域“, 关于这个词,我搜索了一些解释。

  • 什么是词法(Lexical):

通常来讲,词法指的是定义某个事物,例如:创建声明变量、一个表达式的申明。

  • 词法作用域:

定义表达式并能被访问的区间,也就是一个变量、函数 声明的时候所在的作用域,就是他的词法作用域。可以注意到,代码一旦写下就不会在变了(文本内容方面),这个作用域也就固定了,因此,此法作用域,也叫静态作用域。

简单的介绍一下。后续有空可详细研究了解一下。

这个作用域的边界、变量的组织方式是在解析(编译)时决定的。程序开发者 定义的函数、作用域, 决定了程序中某一部分作用域的结构是什么。

说到 JS 的作用域,就不得不说 ”变量提升“(hoisting):无论变量在作用域的什么地方声明,都会被视为在作用域顶部声明,即使在一个块内,例如:if、for-loop等内,这是 JS 的特色之一,不过后来有了 let const 声明方式之后,这个“特性”被规避了。

变量提升、函数作用域 var 都不足以支持 JS不是 词法作用域的说法。

let / const 声明,有一个错误行为,被称之为”Temporal Dead Zone” (TDZ) ,中文翻译叫:时间死区/暂时性死区,(好难懂!!)。

这个变量存在但是无法使用,原文用了“observable” (可观察到),这个从在Chrome中的错误提示信息可以看出来

1
2
3
4
5
6
7
8
9
10
11
12
13
 if (1) {
// console.log(a);
let a = 12;
}
console.log(a); // Uncaught ReferenceError: a is not defined
// 提示a 未定义

if (1) {
console.log(a); // Uncaught ReferenceError: Cannot access 'a' before initialization
// 无法访问在a初始化之前,说明a是已经存在的,必须在初始化之前使用
let a = 12;
}

这些都不是对词性范围的无效化,而且一门语言独有的特性,这也是所有JS开发者应该学习和理解。

当一门语言把函数作为“first-class values” (一等对象),“闭包” 就是词法作用域的自然结果(正常结果),例如:JS。当一个函数的引用引用到外部的变量,并且这个函数被当做一个值被传递,在其他作用域当做一个值被执行,他仍然可以访问到原始作用域的值,这不就是闭包么。

所有编程语言,尤其是JS,闭包驱动了很多重要的模式,包括模块。这个很好理解,目前JS的模块,对外暴露一些方法、变量,相当于引用到外部的变量么。然后可以访问内部的方法、变量。

对于 “first-class values” 的理解,网上找了好多,大部分以维基百科的定义来说明,这是计算机语言中的一个术语,
翻译过来大致意思就是:一个一等公民(即类型、对象、实体、或值等等)在一门特定的编程语言中能够支持其他大多数实体所支持的操作。这些操作一般来说有:被当做参数传递、能够作为函数返回值、能够被修改、能够被赋给一个变量。
看看这些条件,JS的函数都能够满足。

注: 这一节还是强调了作用域类别:词法作用域(静态作用域),以及关系到闭包形成的原因。还说明了一下 var 虽然导致 变量提升,即使这样,仍然不影响认为JS的 是属于词法作用域类型的编程语言,包括,let 、const 的特性,这些只是语言特性,作为每一个开发者应该去学习了解。

Pillar 2: Prototypes 原型

JS 是少量编程语言其中之一,可以直接创建、明确的创建对象而不用事先用 class 定义它的结构

很长时间,大家都用原型来实现类(对象)设计模式,所以称之为”原型继承“。随着ES6 Class 的出现,更加向着 面向对象(类风格、类模式)编程 迈进。

作者认为:这个对这个新特性的关注,反而隐藏了原型系统的美丽和力量。翻译有点反人类,毫无感情的机翻。翻译成人话应该是:虽然大家都因为有了这个新特性,都去关注和使用,但是不应该忽视了原型系统(原型链)的强大功能。例如:两个对象能通过共享同一个上下文环境 this ,来动态的进行简单的关联、合作(在函数的执行期间)。

关于这一点我理解是:利用原型能更简单直接(和 class对比)的去继承一些属性和方法,例如将对象a的 prototype 指向 对象B,那么a 就可以执行B的方法和属性,

1
2
3
4
5
6
7
8
9
const a = {
name: "A",
say() {
console.log(this.name);
},
};
const b = Object.create(a);
b.name = "B";
b.say();

很简单的就可以复用 say(), 如果换成 class 的话,可能就得写成这样了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A {
constructor(name) {
this.name = name;
}
say() {
console.log(this.name);
}
}
class B extends A {
constructor(props) {
super(props);
}
}
const a = new A("李一");
a.say();
const b = new B("李四");
b.say();

虽然是这样说,但是你可以通过浏览器的开发者工具观察到,其实 class 的行为和 function 一致, class 的实例,还是用过 __proto__ 指向了 class的 prototype. class 的 prototype.constructor 还是指向了 Class 本身。

类只是建立在这种力量上的其中的一种模式。 另一种不同的方向,就是认为对象就是简单的一个对象,忘记类-Class,让他们通过原型链来合作,这种模式被称为 “行为代理”。作者认为:作为组织数据、行为的一种手段或方式,委托要比类继承更加强大。

我到是不是特别同意,虽然 普通对象 通过原型链,简单的引用,更加灵活。

后来几段是直接翻译了一下:

但是大部分语言设计聚焦在 “类继承”,然后其他的就是 “函数式编程”,作为一种“反类”的设计程序,让我很难过,因为它扼杀了任何探索委托作为一种可行的替代方案的机会。

后续的 第三本书 “Objects & Classes”,将会让大家看到 “对象委托”的潜力或许已经超过我们已经意识到的,这不是一个反类的信息,但它故意表示是一个 “类并不是使用对象的唯一方法 “的信息,我希望更多的JS开发者考虑这个问题。

作者比较认同这种 “对象委托” 模式,比类他更加符合js的规律

关于 “对象委托” 我还没能深刻的理解到位作者的意图,待后续仔细阅读理解吧。

Pillar 3: Types and Coercion 类型 和 强制(转换)

这一部分是目前最不被重视的一部分

绝大部分开发者对类型在编程语言如何工作有强烈的误解,特别是在JS中。在更加广泛的社区中,有一股兴趣浪潮开始转向到 “静态类型”方法,使用一些类型感知工具,例如:Typescript 、Flow。

什么叫兴趣浪潮,无非是一些开发者开始使用 TS,Flow 等等可以对类型进行强制检查的语法、工具进行开发。而且颇受欢迎,包括我现在在使用 TS 开发,能提起规避掉一些类型错误问题,即使最后还是要被转成 JS。

作者也同意JS开发者应该更多地了解类型,并且应该更多地了解JS如何管理类型转换。也同意类型检查、检测的工具可以帮助开发者,但前提是他们首先获得并使用了这些知识。啥意思呢,就是你可以使用这些工具、语法、语言等等,但是你得明白其中的原理、工作模式,而不是停留在使用层面。举个可能不太恰当的例子:算术你必须会,但是平时还是会用计算器去算,如果有一天计算器没电了,你还是能计算,假如你不会算术,同时计算器没电了,你这不就 GG 了么??

作者不同意的是:虽然认为JS的类型机制不好,然后要使用语言之外的解决方案来掩盖JS的类型的结论。在程序中我们不必要遵循 “静态类型 “的方式,明确、固定的使用类型。如果你愿意与众人的想法相悖,还有其他的选择。并与JS的想法相悖。(后续还会解释)

可以说,这一点比其他两点更重要,因为任何JS程序如果不适当地利用JS的值类型,以及类型之间的值转换(强制),就不会做任何有用的事情。

后面在第四册 :“Types & Grammar” 中会有多关于JS类型和强制的信息。如果不学习这个重要的支柱知识点,你在JS的基础是不稳定的,充其量是不完整的。

以上内容基本上是翻一下了。

关于作者不同意使用其他的方法、方式来解决JS的类型问题,尤其是编程中遵循静态类型(Typescript就是静态类型)检查,这一点我有点不太同意,这种检查可以避免好多问题,而且依赖于一些工具的语法提示,更加方便的开发。

可能作者认为js这种强制类型转换,是js的优点之一,可以用来完成一些好用的操作,例如:静态类型语言只能同类型比较,例如: go, int 和 int 比较大小,哪怕int 和 uint8都不行, 反观JS 就不一样,普通相等比较,会强制类型转换,而且条件还很多, 而且不同类型之间
的比较,规则也不相同。

待续。。。。

作者

Fat Dong

发布于

2022-07-27

更新于

2022-07-27

许可协议

You-Dont-Know-JS-学习笔记-ch3(二)

this Keyword this 关键字

this 如果有熟悉后端语言的话,可以发现 JS 的 this 和一些后端语言的,有表现的很大不一样的地方。

正如作者所说, this 是 JS最强大的 机制(特性)之一,也是最容易弄错的地方之一。 无论是 this 指向函数本身 或者 对应方法的实例,这都是不正确的。

函数除了作用域之外,还有一个重要特性,它们的作用域影响着它们可以访问内容,这个特性最好描述为:执行环境,而 this 恰恰相当于一个桥梁一个,可以通过 this 去访问这个内容,(这一点,之前确实没有这么想过)

JS的作用域是静态的,因此,取决于你定义 变量、函数的时刻和位置。但是执行上下文是动态的,取决调用方式,无论他在哪里定义甚至从哪里调用。

这个时候, this 就起到作用了,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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 一
function classroom(teacher) {
return function study() {
console.log(
`${ teacher } says to study ${ this.topic }`
);
};
}
var assignment = classroom("Kyle");

// 二
window.topic = "math"
function classroom(teacher) {
return function study() {
console.log(
`${ teacher } says to study ${ this.topic }`
);
};
}
var assignment = classroom("Kyle");

// 三
function classroom(teacher) {
return function study() {
console.log(
`${ teacher } says to study ${ this.topic }`
);
};
}
const obj = {
topic:"math",
assignment: classroom("Kyle")
}
obj.assignment()

// 四
var otherHomework = {
topic: "math"
};
function classroom(teacher) {
return function study() {
console.log(
`${ teacher } says to study ${ this.topic }`
);
};
}
assignment.call(otherHomework);

最后作者总结了一下这样的 好处就是:灵活的使用不同的数据结构来重用一个函数,这样对于某些需求任务还是有用的。

Prototypes 原型

这个词在我接触计算机之前,根本没有概念,什么“原型”??

和 this 比较的话, this 是函数的特性,那么“原型”就是对象的特性,特别是对属性的解析和访问。

作者这里描述的很不错:把原型看做两个对象之间的关系,这个关系是隐藏在背后的,尽管有一些方法暴露、观察他们。原型这种关系发生在对象创建时,他被关联到另一个已经存在的对象。

而且这种原型关联,例如: B -> A (B关联到A), B的属性、方法可以委托给A处理,应为B 本身不具有这个方法、属性。这种委托可以是多个对象(C-> B -> A), 合作完成一个任务(操作)

最简单的声明一个对象:

1
2
3
var homework = {
topic: "JS"
};

homework本身只有一个属性 topic ,但是他有一个默认的关联关系 到 Object.prototype , 而 Object.prototype 上有内置的 toString, valueOf 等方法。

1
homework.toString();    // [object Object]

Object Linkage 对象关联/对象链接

Object提供了一个create方法,用来创建原型关联关系。

1
2
3
4
5
6
7
var homework = {
topic: "JS"
};

var otherHomework = Object.create(homewimagesork);

otherHomework.topic; // "JS"

Fig. 4: Objects in a prototype chain

注意:Object.create(null) 这种特殊情况。没有原型,是一个完全独立的对象。也就是说,无法调用toString,valueOf等方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
var homework = {
topic: "JS"
};

var otherHomework = Object.create(homewimagesork);

otherHomework.topic; // "JS"
otherHomework.topic = "Math";
otherHomework.topic; // Math

homework.topic;
// "JS" -- not "Math"

Fig. 5: Shadowed property 'topic'

另一种更复杂但也许仍然更常见的创建具有原型联系的对象的方式 就是 “原型类”

this Revisited 回顾 this

this 这个时候,在原型委托的重要性就体现出来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var homework = {
study() {
console.log(`Please study ${ this.topic }`);
}
};

var jsHomework = Object.create(homework);
jsHomework.topic = "JS";
jsHomework.study();
// Please study JS

var mathHomework = Object.create(homework);
mathHomework.topic = "Math";
mathHomework.study();
// Please study Math

Fig. 6: Two objects linked to a common parent

和其他语言不一样,与许多其他语言不同的是,JS的这种动态性是允许原型委托的一个重要组成部分,而且类也是如此。

“原型链” 是 JS “面向对象”的原理之一把,也是JS 的 一个重要特性之一。弄明白原型链很有必要。

其实这一章对原型也讲得并不是很深入,一些其他的点并没有讲到,所以 ???

Asking “Why?” 为什么???

作者说:本章预期的收获,JS引擎中还有更多东西比表面看起来还要多。 当开始深入的学习了解 JS的时候, 加强、练习js的重要技能之一就是“好奇心”, 还有就是 “提问题”

其实这也不光是学习JS, 更是学习其他知识的一个“共性”, 保持一颗“好奇心”,多提问题,提出正确的问题能更加深入的了解学习某些知识。

作者

Fat Dong

发布于

2022-07-23

更新于

2022-07-23

许可协议

You-Dont-Know-JS-学习笔记-ch3(一)

Chapter 3: Digging to the Roots of JS 挖掘JS的根源

这个机器翻译,好不舒服,中文概括的话就一个成语:刨根问底。

第二章,对一些语法,模式,行为,进行了一个高级别的调研。这一章节我么将注意力转移到一些低级别(更基础)的特性,这些特性影响(支撑)着我们写的每一行代码。

芭芭啦啦讲了一大堆,反正是,别着急,慢慢来,仔细阅读学习。

Iteration 迭代

译:
程序本质就是处理数据,或者说根据数据做一些决策,而浏览数据的模式、方式对程序的可读性有很大的影响。

迭代呢已经存在N多年了,一个“标准化”的方式,从数据源处理使用数据时,一次只处理其中一块,这是一种常见、有效地方式, 处理数据集合,从第一部分,下一部分,依次迭代,要比一次性处理整个数据更加有效。

作者还用 数据库查询 举了个 “栗子”。

我:
迭代的思想,就是将大问题分解成一个一个的小问题,逐个处理,直到完成。这中思想在计算机领域相当常见。例如一些大文件的拷贝等等,

在语法层面:迭代模式,定义了一个迭代器,对底层数据源有一个引用,暴露了一个方法,例如 next(), 通多调用 next() 可以返返回下一部分的数据。你也不惜要知道有多少块(部分)数据,如果迭代到终点,会返回一些特殊值,或者异常来表示数据已经迭代完成。

Consuming Iterators 消耗迭代器

消耗无非就是使用数据,而且使用之后就不再有了。

ES6 的迭代器实现了上面说的标准,通过手动去调用一个迭代器的 next() 方法来获取数据, 不过也提供了一些语法和API用于标准化消耗迭代器。例如: for … of 循环。

1
2
3
4
5
6
7
8
9
10
// given an iterator of some data source:
var it = /* .. */;

// loop over its results one at a time
for (let val of it) {
console.log(`Iterator value: ${ val }`);
}
// Iterator value: ..
// Iterator value: ..
// ..

另一个语法就是: “…” 操作符,中文名为:扩展运算符。常用语参数接收、数组解构。例如:

1
2
3
4
5
6
7
8
// spread an iterator into an array,
// with each iterated value occupying
// an array element position.
var vals = [ ...it ];
// spread an iterator into a function,
// call with each iterated value
// occupying an argument position.
doSomethingUseful( ...it );

大白话就是,把一个大的数据展开成一个一个小的,传入到接受的容器(变量)里,不正是迭代的思想吗?

Iterables 可迭代

“迭代-消费”协议(规范)从为了从技术上定义迭代器, 迭代器就是一个可迭代的对象。这个规范从一个迭代器自动创建迭代实例,一个迭代器可创建多个迭代实例,一个迭代器可被创建、使用多次。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

// an array is an iterable
var arr = [ 10, 20, 30 ];
// for of循环
for (let val of arr) {
console.log(`Array value: ${ val }`);
}
// Array value: 10
// Array value: 20
// Array value: 30

// 数组
var arrCopy = [ ...arr ];

// 字符串
var greeting = "Hello world!";
var chars = [ ...greeting ];

chars;
// [ "H", "e", "l", "l", "o", " ",
// "w", "o", "r", "l", "d", "!" ]

Map 结构的迭代

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
// given two DOM elements, `btn1` and `btn2`

var buttonNames = new Map();
buttonNames.set(btn1,"Button 1");
buttonNames.set(btn2,"Button 2");

for (let [btn,btnName] of buttonNames) {
btn.addEventListener("click",function onClick(){
console.log(`Clicked ${ btnName }`);
});
}

// 换成字符串, map结构迭代, 返回一个数据 [key,value], 对应key, value。
var bnames = new Map()

bnames.set("b1", "btn-1")
bnames.set("b2", "btn-2")

for (let [bKey, bName] of bnames) {
console.log(bKey, bName)
}
// output:
// b1 btn-1
// b2 btn-2

// 同样的数组也是
var arr = [ 10, 20, 30 ];
for (let [idx,val] of arr.entries()) {
console.log(`[${ idx }]: ${ val }`);
}
// [0]: 10
// [1]: 20
// [2]: 30

以下ES6新增规范中,原生具备 Iterator 接口的数据结构如下:

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

作者还说了一个注意点,跌代器本身也是一个可迭代对象,当从一个已经存在的跌代器创建一个迭代实例的时候,迭代器本身被返回,(这一点有点糊涂,之后在仔细研读理解???)

Closure 闭包

我认为这可是js中的重点之一,也是不容易理解的特性之一,尤其在复杂的场景中。

作者也说了,在平时不知不觉中都在使用闭包,只是没有刻意的去注意。重要性不亚于循环、变量等。而且也是大多数编程语言最普通的功能特性之一。

作者对变量的定义:

Closure is when a function remembers and continues to access variables from outside its scope, even when the function is executed in a different scope.
一个函数记住并 继续 访问这些变量,在它的作用域外,甚至这个函数在不同的作用域被执行。

所以说,js 中 闭包 产生是依赖于函数的,对象不会有闭包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function greeting(msg) {
return function who(name) {
console.log(`${ msg }, ${ name }!`);
};
}

var hello = greeting("Hello");
var howdy = greeting("Howdy");

hello("Kyle");
// Hello, Kyle!

hello("Sarah");
// Hello, Sarah!

howdy("Grant");
// Howdy, Grant!

闭包产生的变量 msg 并未被回收,因为依然被 hellohowdy 引用使用。

闭包内的变量 msg 其实并不是一个快照之类的,而是变量本身直接的引用和保存(我理解就是存储内存地址),例如 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function counter(step = 1) {
var count = 0;
return function increaseCount(){
count = count + step;
return count;
};
}

var incBy1 = counter(1);
var incBy3 = counter(3);

incBy1(); // 1
incBy1(); // 2

incBy3(); // 3
incBy3(); // 6
incBy3(); // 9

每一个 increaseCount 的实例,共享各自的同一个count,

当然并不是所有的闭包产生于函数之间,只是js通常用函数来创建一个作用域。在ES6之后,let 声明的变量,只在块级作用域内容生效,块级作用域内的 函数也可以和当前的块级作用域形成闭包,例如:

1
2
3
4
5
for (let [idx,btn] of buttons.entries()) {
btn.addEventListener("click",function onClick(){
console.log(`Clicked on button (${ idx })!`);
});
}

重点: 闭包必须掌握,JS的重点之一。

this Keyword this 关键字

下一个迭代在讲(copy),待续….

作者

Fat Dong

发布于

2022-07-22

更新于

2022-07-22

许可协议

You-Dont-Know-JS-学习笔记-ch2(三)

How We Organize in JS 在JS中怎么组织代码?组织项目?组织一切?

组织代码,也就是对数据、行为进行组织,在 JS 中的两个主要模式是:class 和 modules(类与模块)。 两种模式互不冲突,可以同事使用,或者只使用其中一种。要想精通必须知道这两种模式以及适合他们的使用场景。

Classes 类

ES6 的新特性,涉及到一些计算机术语“面向对象”,“面向类”,“类” ,他们之间还是有很多细节差异的,这里不具体讨论。如果之前学过java, 类应该不陌生。

一般来说,一个“类”,是对自定义数据类型结构的定义,包括:数据、操作数据的行为,一个 “类”必须使用new来实例化。 这个可简单理解为:工厂 -> 商品的关系 ,

作者用 notebook(笔记本) 和 page 为例说明了一下:

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
class Page {
constructor(text) {
this.text = text;
}
// 查看页面内容
print() {
console.log(this.text);
}
}

class Notebook {
constructor() {
this.pages = [];
}

// 每个笔记本可以有很多页
addPage(text) {
var page = new Page(text);
this.pages.push(page);
}

print() {
for (let page of this.pages) {
page.print();
}
}
}

var mathNotes = new Notebook();
mathNotes.addPage("Arithmetic: + - * / ...");
mathNotes.addPage("Trigonometry: sin cos tan ...");

mathNotes.print();

其实就好比,工厂可以生成笔记本,就相当于 new 了一个笔记本 实例,每个笔记本可以添加 内容, 在笔记本上写东西,相当于 new 了一个 Page。查看的话,相当于页面 展示print了内容。

Class Inheritance 类的继承

一个类 可以继承其他类,这个类就可以拥有另一个类的方法、属性,也可以自己扩展自己的方法和属性。 作者也讲到了: 继承可以用在一个独立的逻辑单元(也就是一个类)组织数据和逻辑, 而且还允许 子类 可以父类结合,访问、使用父级的数据和行为(这不就是复用么?)

这也是传统面向对象(面向类)的设计中的特性,也可叫思想(继承,多态,封装)之一把,

代码就不贴了, 说白了也是继承目的之一就是为了复用,还有一些涉及到“接口”设计等相关知识。

Modules 模块

ES6 之前就有了模块化,但是语言层面并未支持,社区提供了一些解决办法,例如: AMD、CMD、CommonJS 等。在 ES6 出来之后,在语言标准层间就有了模块化了,这个更加方便了。

模块化模式目的其实和类 大致是一样的,也是将数据、行为组织到一个逻辑单元(一个模块中),其他模块可以 ”引入“ 和 ”访问“ 这些数据和行为

不管是ES6 还是之前,模块化的目的是一致,那就是拆分 + 组合,根据 数据、功能、业务等等,将不同的代码放到不同文件中, 使用的时候,根据需求 引入、组织到一起。

Classic Modules 经典模块化(古老的)

比较经典的模块化:外层是一个函数,运行之后返回一个实例对象,通过调用对象的方法,可以操作内部的数据。 有一个专业术语 ”模块工厂“ 。 比如像:jQuery。

ES Modules ES 模块化

ES6 的标准,就是为了达到经典模块化一样的目的和意义,特别是考虑到 AMD、UMD、CommonJS等规范。 不过在实现方式上有很大的不同。一个文件就是一个模块。

再也不用 包裹一层 function, 也不用使用一些 模块 API,例如:使用 export 去添加定义一些方法 。也不用去实例化。

ESMs 其实 就是一个”单例模式“, 在模块第一次导入时候,只会创建一个实例,其他的导入,只是 导入他的引用,指向的都是同一个。如果你的模块需要支持多个实例,你必须在你的ESM定义上提供一个经典的模块式工厂函数来实现这一目的。

然后是作者贴了一些代码距离来说明,这里我就不贴代码了,其实熟悉ES6的话,这个应该不是什么大问题。

The Rabbit Hole Deepens 兔子洞越来越深,这应该是一句俚语

Rabbit Hole 按照网上说的,通常指无底洞,没有尽头的感觉。我就理解可能是知识点越挖越多,越挖越深,感觉是没有尽头了。

这章的最后一段,作者这里讲了下,这个章节只是了解了 JS 最广泛(常见)的一些知识,即使是一些比较简要的只是,但是有一些隐藏、涵盖的大量细节,值得反复阅读多次,

下一章节,将会更加深入的挖掘 JS核心工作原理的一些重要方面。 在这之前请确保你能完全消化之前的内容。

额。扪心自问,我自己掌握的怎么样了?可能是70%左右吧,加油。

作者

Fat Dong

发布于

2022-07-20

更新于

2022-07-20

许可协议

You-Dont-Know-JS-学习笔记-ch2(二)

上一章节讲到函数,接着说

函数的两种声明方式:

  1. 普通函数声明语句
  2. 赋值表达式声明
    没啥可说了,作者特别强调了一下重要性:
    js 中 函数是 值 可被赋值和传递, 而且函数 也是一种特殊的对象类型, 并不是所有的语言认为函数是值(我不太了解其他语言的规范和语法,对于这一点不是很理解),而且对于一个语言是或否支持函数式编程比较关键,就像JS一样,JS 是支持函数式编程的,

Comparisons 对比,比较值是否相等

Equal…ish 相等?。。是?

js 有两种相等的判断, ”==“ 和 ”===“(严格相等)

作者说必须知道细微差别 equality comparisonequivalence comparison 区别,(英文比较差,通过机器翻译没搞懂这个词的区别)

不过下面作者给出了几个例子,”===“ 用 equality comparison 描述的,估计 equality 指的严格相等。

”===“ 的比较, 不仅会判断值,还是判断类型,而且不允许转换(相对于”==“),

1
2
3
4
5
6
7
8
9
10
11
3 === 3.0;              // true
"yes" === "yes"; // true
null === null; // true
false === false; // true

42 === "42"; // false
"hello" === "Hello"; // false
true === 1; // false
0 === null; // false
"" === null; // false
null === undefined; // false

不过也有特殊情况:

1
2
NaN === NaN;            // false
0 === -0; // true

js 中 NaN 是一个特殊的值,想要判断是不是 NaN 可以用 Number.isNaN 来判断。 而 0-0 在编程语言中是有区别的(底层二进制存储)。

对于这种情况,可以用 Object.is 来判断,可以看是一个 “====” 四个相等(加强的 严格相等)。

如果是用来判断 对象、函数、数组 呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[ 1, 2, 3 ] === [ 1, 2, 3 ];    // false
{ a: 42 } === { a: 42 } // false
(x => x * 2) === (x => x * 2) // false


// 对比
var x = [ 1, 2, 3 ];

// assignment is by reference-copy, so
// y references the *same* array as x,
// not another copy of it.
var y = x;

y === x; // true
y === [ 1, 2, 3 ]; // false
x === [ 1, 2, 3 ]; // false

这个时候, 对应对象类型(引用类型),内容不重要,重要的时候引用的身份(原始值)是否是同一个。 js 并有内置 判断对象的值 相同的方法,如果要比较需要自己实现, 这部分其实比较复杂,即使用 用源码的的字符串文本去比较,还要考虑闭合问题, 各种边界条件太多了。难以做到。

Coercive Comparisons 强迫/强制性比较,也就是非严格比较

非严格比较 使用 “==”, 这种比较默认会进行类型转换,会将其中一个值转成同样的类型,再进行比较。

具体的话参考一下MDN,里面有一个表格,说明了会进行那种类型转换,地址:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Equality_comparisons_and_sameness

这一点我个人觉得是最恶心的一点,这种默认的强制类型转换让一些从其他语言的开发者很难受。

不过有一些场景却能带来便利,例如:

1
2
3
4
5
6
7
8
9
10
let arr = []
if(arr.length){
// ....
}

if(arr.length > 0){
// ....
}

// 0 隐式转换成 false

未完待续。。。。

作者

Fat Dong

发布于

2022-07-19

更新于

2022-07-20

许可协议

You-Dont-Know-JS-学习笔记-ch2(一)

Ch2 第二章

Surveying JS

标题直译为 调查/勘测 js, 我理解为仔细研究js。

作者留了一个提示: 如果你还在熟悉 js 的阶段,建议大家留下足够的时间去学习这一章。说白了,就是你对 js 还不是很深入的了解,就多学学这一章的内容。

Each File is a Program

每一个文件就是一个程序/应用, 这个和我之前的理解有点不一样,我一直认为这个页面上所有的js,不管内联还是外联的,共同组成了一个程序。

作者说:在 JS中,每一个单独的就是一个独立的程序,而且也解释了为什么:

这样认为的原因主要是围绕错误处理, 一个文件可能会出错或者执行、解析、编译失败,但是不会组织下一个文件的处理(可以理解为同样执行、解析等等操作),很明显,如果你的应用有五个js 文件,如果其中一个处理问题,整个应用可能只有部分功能可以使用,

Values

值, 这里是对一些数据类型,简单介绍说明了一下。

是程序中最基本的单位,也是是数据,是程序用来维护状态的方式,两种类型:原始值(primitive)、对象(Object)类型。

  • 原始:string,number,bigint,boolean,null,undefined,Symbol
  • 引用:Object(用于复杂的数据结构)

这一段,以数据类型为基础,延展了字符串的使用,包括语法啥的,使用单双引号的,以及反向引号 ```的使用, ES6的新语法:字符串模板,包括内置的常量 Math.PI ,

null 和 undefined 的区别,虽然好多人在处理这两个值的用同样的方式,但是还是建议明确的去判断, 尽管null拼写起来更短一点。 例如:

1
2
3
while(value != undefined){
// ......
}

还有就是 Symbol 类型,这个也是ES6的 新类型,表示唯一的值,平时不常遇到,但在一些基础的类库、框架中使用的较多

Arrays And Objects (数组 和 对象)

这就是数组和对象的一些介绍, 还备注了一下 函数(Functions)会在之后介绍。

Value Type Determination 数据类型判断

一种方式: typeof, 这种方式比较局限吧, 对于null, array 不能正确的返回类型

而且作者也提醒了一下: typeof null 返回 objecttypeof function 返回 function

还有就是类型转换问题, 已经原生类型、引用类型在 赋值、传递的不同, 会在后续一个详细的章节说明

Declaring and Using Variables 声明和使用变量

值 可以是 字面量(literal values), 其实就是直接使用字符串、数字、true、false而不用声明变量, 或者是放在变量里, 这个变量其实就是 的容器。

还有就是 var、let、const 的一些区别,以及在块作用域(block scoping)、函数作用域(function scoping)的一些不用的表现。

对于 var 还特意说明了一下,虽然有 letconst 语法, 但是 var 还是有用了,例如 var 声明的变量将表示他的作用域更加广泛(整个函数内部),

作者认为:建议避免使用 var 有点小过了,这种限制性的建议是没啥用(益处)。 虽然对于var 的变量作用域有时候确实比较困惑。这样是在假设你无法正确的学习使用一个与其他功能相结合的功能,我相信你可以也应用正确的学习任何功能,然后正确的去使用他们。(这一句是机器翻译过来的,可能念起来不太顺畅),我理解作者想表达的意思是:var 也是js 的一个功能之以,即使有历史的一些问题和原因吧,他也是js的构成之一,你应该去了解学习他,而且一些老项目依然在使用es5的语法。只要弄明白了,这些都不是问题。

除了 varletconst 之外,还有其他方式,比如:函数

1
2
3
4
function hello(name){
console.log(`my name is ${name}`)
}
//hello("Kyle");

这里是对函数的声明,函数名 hello 也是一个变量, 包括参数name ,而且这种方式 通常 就像使用 var 声明一样。

而且还用 try catch 对比了一下:

1
2
3
4
5
6
try {
someError();
}
catch (err) {
console.log(err);
}

err 就是一个块作用域的变量,只能在catch里面使用,效果相当于 let

Functions 函数

函数这个词 有很多种定义,在 函数式编程中,“函数”有一个精确的数学定义,以及一套严格的规则需要遵守,

这个作者提到了一个词 函数式编程,不了解的同学可以去了解一下,

在 JS 中,我们可以认为函数通常是 另一个更加广泛的术语 :“程序”, 这个程序是可以被执行一次甚至多次的声明的语句的集合。也许需要有户收入,也可返回一个或者多个输出结果。

关于函数的声明有两种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 方式一
function awesomeFunction(coolThings) {
// ..
return amazingStuff;
}

// 方式二
// let awesomeFunction = ..
// const awesomeFunction = ..
var awesomeFunction = function(coolThings) {
// ..
return amazingStuff;
};

前置知识:JavaScript 中表达式和语句的主要区别在于一条语句执行一个动作,一个表达式产生一个值。意思是一个表达式执行后一定会生成一个值,而语句不一定会产生值。语句主要是用来执行动作,程序就是由一系列语句组成。

方式一称为函数声明,应为它本身是被认为是一个语句, 而不是表达式

作者

Fat Dong

发布于

2022-07-18

更新于

2022-07-20

许可协议