js 基础&进阶 速查笔记
笔记
个人笔记!不是文档!大家 尽量减少个人文档行为,多做公共wiki和文档翻译!
感谢
有精力的朋友还是建议读上面链接,如果它们的文档有帮助,也建议购买它们的 epub/pdf 作为支持
本文是基于我C++的熟练,和一定时间js使用后的阅读知识补充整理,章节分化基本和上面链接的内容对应
JavaScript 基础知识 2
函数 2.15
函数表达式 vs 函数声明 2.16
function a(){}
vs a = function(){}
当 JavaScript 准备运行脚本或代码块时,它首先在其中查找函数声明并创建函数。我们可以将其视为“初始化阶段”。
在处理完所有函数声明后,执行继续。
所以以下可以而正常运行
1 | sayHi("John"); // Hello, John |
声明还是有代码块的问题
分号
1 | alert("There will be an error after this message") |
上面会报错,因为编译器不确定加分号,导致
alert()[1,2].forEach()
, 其中1,2视作 逗号表达式返回2,也就是取alert()
返回数组的第二个元素
代码质量 3
https://zh.javascript.info/ninja-code 这一章吐槽真的有趣23333
测试 3.5
同时适用于浏览器端和服务器端!
Mocha —— 核心框架:提供了包括 describe 和 it 的通用型测试函数和运行测试的主函数。
Chai —— 提供很多断言支持的库。它可以用很多不同的断言。现在我们只需要用 assert.equal。Chai doc
Sinon —— 用于监视函数、模拟内置函数和其他函数的库,我们稍后会用到它。
补充,现在还有可选的jasmine和karma
例子
1 | describe("pow", function() { |
一个测试检测一个东西。
https://zh.javascript.info/testing-mocha 上面的demo 有点长,但很好的,建议直接看网页
before/after and beforeEach/afterEach
测试前后包围
规范先行,实现在后。一个有良好测试的代码通常都有更好的架构。
测试规范,实际上 不应该在测试中 使用”运算“,否则可能需要 调试测试
经过 具体化以后的测试,还可以使用it()->it.only()
进行单个测试
Polyfill 3.6
ES6 支持
https://kangax.github.io/compat-table/es6/
Babel
它将现代的 JavaScript 代码转化为上一代的标准形式。
脚本有一个术语 “polyfill” 表示用来“填补”缺口并添加缺少的实现。
两个有意思的 polyfills 是:
- babel polyfill 支持很多,但是很大。
- polyfill.io 服务允许我们能根据自己所需的特性来按需加载、构建 polyfill。
除了针对旧的版本,反过来,还有https://www.google.com/chrome/canary/ 的为开发人员准备的未来版本,每日构建版本,不支持Linux?????? 操了
https://groups.google.com/a/chromium.org/forum/#!msg/chromium-discuss/GNE4pEXLMkQ/cis32ewLosYJ
Objects(对象):基础知识 4
对象 4.1
计算属性
1 | let bag = { |
特殊属性
__proto__
历史原因,不能设置为 非对象 值
属性值简写(不建议)
1 | function makeUser(name, age) { |
for…in
和标题有点对不上,主要是 string和number转换相关
字符串整数属性会相互转换,用加号控制
1 | let codes = { |
引用复制(值和引用) 4.2
原始类型是:字符串,数字,布尔类型 – 是被整个赋值的。
其它是引用
Object.assign 只是深度为1的深拷贝,要完全深拷贝,还是要自己实现,我喜欢在这里出面试题,深拷贝深比较
垃圾回收 4.3
let
变量没有let就是全局
Symbol 类型 4.7
对象的属性键只能是 String 类型或者 Symbol 类型。不是 Number,也不是 Boolean,只有 String 或 Symbol 这两种类型。
Symbol 保证是唯一的。即使我们创建了许多具有相同描述的 Symbol,它们的值也是不同。描述只是一个不影响任何东西的标签。
Symbol 不能隐式的转换为String,要转化的话只能用 toString()
Symbol 在 for…in 中被跳过
Symbol 在 Object.assign 中不会被忽略
全局注册表用法
let id = Symbol.for(“id”)
返回key
Symbol.keyFor(Symbol.for(“test”))
从技术上说,Symbol 不是 100% 隐藏的。有一个内置方法 Object.getOwnPropertySymbols(obj) 允许我们获取所有的 Symbol。还有一个名为 Reflect.ownKeys(obj) 返回所有键,包括 Symbol。所以它们不是真正的隐藏。但是大多数库、内置方法和语法结构都遵循一个共同的协议。而明确调用上述方法的人可能很清楚他在做什么。
对象方法与this 4.4
this运作方式
其实真的要讲就一句话,this在运行时才被复为具体的值,所以看运行到解释器去解释this那一行时的运行主体是谁即可。(这里忽略了一些编译器优化 可能发生的直观和实际造成的差异)
从根本上,我们是要去避免它带来的问题,而不是掌握它的各种用法。
怎样选择合适的实体?如何组织它们之间的交互?这就是架构
1 | let user = { name: "John" }; |
this
是在运行时求值的。它可以是任何值。在运行时对 this 求值的这个想法有其优缺点。一方面,函数可以被重用于不同的对象。另一方面,更大的灵活性给错误留下了余地。这里我们的立场并不是要评判编程语言的这个想法的好坏,而是要了解怎样使用它,如何趋利避害。
要深入理解 obj.method() 调用的原理。
- 首先,点 ‘.’ 取得这个 obj.method 属性。
- 其后的括号 () 调用它。
错误示例
1 | let user = { |
实际为
1 | let XXX = (user.name == "John" ? user.hi : user.bye); |
引用类型 (base, name, strict)
三元组 ,其中strict
为bool,与是否use strict
相关
上面user.hi()
(user['hi']
同理)在严格模式下,对应(user,'hi',true)
而上面的XXX = user.hi
过程中会丢掉user,只取函数,所以 一个函数
,要么是一个单纯的函数,要没么是一个上面的三元组
1 | user.hi() // 直接的调用 |
1 | function makeUser() { |
箭头函数没有自己的this
箭头函数使用外部正常的
函数的this
1 | let user = { |
对象原始值转换 4.8
Symbol.toPrimitive String Number
相对于c++ 的重载operator,
Symbol.toPrimitive
当一个对象被用在需要原始值的上下文中时,例如,在 alert 或数学运算中,它会使用 ToPrimitive 算法转换为原始值
1 | (x = [2,3,3])[Symbol.toPrimitive] = function(hint){console.log(`HINT[${hint}]`);} |
从例子,首先能看到,是会调用到 该函数,第二能看到hint传递过来的是string
string
当一个操作期望一个字符串时,对于对象到字符串的转换
alert(obj)
或anotherObj[obj] = XXX;
toString -> valueOf
number
当一个操作需要一个数字时,用于对象到数字的转换,如 maths
1 | // 显式转换 |
valueOf -> toString
default 未知状况
例如 字符串+数字,是采用链接字符串,或数值相加,,这里说了一堆,大概意思是,default 可以视为和number 状况一致
string+number 例子
1 | let user = { |
运作方式总结
为了进行转换,JavaScript 尝试查找并调用三个对象方法:
调用 objSymbol.toPrimitive 如果这个方法存在的话,
否则如果暗示是 “string”
尝试 obj.toString() 和 obj.valueOf(),无论哪个存在。
否则,如果暗示 “number” 或者 “default”
尝试 obj.valueOf() 和 obj.toString(),无论哪个存在。
以上三个方法,不规定返回对应值,但一定要返回原始值[强制的],[否则 有潜在递归可能性, 比如A.toString()返回类型A]
一点区别
toString/valueOf
如果返回的不是原始值,则会被忽略该方法
Symbol.toPrimitive
如果返回的不是原始值,则会报错被忽略该方法
构造函数和操作符new
约定首字母大写…我们谈语法时别谈约定,可以说建议,如果代码一定要就eslint什么的搞一搞
1 | function user(name) { |
当一个函数作为new user(...)
(没有参数时,可以省略括号)执行时,它执行以下步骤:
- 一个新的空对象被创建并分配给 this。
- 函数体执行。通常它会修改 this,为其添加新的属性。
- 返回 this 的值!!!
单次使用x = new function(){ ... }
1 | function User(name) { |
但是不建议这样做,这样会对代码的阅读造成困扰
Optional chaining ‘?.’ 4.6
觊觎这个很久了,感觉还是历史遗留问题的 半 优化,没有从根本上解决问题。
虽然感觉根本办法还是应该在 和 其它端交互时进行 强制的状态和空值处理,TS基本考虑100%上,然后可以考虑用protobuf替代json。
数据类型 5
吐槽:看到IE 浏览器不支持这个方法
就想笑
基本类型
- Boolean
- Null
- Undefined
- Number
- String
- Symbol (new in ECMAScript 6)
基本类型在作为对象使用时,会临时创造一个包装对象,使用完后即销毁
1 | a= 3 |
哎 js还不支持c++14支持的a=1'000'000'000
这种写法
toString(base)
可以进行进制转换 base
取值2->36
isNaN / isFinite
之前看vuex源码时 ,看到很喜欢用new 来建立空的对象、数组什么的,难道是英文友好性,比如我一般写a=[]
而他们可能就会写a=new Array()
数组=队列 支持 shift/unshift (首部添加 慢) 和 pop/push (尾部添加 较快)
数组引用的方式复制
遍历 for i =0 -> arr.length
, for key in arr
(会迭代所有属性,这里说这还会慢..10-100倍 所以遍历数组通常不用forin), for value of arr
性能测试:
1 | let arr = new Array(); |
修改数组时,length 属性会自动更新。准确来说,它实际上不是数组里元素的个数,而是最大的数字索引值加一。(慎用)
length 属性的另一个有意思的点是它是可写的。把它变小,数组真的可以变短,所以可以用arr.length=0来清空..(为什么这里说这是清空数组的最好的方法?????)…….. 危险的语言
警告 new Array(xxxx), 当xxxx是字符obj等东西的时候 实际创建的是 包含对应元素的数组,而是数字时,表示的是 对应长度的空数组
delete arr[1]; // 如果arr的长度为5(大于2),会删除index==1的元素,但是 不会把后面的向前移动,也不会影响length
arr.splice(index[,deleteCount,elem1,…,])
-1
的index 表示从尾部计算
还有支持简单参数的slice,
….说这么多 ,简单的说 数组别乱玩,
一些concat函数等就不说了,要用啥查啥
1 | //map |
判断一个变量是否是数组,不要用typeof,用Array.isArray()
1 | arr.find(func, thisArg); |
表示比较函数用的 2号参数,也就是 比较过程中采用func(数组中的元素,thisArg);
可迭代对象(Iterables)
一个含有 [Symbol.iterator] key的对象,实例
1 | a = {x:1,y=2}; |
- 需要
Symbol.iterator
- 该函数返回值是个object (迭代器),该object需要带有next()方法
- next方法回传一个迭代器
{done:Boolean,value:any}
的形式,这样就可以支持,for...of
学过c++的就可以类比看看vector,string和它们的迭代器
Array.from(XXX) 方法从一个类似数组或可迭代对象中创建一个新的数组实例。
Map 可以用 对象作为key(区别于普通Object)………嗯嗯 好了好了 我知道了,说得c++好像不能用指针作为key一样
map的key比较是采用===
, 然后map的set可以链式操作,因为返回本体
键值数组->new Map()->变为map
map/obj-> Object.entries() -> 变为键值数组
for..of会默认使用entries
js有Set, 迭代for..of 和 set.forEach
WeakMap/WeakSet 忽略内存回收的依赖检测
这里说的适用场景,是有主对象控制区,在辅助非必要信息区域可以采用WeakMap来存
比如
1 | obj = {}; |
map.keys()
返回的是迭代器不是 数组,用Array.from(map.keys())可以转化成数组
对于obj,Object.keys(obj)来返回数组
,而不是迭代器
Object.keys/values/entries (), 忽略Symbol类型的属性
数组/可迭代对象解构
let [a,b,c..] = arr/可迭代对象; // 自动分配到a,b,c
let [,,a,b,c..] = arr/可迭代对象; // 忽略前两个值
let [a,b,...rest] = arr/可迭代对象; // rest接受剩余元素
对象解构
let {key1,key2} = {key1:...,key2:...}
以key配对,不是顺序
let {key1:var1,key2:var2} = {key1:...,key2:...}
目标变量的写法
let {key1= 100,key2:var2} = {key1:...,key2:...}
缺省值写法
严格模式下,先let后解构,外部加括号包起来
1 | let title, width, height; |
还有嵌套解构……..我觉得还行,感觉这也算得上半结构化了
然后函数传参可以用默认值+ 等于号的python里常看到的方法,假设有定义f(x=1,y=3,z=4){}
, 调用f(z=233)
函数还可以写成 f({a=1,b=7,c=3}){}
, 然后调用x={b=2};f(x)
这一块来说就很是灵活好用易读的感觉了
date.get你希望改变的时间维度()+数值
Date.now()相当于 new Date().getTime(), 只会创建时间戳,性能友好 更快!
浏览器方法performance.now() 微秒数
JSON 相关,内部不应该有循环引用
JSON.stringify(value [,replacer(替换函数设置),space(空格缩进换行符设置) ])
JSON.stringify(value ,[指定属性1,指定属性2,...])
replacer : function(key,value) {return 替换值;}
toJson 函数可以重写,用于被解析
目前非标准的https://json5.org/ 支持注释,无引号的键 !!
JSON.parse(data,function(key,value){return 定制化value;}),可以进行定制化处理
Advanced working with functions 6
任意长度参数 Rest 参数与 Spread 语法 6.2
function f(...args){}
// 注意… 必须放在列表尾
或者通过内置的arguments访问(旧式的,这里没说不建议用)
箭头函数是没有 “arguments”,箭头函数内访问arguments等同于访问其外部非箭头函数的arguments
三点展开a=[2,3,3]; f(...a);
// 展开a并调用f,可以放在句中, 展开和for .. of 采用相同展开对象
闭包
变量环境
1 | // 3 |
JavaScript中的所有函数都是闭包
if for等的花括号内的 都有此法环境
在旧的脚本中,我们可以找到一个所谓的『立即调用函数表达式』(简称为 IIFE)用于此目的。
(function(){ /*.CODE HERE.*/; })()
除此以外还有方式
(function(){ /*.CODE HERE.*/; }())
!function(){ /*.CODE HERE.*/; }()
+function(){ /*.CODE HERE.*/; }()
垃圾回收 与 闭包
1 | function f() { |
通过上面的实验可以看到,
在有第一个console的时候,我们可以在debugger的位置访问到value,没有的这个console则则不行,也就是这里可以看到js预测下的优化,省略掉一些认为不存在的值,
因为我们的h在g函数之后,还能够访问value,但是g的位置在注释掉第一个console则访问不到,说明这个地方并不是value被回收了,而是,value没有被传入
用 var 声明的变量(不建议 不建议 不建议),不是函数范围就是全局的,它们在块内是可见的。也就是花括号 不会限制var, var 在函数开始时执行,不论声明位置,甚至是这样[所有函数内的var的声明 都会被提升到顶部,但是赋值的顺序不会变]
1 | function sayHi() { |
真的被使用了吗?如果你有疑问,可以去掉if以及var,你会发现在全局可以访问phrase,而加上if和var后,全局是访问不到phrase的,也就是说创建的是函数内的phrase,也就是var真的被执行了!!
在全局下使用let以后,这个变量也不是全局,只是”根”下可访问
全局的使用window.XMLHttpRequest
检测类型,iframe 的正确窗口获取变量
函数
函数的.name
能得到函数名a =function(){}; b={c(){}}; a.name;b.c.name;
而很多函数也没有函数名,d={e:function(){}};d.e.name; l=b.c;
// 最后这种赋值函数指针会带上函数名一起复制
函数的.length
表示接受的参数个数,其中...args
在计数中被忽略
函数可以加上属性XD有时用来代替闭包
如下的func秀啊
- 它允许函数在内部引用自己。
- 它在函数外是不可见的。
1 | let sayHi = function func(who) { |
嗯。。。。感觉这种就是个函数指针类似的玩法咯
可以还奶了一口jQuery和lodash
可以这样创建 函数,一下所有字段都是字符串 XD
let func = new Function ([arg1[, arg2[, ...argN]],] functionBody)
比如使用场景是,从服务器获取动态函数
但是new Function不论在哪里创建, 所指向的词法环境是全局,从一定程度上强迫写出好代码
1 | new Function('a', 'b', 'return a + b'); // 基础语法 |
setTimeout 和 setInterval
不在标准方法中不在规范中
取消调度
1 | let timerId = setTimeout(...); |
弹窗会让 Chrome/Opera/Safari 内的时钟停止!!!!!!???? 我的chromium 执行setInterval 如果不点,等一会,会连续冒出alert啊,难道chrome也改了??
setTimeout(…,0) 尽快,但是在当前代码执行完后紧接着执行
这里有一种slot+setTimeout0分割任务CPU占用方法
用 setTimeout 进行分割和没用这两种做法在速度方面平分秋色,总的计数过程
所花的时间几乎没什么差别。我一个总耗时2s,一个总耗时8s,但是我把它们都 用nodejs去跑的确时间差不多,也就是8s的应该在过程中处理了其它事件耗时了
HTML5 标准 所言:“经过 5 重嵌套之后,定时器运行间隔强制要求至少达到 4 毫秒”。服务端 JavaScript 就没这个限制了
1 | let start = Date.now(); |
所有的调度方法都不能保证延时的准确性,所以在调度代码中,万不可依赖它。
优化 & bug fix
缓存包装函数,耗时久,但是输入输出稳定的,大量相同输入访问的函数的通用包装函数,见原文实现
func.call(context,...args)
解决包装后上下文丢失没有this
func.apply(context,args)
解决 多参数为一个数组
func(1,2,3) ≈ func.call(context,1,2,3) ≈ func.apply(context,[1,2,3])
// 这里是外部再接受一个自定义hash函数来对多参数计算一个key,arguments本身不是数组,不过可以[].join.call(arguments)
来拼接
下面几个装饰器 任务 挺有用
对于延迟的this丢失 处理
let newFunc = func.bind(thecontextyouwant)
通过bind 指定this,只能bind一次,多次bind,第2次及以后的会被忽略
lodash 中的 _.bindAll(obj)。
批量绑定
另一种方案就是箭头函数了
偏函数,函数的封装=.= lodash 大法好啊 有对应的_.partial
柯里化 把调用形式f(a,b,c)
变为f(a)(b)(c)
形式 lodash _.curry
用lodash的封装,柯里化之后 原来调用方法 依然正常运行:也可以柯里化格式调用
高级柯里化https://github.com/lodash/lodash/blob/4.1.1-npm-packages/lodash.curry/index.js
对象、类和继承 7&8
对象属性除value
外还有三个特殊属性(所谓的“标志”):
- writable — 如果为 true,则可以修改,否则它是只读的。
- enumerable — 如果是 true,则可在循环中列出,否则不列出。for … in
- configurable — 如果是 true,则此属性可以被删除,相应的特性也可以被修改,否则不可以。使属性不可配置是一条单行道。我们不能把它改回去.非严格模式 写入被忽略,严格模式报错
Object.getOwnPropertyDescriptor(obj, propertyName);
方法允许查询有关属性的完整信息。
Object.defineProperty(obj, propertyName, descriptor)
可以修改标识
上面的两个方法均有带s的 多元素方法
如果用for..in复制对象,会忽略symbolic属性,这样可以 获取所有属性(“更好”的克隆)
还有一些属性(实践中不常用的) 见原文了
set v(){return val;}
&& get v(val){return this.xxx=val;}
可以只有其中一个, 意味着只读或只写
属性可以是“数据属性”或“访问器属性”,但不能同时属于两者。
访问器没有value和writable属性,有的4个是
- get —— 一个没有参数的函数,在读取属性时工作,
- set —— 带有一个参数的函数,当属性被设置时调用,
- enumerable —— 与数据属性相同,
- configurable —— 与数据属性相同。
众所周知的协议,即以下划线"_"
开头的属性是内部的,不应该从对象外部访问。eslint里没有 ,tslint有一种member-access
的限制办法
1 | Object.defineProperty(this, "age", { |
对象有一个特殊的隐藏属性 Prototype
值是null或是另一个引用对象
1 | let animal = { |
不能通过子的修改原型的数据属性,只会对子实体增加键,
而原型上的getters/setters,会运行,但是不论能找到的值在哪,只要解析this,始终是最初的子实体,也就是this不受到原型影响
意味着A
有写方法W
,会写this.X
,B
继承于A
,那么B
执行W
会调用A
的W
,但是写的this.x
会写到B
上
按照c++的一些理解的话,是 他们之间有继承关系,所以执行函数会去找函数表,当子类没有时,就去父的表里找,找到执行时,传入的实体还是 子类
即使是这样,下面也列出了一个 错误/危险
的实现:
1 | let hamster = { |
函数原型
设置 Rabbit.prototype = animal 的这段代码表达的意思是:“当 new Rabbit 创建时,把它的 [[Prototype]] 赋值为 animal” 。
1 |
|
1 | function Rabbit() {} |
所以这个prototype,只是用于当一个 实例找不到对象key的时候,会去访问找个对象的原型(.prototype
)的对应对象key,相当于用于记录的父类的一个属性,
__proto__
(实例上原型上都有,指向它的原型的.prototype
)和.prototype
(一般在原型上)的关系
https://stackoverflow.com/questions/9959727/proto-vs-prototype-in-javascript
1 | ( new Foo ).__proto__ === Foo.prototype; |
- Object.create(proto[, descriptors]) —— 利用 proto 作为 [[Prototype]] 和可选的属性描述来创建一个空对象。
- Object.getPrototypeOf(obj) —— 返回 obj 对象的 [[Prototype]]。
- Object.setPrototypeOf(obj, proto) —— 将 obj 对象的 [[Prototype]] 设置为 proto。
潜在问题,如果我们的Object存的是用户提供的键值对,那么__proto__
将会被忽略或其它错误,办法是使用Map,或者极简单的对象let obj = Object.create(null)
,它的prototype是null,缺点则是没有默认的toString方法
属性描述器定义出来的是不可枚举的
ES6的class语法
1 | function User(name) { |
super.
访问父类中的方法或些元素
1 | class Animal { |
不同点就在于:
当一个普通构造函数执行时,它会创建一个空对象作为 this 并继续执行。
但是当派生的构造函数执行时,它并不会做这件事。它期望父类的构造函数来完成这项工作。
A 是基于 B, B 基于 C,
在调用时候this不变,不会在原型链上找所有,只会调用原型链上的第一个,所以如果写法都是this,可能导致无限递归
[[HomeObject]] 特殊外部属性
这个绑定是永久的,内置的,会绑定到父类。因此这是一个非常重要的语言变化。
只有调用super.XXX
时才会被使用,所以不会破坏兼容性,解决上面this不会变导致无限循环的bug
在这种情况下,类中的 func(){}
写法和func:function(){}
写法就不同了,后面一种冒号的写法不会绑定[[HomeObject]]
替换构造函数,
1 | // 内置函数会使用它作为构造函数 |
最后的例子很有价值看一看,class X{}
vs class X extends Object{}
- 用了extends的派生类 必须调用super
- 用了extends相当于设置了
Rabbit.prototype.__proto__ = Object.prototype
- 用了extends相当于设置了
Rabbit.__proto__ = Object)
;
如果没有extends
的该处不会被设置,即无法访问静态方法Rabibit.getOwnPropertyNames()
报错,其.__proto__ = Function.prototype
xxx instanceof 某类
检查一个变量实体是不是 属于一个类,改语法会在原型链上检测
该方法可以指定,设置 static [Symbol.hasInstance](obj){}
自定义检测方法
如果没有[Symbol.hasInstance]
方法,那么实际的比较过程就是
1 | obj.__proto__ === Class.prototype |
实际的调用可以看做是类.prototype.isPrototypeOf(obj)
,可以看做这个调用,所以如果prototype
被修改,即使__proto__
没被改,rabbit instanceof Rabbit
会返回false
也可以用toString
来做类型识别,let arr=[];alert(arr.toString())
,或者
objectToString = Object.prototype.toString;alert(objectToString.call(arr))
对于自动以class,可以写[Symbol.toStringTag]
字段来改变toString()
的输出
这样的话 就比typeof更强大(支持自定义),甚至可以带上更多自定义信息,替代instanceof
实例a | 类名A | Function | Object | |
---|---|---|---|---|
__proto__ |
等于它来源类的prototype ,如果是Object.create(实例b)来的,则指向实例b |
Object.__proto__ , 如果是 class extends B创建的 则等于B |
见下 | 见下 |
prototype |
无 | 它自己的prototype,相反的方向Foo.prototype.constructor === Foo | 见下 | Object的prototype |
prototype.__proto__ |
无 | Object.prototype | Object.prototype | null |
Function.__proto__ === Function.prototype === Object.__proto__ !== Object.prototype
用于 | 返回 | |
---|---|---|
typeof | 基本数据类型 | string |
{}.toString | 基本数据类型、内置对象以及包含 Symbol.toStringTag 属性的对象 | string |
instanceof | 任意对象 | true/false |
Mixin 模式 9.7
单继承模式有时会对人进行限制,所以有了mixin,能同时实现支持多个类的方法实现
根据维基百科的定义,mixin 是一个包含许多供其它类使用的方法的类,而且这个类不必是其它类的父类。
换句话说,一个 mixin 提供了许多实现具体行为的方法,但是我们不单独使用它,我们用它来将这些行为添加到其它类中。
1 | // mixin |
User.mixinfunction()
->首先通过User的prototype找到User.prototype
,在调用mixinfunction
时,因为mixin的类有自己的[[HomeObject]]
所以如果有super,则会沿着mixin
去找,而不是user
事件订阅mixin
1 | let eventMixin = { |
异常try…catch…finally 10.1
catch到的
name -> 异常名称
message -> 详情描述
stack -> 用于调试的
建议的:throw
1 | let error = new Error(message); |
catch 应该只捕获已知的异常,而重新抛出其他的异常。
try -> catch -> finally。
或者try -> finally
缺少catch时, try-finally->throw err
自定义错误
1 | class ValidationError extends Error { |
当代码越来越大,对于每个层次应该有专门的处理和转换
Error 10.2
其实是warning,但是写warning 可能没人看
现代的 JavaScript 引擎会做很多优化。相对于「正常情况」,它们可能会改变「人为测试」的结果,特别是我们度量的目标很细微。因此,如果你想好好了解一下性能,请学习 JavaScript 引擎的工作原理。在那之后,你可能再也不需要微度量了。
关于 V8 引擎的大量文章,点击:http://mrale.ph.
Generator and Promise
我分别有写笔记
Module 简介
AMD — 最古老的模块系统之一,最初由 require.js 库实现。
CommonJS — 为 Node.js 服务器创建的模块系统。
UMD — 另外一个模块系统,建议作为通用的模块系统,它与 AMD 和 CommonJS 都兼容
<script type="module">
和旧浏览器的nomodule
模块代码仅在第一次导入时被解析, 生成导出,然后它被分享给所有对其的导入,所以如果某个地方修改了 admin 对象,其他的模块也能看到这个修改。
Proxy
感谢
有精力的朋友还是建议读上面链接,如果它们的文档有帮助,也建议购买它们的 epub/pdf 作为支持
本文是基于我C++的熟练,和一定时间js使用后的阅读知识补充整理,章节分化基本和上面链接的内容对应
该篇是对应上述链接的第二部分
Document 1
运行时 鸟瞰图
文档对象模型 DOM : document https://dom.spec.whatwg.org/
开发人员必须为每个浏览器编写不同的代码。那是昏暗、混乱的时代。
现在尽管有两个组在做document,但它们99%是相似的
当我们修改文档的样式规则时,CSSOM 与 DOM 一起使用。但实际上,很少需要 CSSOM,因为通常 CSS 规则是静态的。 https://www.w3.org/TR/cssom-1/
BOM(HTML规范的一部分):例如包括 https://html.spec.whatwg.org
navigator,提供有关浏览器和操作系统的背景信息。
location允许读取和重定向URL
Mozilla 手册 https://developer.mozilla.org/en-US/search
DOM:
#text
永远是 DOM中的叶子节点
https://zh.javascript.info/dom-nodes
看这个举例,你会发现,比你预期的的#text会多更多,不过好在是这些都是#text,都是叶子节点,在过滤时只要规则正确,不会被过滤到
下面举例说如果你把各个<html>
,<body>
等之间的空格回车都去掉,那它们生成的dom树就不再会有多余的#text
开发者工具中的 DOM 结构已经过简化。文本节点仅以文本形式显。根本没有“空白”(只有空格)的文本节点
历史原因:
- 由于历史原因,
<head>
之前的空格和换行符被忽略, - 如果我们在
</body>
之后放置了一些东西,那么它会自动移动到body
内部,因为HTML
规范要求所有内容必须位于<body>
内。所以</body>
后面可能没有空格。
自动修正【看看,更多的还是应该保证 正确的编写
表格是一个有趣的“特例”。按照 DOM
规范,它们必须具有 <tbody>
,但 HTML
文本可能(官方的)忽略它。然后浏览器自动在 DOM
中创建 <tbody>
。
有12个节点类型,常用的就是你F12能看到的document
,元素节点,文本节点,和注释
http://software.hixie.ch/utilities/js/live-dom-viewer/
以前也没系统的看过chrome的控制台方法 官方文档 TODO: https://developers.google.com/web/tools/chrome-devtools/
现在最后选中的元素可以用 $0 来进行操作,以前选择的是 $1,如此等等。
我们可以在它们之上运行命令。例如,$0.style.background = ‘red’ 使选定的列表项变成红色,
inspect(node)来从命令行,反过去找document中的元素
DOM 结构
document.body
,document.head
在DOM中用===null
判断节点不存在
childNodes
一个类似数组的个迭代集合只读,引用(实时的), ===.firstChild
, ===.lastChild
,.hasChildNodes()
.previousSibling
,.nextSibling
如果希望忽略文本和注释节点,只访问元素节点
table支持更多的东西[感觉这部分主要是了解,因为 实际编写中,现在主流框架,基本很少需要进行原生元素的操作了[github说没用任何框架除外
.getElementsBy*
:Id
,TagName
,Name
,ClassName
.querySelectorAll()
,querySelector
,.matches
,.closest
(最近祖先匹配) 都是css相关的调用 方法
注意! getElementsBy*
返回的是 一个实时会变动的集合,而querySelectorAll
返回的是一个static
的
场景:
获取元素
改变元素
操作元素 [如果用的
querySelectorAll
得到的,那么可能和当前真是的元素不一致]
原文上给了几个操作的一个总结表[感觉因为现在基本都是框架就不整理进来了]
Node
是实现了DOM树上的节点方法,父,子,前后节点等
Element
是DOM元素的基类,在Node之上,提供了.*Element.*
,query*
.nodeType ==
检查,是不是,元素节点,文本节点,document对象
.nodeName
(Node定义) 和.tagName
(Element的节点) 总是全大写
.innerHTML
字符串html,.outerHTML
, 相对于.innerHTML
会多一层外层的包裹,注意到这些都是指针式的操作,所以outerHTML改完以后,不应该继续使用
.data
可以获取.nodeValue/data
文本节点内容
.textContent
纯文本 [安全的!也就是不担心元素注入]
.hidden
是否可见 等同于display:none;
除此以外,特例
input
,select
,textarea
都有.value
a
有href
id
事件简介 2
UI事件 3
表单,控件 4
加载文档和其他资源 5
杂项 6
MORE
Task/MicroTask
https://javascript.info/event-loop
同步
Task: script标签加载完,一个用户事件如Click,一个setTimeout
MicroTask: MutationObserver callbacks,例如promise的callback 或 await以后
看规范 https://tc39.es/ecma262/#sec-jobs
Host environments are not required to treat Jobs uniformly with respect to scheduling. For example, web browsers and Node.js treat Promise-handling Jobs as a higher priority than other work; future features may add Jobs that are not treated at such a high priority.
其实只严格FIFO