Js进阶
JS 进阶
代码示例
1 | (() => { |
catch代码块接收参数x。当我们传递参数时,这与之前定义的变量x不同。这个x是属于catch块级作用域的。然后,我们将块级作用域中的变量赋值为
1,同时也设置了变量y的值。现在,我们打印块级作用域中的变量x,值为1。
catch块之外的变量x的值仍为undefined,y的值为2。当我们在catch块之外执行console.log(x)时,返回undefined,y返回2。
1 | let person = { name: "Lydia" }; |
首先我们声明了一个拥有
name属性的对象person。然后我们又声明了一个变量
members. 将首个元素赋值为变量person。当设置两个对象彼此相等时,它们会通过 引用 进行交互。但是当你将引用从一个变量分配至另一个变量时,其实只是执行了一个 复制 操作。(注意一点,他们的引用 并不相同!)接下来我们让
person等于null。我们没有修改数组第一个元素的值,而只是修改了变量
person的值,因为元素(复制而来)的引用与person不同。members的第一个元素仍然保持着对原始对象的引用。当我们输出members数组时,第一个元素会将引用的对象打印出来。
1 | const num = parseInt("7*6", 10) // 7 |
只返回了字符串中第一个字母。设定了 进制 后 (也就是第二个参数,指定需要解析的数字是什么进制:十进制、十六机制、八进制、二进制等等……),
parseInt检查字符串中的字符是否合法。一旦遇到一个在指定进制中不合法的字符后,立即停止解析并且忽略后面所有的字符。
*就是不合法的数字字符。所以只解析到"7",并将其解析为十进制的7.num的值即为7.
1 | const name = "Lydia"; |
delete操作符返回一个布尔值:true指删除成功,否则返回false. 但是通过var,const或let关键字声明的变量无法用delete操作符来删除。
name变量由const关键字声明,所以删除不成功:返回false. 而我们设定age等于21时,我们实际上添加了一个名为age的属性给全局对象。对象中的属性是可以删除的,全局对象也是如此,所以delete age返回true.
函数柯里化(Curry):函数元(参数)降维技术
1 | const a = (b, c) => b + c |
1 | const curry = function(fn){ // bind(绑定) fn |
1 | [y] = [1, 2, 3, 4, 5]; |
1 | // module.js |
类 是 构造函数 的 语法糖 如果用 构造函数 的方式重写 person 类则是:
1 | // 类 |
1 | const myMap = new Map() |
当通过
set方法添加一个键值对,一个传递给set方法的参数将会是键名,第二个参数将会是值。在这个 case 里,键名为 函数() => 'greeting',值为'Hello world'。myMap现在就是{ () => 'greeting' => 'Hello world!' }。1 是错的,因为键名不是
'greeting'而是() => 'greeting'。 3 是错的,因为我们给get方法传递了一个新的函数。对象受 引用 影响。函数也是对象,因此两个函数严格上并不等价,尽管他们相同:他们有两个不同的内存引用地址。
创建一个新的变量 counterTwo 并将 counOne 的 引用地址 赋值给他(Two、One 指向内存同一个地址)
1 | class Counter { |
事件循环机制**event loop**
1 | const myPromise = Promise.resolve("Promise!"); |
疑难杂症
事件循环
JS是一门单线程的语言,这是因为它运行在浏览器的渲染主线程中,而渲染主线程只有一个。
而渲染主线程承担着诸多的工作,渲染页面、执行 JS 都在其中运行。
如果使用同步的方式,就极有可能导致主线程产生阻塞,从而导致消息队列中的很多其他任务无法得到执行。这样一来,一方面会导致繁忙的主线程白白的消耗时间,另一方面导致页面无法及时更新,给用户造成卡死现象。
所以浏览器采用异步的方式来避免。具体做法是当某些任务发生时,比如计时器、网络、事件监听,主线程将任务交给其他线程去处理,自身立即结束任务的执行,转而执行后续代码。当其他线程完成时,将事先传递的回调函数包装成任务,加入到消息队列的末尾排队,等待主线程调度执行。
在这种异步模式下,浏览器永不阻塞,从而最大限度的保证了单线程的流畅运行。
对象的键被自动转化为字符串,当对象化为字符串时,变成”[object object]”
导致事件的最深嵌套的元素是事件的 target 可以通过event.stopPropagation 来阻止事件冒泡
.apply .call .bind (parameter1, parameter2)
parameter1 传递对象引用
parameter2 所传递对象的参数(apply是数组,而call是参数列表,且为一次性传入,bind可多次动态传入,参数列表形式传入)
bind 返回函数的副本,但带有绑定上下文!它不是立即执行的
apply call 立即执行
7 种内置类型
string number (bigint) boolean null undefined symbol object
function 不是一种类型 属于 object 对象
JavaScript 中的一切都是 基本类型(7)+ 对象
string 是可迭代的 […’Lydia’] = [‘L’, ‘y’, ‘d’, ‘i’, ‘a’]
引入的模块 是只读的
1 | import myCounter from "./counter"; // myCounter 为只读属性 |
引入的模块是 只读 的:你不能修改引入的模块。只有导出他们的模块才能修改其值。
JavaScript中闭包(Closure,上面我们已经提到过)产生的原因,一个函数还没有被销毁(调用没有完全结束),你可以在子环境内使用父环境的变量。
delete 不能删除 const let var 声明的变量 返回 false
defineProperty(object, key, { value: 1 }) 给指定对象添加 key属性 不可枚举
import 命令是编译阶段执行的,在代码运行之前,被导入的模块代码,先运行。区别于 CommonJs 的 require ,require 按需加载,执行顺序取决于 require 所在代码位置
functong * name(params){} : 表示声明了 一个函数生成器Genarator 对象,通常 name 生成器函数,配合yeild 使用,以达到 name 函数内部逻辑 暂停 恢复执行 。 name.next() 得到一个 { value: 值, done: true } 对象。next 方法可以带参数 name.next('param1') 则 param1 将传递给 name 函数的 上一个 yeild 表达式的返回值。注:yeild 只能在生成函数中使用。
Object.freeze(obj) 使得obj对象的属性 无法添加、修改、删除(冻结对象)
尝试打印一个未定义的对象,报错 ReferenceError 引用错误
数组元素:任何值、数学表达式、日期、函数计算
定时器 setTimeout setInterval 中,this 指向 全局对象 windows
function(a, b = a) 可以将函数参数的默认值 设置为另一个参数,只需另一个参数定义于这个函数参数之前
push 方法返回 数组长度 [1,2,3].push(5) === 4
function(a, b, ...c){} ...c 表示 剩余参数 是一个 数组 只能 作为最后一个参数 否则抛出 语法错误 syntaxError
Javascript 引擎自动在语句后添加 ;号
可以将类设置为等同于其他类/函数 构造函数
symbol 类型是不可 枚举的 Object.keys(obj) 将不可见 symbol 类型的 key 但 Object.getOwnPropertySymbols(obj) 方法访问
箭头函数 若返回 一个值,则 () => 不必要写 {} ,若返回 对象 则必须 () => { return { obj } } 或 () => ({ obj })
"string"() 字符串调用(非函数不能调用)报错 TypeError 类型错误
常见 假值 null undefined "" 0 -0 NaN false 0n
微任务: promise.then() process.nextTick() MutationOvserver() 宏任务: setTimeout setInterval script setImmediate I/O UI-rendering await 执行当前代码,将后续代码加入 微任务队列。等价于 先执行new Promise()后,将 promise.then() 加入微任务队列 事件循环数据结构:微任务队列 、 宏任务队列。不断循环执行 宏任务队列
a + b 若 a、b 不全为 数字 Js引擎 会将 a、强制转为 字符串 { name: value } 对象强转字符串为 [ Object object ] ,再进行字符串拼接
对象通过引用传递(引用内存的位置),当我们检查对象的严格性相等时=== ,我们正在比较他的引用
. 点 表示法访问对象属性,Obj.name,会使用改确切名称在对象上查找属性,若没有name ,则返回 undefined
Obj['name'],方括号表示法,它会从第一个 [ 开始直到找到右方括号 ],之后才开始评估 语句,[]中可以插入表达式,运行时才确认的属性,而. 属性不能访问。
❤️ 表情符号是 unicode,对于相同的 表情符号,他们总是相等的 如❤️ === ❤️
map slice filter 返回一个新数组,find返回一个元素,reduce返回一个计算 值
在 javascript 中 原始类型通过 值起作用。当 原始类型 如字符串 的值是数组的值时,该字符串的值只是 数组引用值的复制,改变字符串值时,数组值并不会被改变
let const与 var 声明的变量都会有 提升,但与var 不同的是,它们不会被初始化 undefined.在声明之前不能访问它,这个行为成为暂时性死区,试图访问会抛出ReferenceError (暂时性死区:在 let 声明之前的执行瞬间)
((x => x)('param')) 是个立即执行函数 相当于向箭头函数 x => x 传递参数 param 并且执行
() => 箭头函数并没有属于自己的 this,this捕获其所在上下文的this,作为自己的值
??= 逻辑空赋值,x ??= value 仅在 x = 空值(undefined 或 null)时,对其赋值 value
get语法将对象属性绑定到查询该属性时被调用的方法。(当 访问 obj.name时,就会调用 get name()方法若有)。当尝试设置属性时,set语法将对象属性绑定到要调用的函数,它还可以在类中应用。(当 修改属性 obj.name = value时,就会调用 set name(value)方法若有)。get、set方法返回 undefined
!typeof name === "string" : 先 计算 typeof name 再取反 ! 最后再检验 ===
Number.isNaN(value) 检测 value是否是 数字且等价于NaN isNaN 检测 value 是不是 不是一个数字
Object.seal(obj) 可以防止新属性被添加、存在属性 被移除。然而、仍然可以对存在属性 进行更改
Object.freeze(obj)对 对象进行浅冻结 ,不能对 obj 的属性 添加、修改、删除。但可以操作 obj 里的对象属性(浅冻结)
ES2015 -> es6 ... ES2020 -> es11,在 ES2020 中,通过 #可以给 class添加私有变量,但在 class 外部我们无法获取该值,尝试获取、则会报错 syntaxError 语法错误
对象默认不是可迭代的,可以通过添加对象迭代器[sysmbol.iterator]来定义迭代规则,其返回一个generator函数*[sysmbol.iterator](){},[...obj]无法解构,{...obj}可以解构
1 | function* infiniteCounter() { |
在 class中constructor中的属性,不会在原型链上共享
Promise.all方法可以并行运行promise,如果其中一个promise失败了,Promise.all方法会带上reject的promise的值_rejects_
函数式编程特性
说了这么久,都是在讲函数,那么究竟什么是函数式编程呢?在网上你可以看到很多定义,但大都离不开这些特性。
- First Class 函数:函数可以被应用,也可以被当作数据。
- Pure 纯函数,无副作用:任意时刻以相同参数调用函数任意次数得到的结果都一样。
- Referential Transparency 引用透明:可以被表达式替代。
- Expression 基于表达式:表达式可以被计算,促进数据流动,状态声明就像是一个暂停,好像数据到这里就会停滞了一下。
- Immutable 不可变性:参数不可被修改、变量不可被修改—宁可牺牲性能,也要产生新的数据(Rust内存模型例外)。
- High Order Function 大量使用高阶函数:变量存储、闭包应用、函数高度可组合。
- Curry 柯里化:对函数进行降维,方便进行组合。
- Composition 函数组合:将多个单函数进行组合,像流水线一样工作。
另外还有一些特性,有的会提到,有的一笔带过,但实际也是一个特性(以Haskell为例)。
- Type Inference 类型推导:如果无法确定数据的类型,那函数怎么去组合?(常见,但非必需)
- Lazy Evaluation 惰性求值:函数天然就是一个执行环境,惰性求值是很自然的选择。
- Side Effect IO:一种类型,用于处理副作用。一个不能执行打印文字、修改文件等操作的程序,是没有意义的,总要有位置处理副作用。(边缘)
数学上,我们定义函数为集合A到集合B的映射。在函数式编程中,我们也是这么认为的。函数就是把数据从某种形态映射到另一种形态。注意理解“映射”,后面我们还会讲到。
浏览器性能优化
渲染流程
渲染进程 html -> DOM树
渲染引擎 css -> css样式树 计算dom节点样式
构建布局树 Dom树 + css样式树
生成图层树 对布局树进行分层
每个图层生成绘制列表 提交给合成线程
每个图层单独绘制
合成图层
渲染性能
减少回流(重排:大小、位置、布局几何信息发生变化)、重绘 (颜色、背景、border)
将回流、重绘的元素作为单独的图层(css3D、canvas、position: fixed、video、css3动画节点)
浏览器以 图层 为单位 进行渲染
元素作为单独图层(
will-change: transform),使用opacity 或者 transform: transformX(100px)不会触发 回流、重绘(元素偏移量右 100px 则会交给GPU处理)
