本文主要是介绍循序渐进学 JavaScript <三>,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
续<二>
十五、深入浏览器的渲染原理
15.1 网页的解析过程
- 输入域名URL -> 经过DNS(域名)解析 -> ·转成对应的IP 地址 -> 获取资源(从服务器(主机电脑 => 有自己的IP地址)中获取) -> 返回资源 -> 浏览器进行解析
- 只下载 html ,浏览器会根据 link , script 元素请求另外一个资源
15.2 浏览器的内核
- 在浏览器中把 html 、css 、js进行解析的是浏览器内核
- 常见的浏览器内核有
- Trident(三叉戟):IE、360安全浏览器、搜狗高速浏览器、百度浏览器、UC浏览器
- Gecko(壁虎):Mozilla Firefox
- Presto(急板乐曲)->Blink(眨眼):Opera
- Webkit:Safari、360极速浏览器、搜狗高速浏览器、移动端浏览器(Android、iOS)
- Webkit->Blink:Google Chrome,Edge
- 浏览器内核指的是浏览器的排版引擎:
- 排版引擎(layout engine),也称为浏览器引擎(browser engine)、页面渲染引擎(rendering engine)或样版引擎
- dom树 + css 规则 —> 渲染树 (layout) —> paint 绘制 —> display
- 渲染树上有节点,但是没有位置信息
15.3 解析一:HTML 解析过程
- 解析 HTML,形成 dom tree
15.4 解析二:生成 css 规则
- 在解析的过程中,如果遇到CSS的link元素,那么会由浏览器负责下载对应的CSS文件
- 注意:下载 **CSS **文件是不会影响 DOM 的解析的,可能会影响渲染树
- 浏览器下载完 CSS 文件后,就会对 CSS 文件进行解析,解析出对应的规则树
- 可以称之为 CSSOM (CSS Object Model,CSS对象模型)
15.5 解析三:构建 Render Tree
- 有了DOM Tree和 CSSOM Tree后,就可以两个结合来构建 Render Tree了
- 注意一:link 元素不会阻塞DOM Tree的构建过程,但是会阻塞 Render Tree的构建过程
- 这是因为Render Tree在构建时,需要对应的CSSOM Tree;
- 注意二:Render Tree 和 DOM Tree 并不是一一对应的关系,比如对于 display为none的元素,压根不会出现在 render tree 中
15.6 解析四:布局(layout)和绘制(Paint)
- 第四步是在渲染树(Render Tree)上运行布局(Layout)以计算每个节点的几何体(大小位置信息)
- 渲染树会表示显示哪些节点以及其他样式,但是不表示每个节点的尺寸、位置等信息
- 布局是确定呈现树中所有节点的宽度、高度和位置信息;
- 第五步是将每个节点绘制(Paint)到屏幕上
- 在绘制阶段,浏览器将布局阶段计算的每个 frame 转为屏幕上实际的像素点
- 包括将元素的可见部分进行绘制,比如文本、颜色、边框、阴影、替换元素(比如img)
15.7 回流和重绘
- 回流 reflow (重排、回流)
- **布局 **: 第一次确定节点的大小和位置
- 回流 : 之后对节点的大小/位置重新计算
- 只要你有重新做布局,重新计算大小
- 引起回流: => 回流一定会引起重绘
- DOM 结构改变
- 改变了布局(width,height,padding,font-size)
- 窗口 resize 尺寸
- 调用 getComputedStyle 获取尺寸、位置
- 重绘 repaint:
- 绘制:第一次渲染内容
- 重绘:之后重新渲染,再次做一次绘制,性能影响比较小
- 引起重绘
- 修改背景色、文字颜色、边框颜色、样式
- 避免发生回流
- 修改样式时尽量一次性修改: cssText 、添加 class 修改
- 尽量避免频繁的操作 DOM
- 在一个documentFragment(片段) 或者父元素中将要操作的DOM操作完成
- 再一次性地操作
- 尽量避免通过 getComputedStyle 获取尺寸/位置
- 对某些元素使用 position 中的 absolute 或者 fixed :
- 开销相对较小
- 脱标元素不影响其他元素
15.8 特殊解析五- composite 合成
-
绘制的过程,可以将局部后的元素绘制到多个合成图层中
-
标准流里面的元素 -> layout tree -> render layer1
-
absolute->render layer2
-
-----> 多个图层合并,合成(合成图层)12
-
默认情况下,标准流中的内容都是被绘制在同一个图层中
- 虽然是不同的渲染层
-
而一些特殊的属性,会创建一个新的合成层(CompositingLayer),并且新的图层可以利用 GPU(处理图形的渲染) 来加速绘制,提高页面性能
- 可以形成新的合成层的属性
- 3D transforms
- video、canvas、iframe
- opacity 动画转换时
- position: fixed
- will-change:一个实验性的属性,提前告诉浏览器元素可能发生哪些变化
- animation 或 transition 设置了 opacity、transform
- 因为每个合成层都是单独渲染的
- 可以形成新的合成层的属性
-
分层确实可以提高性能,但是它以内存管理为代价,因此不应作为 web 性能优化策略的一部分过度使用
15.9 script 元素和页面解析的关系
-
遇到 link 元素还是会下载对应的 css 文件,但还是会继续 html 的解析
-
但是,浏览器在解析 HTML 的过程中,遇到了 script 元素是不能继续构建 DOM 树的
-
它会停止继续构建,首先下载 JavaScript 代码,并且执行 JavaScript 的脚本
-
只有等到 JavaScript 脚本执行结束后,才会继续解析 HTML ,构建 DOM 树
-
为什么要这样做呢?
- 这是因为 JavaScript 的作用之一就是操作 DOM ,并且可以修改 DOM
- 如果等到 DOM 树构建完成并且渲染再执行 JavaScript,会造成严重的回流和重绘,影响页面的性能
- 所以在遇到 script 元素时,优先下载和执行 JavaScript 代码,再继续构建 DOM 树
-
这个也往往会带来新的问题,特别是现代页面开发中
- 在目前的开发模式中(比如 Vue、React ),脚本往往比 HTML 页面更“重”,处理时间(下载->执行)需要更长;
- 所以会造成页面的解析阻塞,在脚本下载、执行完成之前,用户在界面上什么都看不到
- 为了解决这个问题,script 元素提供了两个属性(attribute):defer 和 async
15.10 defer 属性
- defer 属性告诉浏览器不要等待脚本下载,而继续解析 HTML,构建 DOM Tree
- 脚本会由浏览器来进行下载,但是不会阻塞 DOM Tree的构建过程
- 如果脚本提前下载好了,它会等待 DOM Tree 构建完成,先执行 defer 中的代码
- 再触发 DOMContentLoaded (DOM Tree 发出)事件
- 两个都有 defer,保证第二个 demo 可以使用第一个脚本的 message ,所以从上到下执行
- 多个带 defer 的脚本是可以保持正确的顺序执行的
- 从某种角度来说,defer 可以提高页面的性能,并且推荐放到 head 元素中
- 注意:defer 仅适用于外部脚本,对于 script 默认内容会被忽略
- 需要操作 dom 使用 defer
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><script src="./js/test.js" defer></script><script src="./js/demo.js" defer></script>
</head>
<body><div id="app">app</div><div class="box"></div><div id="title">title</div><div id="nav">nav</div><div id="product">product</div><!-- 1.下载需要很长的事件, 并且执行也需要很长的时间 --><!-- 总结一: 加上defer之后, js文件的下载和执行, 不会影响后面的 DOM Tree的构建 --><script>// 总结三: defer 代码是在 DOMContentLoaded事件发出之前执行window.addEventListener("DOMContentLoaded", () => {console.log("DOMContentLoaded")})</script><h1>哈哈哈哈啊</h1></body>
</html>
15.11 async 属性
- async 特性与 defer 有些类似,能够让脚本不阻塞页面。
- async 是让一个脚本完全独立的
- 浏览器不会因 async 脚本而阻塞(与 defer 类似);
- 下载完了立马执行
- async 脚本不能保证顺序,它是独立下载、独立运行,不会等待其他脚本
- async 不会能保证在 DOMContentLoaded 之前或者之后执行
- defer 通常用于需要在文档解析后操作 DOM 的 JavaScript 代码,并且对多个 script 文件有顺序要求
- async 通常用于独立的脚本,对其他脚本,甚至 DOM 没有依赖的
十六、JavaScript 内存管理和闭包
16.1 内存管理
- 不管什么样的编程语言,在代码的执行过程中都是需要给它分配内存的,不同的是某些编程语言需要自己手动的管理内存,某些编程语言可以自动管理内存
- 不管以什么样的方式来管理内存,内存的管理都会有如下的生命周期(历程)
- 分配申请你需要的内存(申请)
- 使用分配的内存(存放一些东西,比如对象等)
- 不需要使用时,对其进行释放
- 不同的编程语言对于第一步和第三步会有不同的实现
- 手动管理内存:比如 C、C++,包括早期的 OC ,都是需要手动来管理内存的申请和释放的(malloc 和 free 函数)
- 自动管理内存:比如 Java、JavaScript、Python、Swift、Dart 等,自动管理内存
- 对于开发者来说,JavaScript 的内存管理是自动的、无形的。
- 创建的原始值、对象、函数……这一切都会占用内存
- 并不需要手动进行管理,JavaScript 引擎会帮助处理好它
16.2 JavaScript 的内存管理
- JavaScript 会在定义数据时分配内存
- 不同的内存分配方式
- JS 对于原始数据类型内存的分配会在执行时,直接在栈空间进行分配
- JS 对于复杂数据类型内存的分配会在堆内存中开辟一块空间,并且将这块空间的指针返回值变量引用
16.3 JavaScript 的垃圾回收
-
垃圾
- 程序不需要再使用的对象
- 程序不能再访问到的对象
-
因为内存的大小是有限的,所以当内存不再需要的时候,需要对其进行释放,以便腾出更多的内存空间
-
在手动管理内存的语言中,需要通过一些方式自己来释放不再需要的内存,比如 free 函数
- 但是这种管理的方式其实非常的低效,影响编写逻辑的代码的效率
- 并且这种方式对开发者的要求也很高,并且一不小心就会产生内存泄露
-
所以大部分现代的编程语言都是有自己的垃圾回收机制
- 垃圾回收的英文是 Garbage Collection,简称 GC
- 对于不再使用的对象,都称之为是垃圾,它需要被回收,以释放更多的内存空间
- 语言运行环境,比如Java的运行环境 JVM,JavaScript 的运行环境 js 引擎都会内存 垃圾回收器
- 垃圾回收器也会简称为GC,所以在很多地方你看到GC其实指的是垃圾回收器
-
但是这里又出现了另外一个很关键的问题:GC怎么知道哪些对象是不再使用的呢?
- GC 的实现以及对应的算法
16.4 常见的 GC 算法
16.4.1 引用计数(Reference counting)
- 引用计数
- 当一个对象有一个引用指向它时,那么这个对象的引用就+1
- 当一个对象的引用为0时,这个对象就可以被销毁掉
- 这个算法有一个很大的弊端就是会产生循环引用
obj1 = {}
obj2 = {}
obj1.info = obj2
obj2.info = obj1
16.4.2 标记清除(mark-Sweep)
- 标记清除:
- 标记清除的核心思路是可达性(Reachability)
- 这个算法是设置一个根对象(root object),垃圾回收器会定期从这个根开始,找所有从根开始有引用到的对象,对于哪些没有引用到的对象,就认为是不可用的对象
- 这个算法可以很好的解决循环引用的问题
- 遛狗法则
- 缺点:产生空间碎片化的问题
16.4.3 其他算法优化补充
- JS 引擎比较广泛的采用的就是可达性中的标记清除算法,当然类似于V8引擎为了进行更好的优化,它在算法的实现细节上也会结合一些其他的算法
- 标记整理(Mark-Compact) 和“标记-清除”相似
- 不同的是,回收期间同时会将保留的存储对象搬运汇集到连续的内存空间,从而整合空闲空间,避免内存碎片化
- 分代收集(Generational collection)—— 对象被分成两组:“新的”和“旧的”
- 许多对象出现,完成它们的工作并很快死去,它们可以很快被清理
- 那些长期存活的对象会变得“老旧”,而且被检查的频次也会减少
- V8 垃圾回收策略
- 增量收集(Incremental collection)
- 如果有许多对象,并且试图一次遍历并标记整个对象集,则可能需要一些时间,并在执行过程中带来明显的延迟
- 所以引擎试图将垃圾收集工作分成几部分来做,然后将这几部分会逐一进行处理,这样会有许多微小的延迟而不是一个大的延迟
- 闲时收集(Idle-time collection)
- 垃圾收集器只会在 CPU 空闲时尝试运行,以减少可能对代码执行的影响
16.5 JavaScript的函数式编程 FP
-
JavaScript是支持函数式编程的
- 函数式编程中的函数指的不是程序中的函数(方法),而是数学中的函数即映射关系,例如:y = sin(x),x和y的关系
- 对运算过程的抽象,屏蔽实现的细节,只关注
- 相同的输入要有相同的输出
-
在JavaScript中,函数是非常重要的,并且是一等公民
- 那么就意味着函数的使用是非常灵活的
- 高阶函数:可以作为另外一个函数的参数,也可以作为另外一个函数的返回值来使用
-
所以JavaScript存在很多的高阶函数
- 自己编写高阶函数
- 使用内置的高阶函数
-
目前在 vue3+react
-
开发中,也都在趋向于函数式编程
- vue3 composition api: setup函数 -> 代码(函数hook,定义函数)
- react:class -> function -> hooks
// 非函数式:面向过程
let num1 = 2
let num2 = 3
let sum = num1 + num2
console.log(sum)
// 函数式
function add (n1, n2) {
return n1 + n2
}
let sum = add(2, 3)
console.log(sum)
16.6 闭包
-
之所以存在这么多对象,作用域和作用域链,都是为了闭包。闭包是通过作用域链实现的。
-
在 计算机科学中 和在 JavaScript 中
-
在计算机科学中对闭包的定义(维基百科)
- 闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures)
- 是在支持 头等函数 的编程语言中,实现词法绑定的一种技术
- 闭包在实现上是一个结构体,它存储了一个函数和一个关联的环境(相当于一个符号查找表)
- 闭包跟函数最大的区别在于,当捕捉闭包的时候,它的 自由变量 会在捕捉时被确定,这样即使脱离了捕捉时的上下文,它也能照常运行
-
闭包的概念出现于60年代,最早实现闭包的程序是 Scheme,那么就可以理解为什么 JavaScript 中有闭包
- 因为 JavaScript 中有大量的设计是来源于 Scheme 的
-
MDN 对 JavaScript 闭包的解释
- 一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)
- 闭包可以在一个内层函数中访问到其外层函数的作用域
- 在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来
-
总结
- 一个普通的函数 function,如果它可以访问外层作用域的自由变量,那么这个函数和周围环境就是一个闭包
- 从广义的角度来说:JavaScript 中的函数都是闭包
- 从狭义的角度来说:JavaScript 中一个函数,如果访问了外层作用域的变量,那么它是一个闭包
-
应用
// 求员工工资:基本工资 + 绩效
function getSalary(base){return function(performance){return base + performance}
}
let level1 = getSalary(10000)
const allSalart = level1(1200)
16.7 内存泄漏以及释放
- 内存释放:
- 对象:
adder=null
- 数组:
adder=[]/null
- 对象:
- 对于那些永远不会再使用的对象,但是对于 GC 来说,它不知道要进行释放的对应内存会依然保留着
- 什么情况会出现内存泄露
- 意外的全局变量 ==》开启严格模式
- 没有声明直接赋值,放在 window 上面
- 通过 this 创建意外的全局变量
- 闭包
- 闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多
- dom 泄露
- 为了减少DOM访问次数,一般情况下,当需要多次访问同一个DOM方法或属性时,会将DOM引用缓存到一个局部变量中。但如果在执行某些删除、更新操作后,可能会忘记释放掉代码中对应的DOM引用,这样会造成DOM内存泄露
- 意外的全局变量 ==》开启严格模式
16.8 浏览器的优化操作
- AO 对象不会被销毁时,并不是里面的所有属性都不会被释放
function foo() {var name = "foo"var age = 18var height = 1.88function bar() {debuggerconsole.log(name)}return bar}var fn = foo()fn()
十七、JavaScript 函数的增强知识
17.1 函数对象的属性
-
默认函数对象中已经有自己的属性
- name 属性:
console.log(foo.name)
// 将两个函数放到数组中(了解)var fns = [foo, bar]for (var fn of fns) {console.log(fn.name)}
- length 属性:返回函数参数的个数
- 不会将剩余属性放在 length 里面
function demo(...args) {}demo("abc", "cba", "nba")
- name 属性:
17.2 arguments
-
arguments 是一个对应于传递给函数的参数的类数组(array-like)对象
-
默认用法
//通过索引获取内容console.log(arguments[0])console.log(arguments[1])// 遍历(可迭代对象)for (var i = 0; i < arguments.length; i++) {console.log(arguments[i])}for (var arg of arguments) {console.log(arg)}
-
是一个对象类型
- 但是它却拥有数组的一些特性,比如说 length,比如可以通过 index 索引来访问
- 但是它却没有数组的一些方法,比如 filter、map、foreach 等
// 数组 filterfor (var arg of arguments) {if (arg % 2 === 0) {console.log(arg)}}var evenNums = arguments.filter(item => item % 2 === 0)//错误!console.log(eventNums)
-
转成数组
// 2.1.将arguments转成数组方式一:// var newArguments = []// for (var arg of arguments) {// newArguments.push(arg)// }// console.log(newArguments)// 2.2.将arguments转成数组方式三: ES6中方式// Array.from// var newArgs1 = Array.from(arguments)// console.log(newArgs1)//展开运算符// var newArgs2 = [...arguments]// console.log(newArgs2)// 2.3.将arguments转成数组方式二: 调用slice(截取)方法 //slice在内部对函数做了一个截取 //显式绑定this //使用[]获取slice方法var newArgs = [].slice.apply(arguments)// var newArgs = Array.prototype.slice.apply(arguments)console.log(newArgs)
17.3 箭头函数不绑定arguments
- 不是说不能用,是会在上层作用域进行查找
- 全局作用域没有就去 window 找
// 1.箭头函数不绑定arguments// var bar = () => {// console.log(arguments)// }// bar(11, 22, 33)// 2.函数的嵌套箭头函数function foo() {var bar = () => {console.log(arguments)}bar()}foo(111, 222)
17.4 函数的剩余( rest )参数
-
ES6 中引用了 rest parameter,可以将不定数量的参数放入到一个数组中
-
…otherNums 最后一个参数是… 为前缀的,那么它会将剩余的参数放到该参数中,是一个数组
-
剩余参数需要写到最后
-
以前的时候为了拿到其它的参数,会使用 arguments
- 不是数组,类数组
- 还得剥除前面
- 箭头函数没有 arguments
-
用来替代 arguments,直接使用 rest
// 剩余参数: rest parametersfunction foo(num1, num2, ...otherNums) {// otherNums数组console.log(otherNums)}foo(20, 30, 111, 222, 333)// 默认一个函数只有剩余参数function bar(...args) {console.log(args) // ["abc",123,"cba",321]}bar("abc", 123, "cba", 321)// 注意事项: 剩余参数需要写到其他的参数最后
- 区别
- 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参
- arguments 对象不是一个真正的数组,而 rest 参数是一个真正的数组,可以进行数组的所有操作
- arguments 是早期的 ECMAScript 中为了方便去获取所有的参数提供的一个数据结构,而 rest 参数是 ES6 中提供并且希望以此替代 arguments 的
17.5 JavaScript 纯函数
-
Pure
-
函数式编程中有一个非常重要的概念叫纯函数,JavaScript 符合函数式编程的范式,所以也有纯函数的概念
-
纯函数的维基百科定义
- 在程序设计中,若一个函数符合以下条件,那么这个函数被称为纯函数
- 此函数在相同的输入值时,需产生相同的输出
- 有闭包就不一定
- 函数的输出和输入值以外的其他隐藏信息或状态无关,也和由 I/O 设备产生的外部输出无关
- 该函数不能有语义上可观察的函数副作用,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等
-
总结:确定的输入一定产生确定的输出;函数在执行过程中,不能产生副作用
-
函数副作用
-
依赖于外部变量
- 配置文件
- 数据库
- 获取用户的输入
-
在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响,比如修改了全局变量,修改参数或者改变外部的存储
// 不是一个纯函数var address = "广州市"function printInfo(info) {console.log(info.name, info.age, info.message)info.flag = "已经打印结束"address = info.address}var obj = {name: "lili",age: 18,message: "哈哈哈哈"}printInfo(obj)console.log(obj)if (obj.flag) {}
-
-
举例
- slice:slice 截取数组时不会对原数组进行任何操作,而是生成一个新的数组
- splice:splice 截取数组, 会返回一个新的数组, 也会对原数组进行修改–不是纯函数
-
作用和优势
- 安心的写:不需要去关心外层作用域中的值目前是什么状态
- 安心的用:调用函数时,确定的输入一定产生确定的输出
-
React 中就要求无论是函数还是 class 声明一个组件,这个组件都必须像纯函数一样,保护它们的 props 不被修改
-
纯函数的好处
- 可缓存,提高性能
- 可以测试
- 方便并行处理
- js 本来是单线程的,但是 es6 之后开启了一个Web Worker,可以开启新的线程
- 在多线程环境下并行操作共享的内存数据很可能会出现意外情况
- 纯函数不需要访问共享的内存数据,所以在并行环境下可以任意运行纯函数
17.6 柯里化(一种转换)
-
柯里化也是属于函数式编程里面一个非常重要的概念
- 是一种关于函数的高阶技术
- 它不仅被用于 JavaScript,还被用于其他编程语言
-
维基百科的解释
- 在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化
- 是把接收多个参数的函数,变成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数,而且返回结果的新函数的技术
// 因为foo不是一个柯里化的函数, 所以目前是不能这样调用// 柯里化函数function foo2(x) {return function(y) {return function(z) {console.log(x + y + z)}}}foo2(10)(20)(30)
- 柯里化声称“如果你固定某些参数,你将得到接受余下参数的一个函数”
-
总结:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩余的参数
-
柯里化是一种函数的转换,将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)©
- 柯里化不会调用函数,只是对函数进行转换
-
优势
- 职责单一
- 参数复用
17.7 自动柯里化函数
- 经过 liliCurrying(fn) 传入一个函数对象
- 返回一个柯里化函数
- 两类操作
- 继续返回一个新的函数
- 继续接受参数
- 直接执行 fn 函数
- 继续返回一个新的函数
function liliCurrying(fn) {function curryFn(...args) {// 两类操作:// 第一类操作: 继续返回一个新的函数, 继续接受参数// 第二类操作: 直接执行fn的函数if (args.length >= fn.length) { // 执行第二类// return fn(...args)return fn.apply(this, args)} else { // 执行第一类return function (...newArgs) {// return curryFn(...args.concat(newArgs))return curryFn.apply(this, args.concat(newArgs))}}}return curryFn}
17.8 组合函数
- 组合(Compose)函数是在 JavaScript 开发过程中一种对函数的使用技巧、模式
- 比如现在需要对某一个数据进行函数的调用,执行两个函数 fn1 和 fn2,这两个函数是依次执行的
- 那么如果每次都需要进行两个函数的调用,操作上就会显得重复
- 可以将这两个函数组合起来,自动依次调用
- 这个过程就是对函数的组合,称之为组合函数(Compose Function)
// 第一步对数字*2function double(num) {return num * 2}// 第二步对数字**2function pow(num) {return num ** 2}console.log(pow(double(num)))console.log(pow(double(55)))console.log(pow(double(22)))// 将上面的两个函数组合在一起, 生成一个新的函数function composeFn(num) {return pow(double(num))}
- 封装组合函数
// 封装的函数: 你传入多个函数, 我自动的将多个函数组合在一起挨个调用function composeFn(...fns) {// 1.边界判断(edge case)var length = fns.lengthif (length <= 0) returnfor (var i = 0; i < length; i++) {var fn = fns[i]if (typeof fn !== "function") {throw new Error(`index position ${i} must be function`)}}// 2.返回的新函数return function(...args) {var result = fns[0].apply(this, args)for (var i = 1; i < length; i++) {var fn = fns[i]result = fn.apply(this, [result])}return result}}
17.9 with 语句的使用
- 作用:扩展了作用域链
- with(obj):明确指定了作用域
- 不建议使用,混淆原有的作用域
var obj = {message: "Hello World"}with (obj) {console.log(message)}
17.10 eval 函数
-
内建函数 eval 允许执行一个代码字符串
- eval 是一个特殊的函数,它可以将传入的字符串当做 JavaScript 代码来运行
- eval 会将最后一句执行语句的结果,作为返回值
-
不建议在开发中使用 eval
- 可读性非常差
- 字符串被篡改的危险
- 必须经过 js 解释器,不能被 js 引擎优化
17.11 严格模式
-
JavaScript 历史的局限性
- 长久以来,JavaScript 不断向前发展且并未带来任何兼容性问题
- 新的特性被加入,旧的功能也没有改变,这么做有利于兼容旧代码
- 但缺点是 JavaScript 创造者的任何错误或不完善的决定也将永远被保留在 JavaScript 语言中
-
在 ECMAScript5 标准中,JavaScript 提出了严格模式的概念(Strict Mode)
- 严格模式很好理解,是一种具有限制性的 JavaScript 模式,从而使代码隐式的脱离了”懒散(sloppy)模式“
- 支持严格模式的浏览器在检测到代码中有严格模式时,会以更加严格的方式对代码进行检测和执行
-
严格模式对正常的 JavaScript 语义进行了一些限制
- 严格模式通过抛出错误来消除一些原有的静默(silent)错误
- 严格模式让 JS 引擎在执行代码时可以进行更多的优化(不需要对一些特殊的语法进行处理)
- 严格模式禁用了在 ECMAScript 未来版本中可能会定义的一些语法
17.12 开启严格模式
- 严格模式通过在文件或者函数开头使用“use strict” 来开启
<script>// 给整个script开启严格模式"use strict"// 粒度化的控制// 给一个函数开启严格模式function foo() {"use strict"}
//默认就是在严格模式class Person {}</script>
- 没有类似于 “no use strict” 这样的指令可以使程序返回默认模式
- 现代 JavaScript 支持“ class ” 和“ module ”,它们会自动启用 use strict
17.13 严格模式限制
-
无法意外地创建全局变量
-
严格模式会使引起静默失败(silently fail,注:不报错也没有任何效果)的赋值操作抛出异常)
-
严格模式下试图删除不可删除的属性
-
严格模式不允许函数参数有相同的名称
-
不允许 0 开头的八进制语法(0o)
-
在严格模式下,不允许使用 with
-
在严格模式下,eval 不再为上层创建变量
-
严格模式下,this 绑定不会默认转化为对象
-
独立函数执行默认情况下,绑定 window 对象
-
在严格模式下,不绑定全局对象而是 undefined
"use strict"// 1.不会意外创建全局变量// function foo() {// message = "Hello World"// }// foo()// console.log(message)// 2.发现静默错误var obj = {name: "lili"}Object.defineProperty(obj, "name", {writable: false,configurable: false})// obj.name = "kobe"console.log(obj.name)// delete obj.nameconsole.log(obj)// 3.参数名称不能相同// function foo(num, num) {// }// 4.不能以0开头// console.log(0o123)// 5.eval函数不能为上层创建变量// eval(`var message = "Hello World"`)// console.log(message)// 6.严格模式下, this是不会转成对象类型的function foo() {console.log(this)}foo.apply("abc")foo.apply(123)foo.apply(undefined)foo.apply(null)// 独立函数执行默认模式下, 绑定window对象// 在严格模式下, 不绑定全局对象而是undefinedfoo()
十八、JavaScript 对象的增强知识
18.1 对象属性的控制
-
默认情况下属性都是没有特别的限制
-
可以对对象中的属性进行某些限制,使用的不多
- 但是 getter 和 setter 在 vue2 中做响应式操作
-
对一个属性进行比较精准的控制操作,使用属性描述符
- 精准的添加或修改对象的属性(精准的描述)
- 这个属性不能被删除
- 这个不能写入只能读取
- 获取值时,只能获取xxx
- 在遍历的时候不要出现
- 属性描述符需要使用 Object.defineProperty 来对属性进行添加或者修改
- 精准的添加或修改对象的属性(精准的描述)
18.2 Object.defineProperty
-
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
- Object.defineProperty(obj,prop,descriptor)
- 修改哪一个对象的哪一个属性进行哪些描述
-
三个参数
- obj:要定义属性的对象
- prop:要定义或修改的属性的名称或Symbol
- descriptor:要定义或修改的属性描述符
-
返回值:返回被修改后的对象
18.3 属性描述符
- 类型
- 数据属性(Data Properties)描述符(Descriptor)
- 存取属性(Accessor访问器 Properties)描述符(Descriptor)
- 获取或存取的时候调用一些函数
18.4 数据属性描述符
- Configurable
- 配置能够通过 delete 删除属性,是否可以修改他的特性,或者是否可以将它修改为存取属性描述符
- 直接在对象上定义属性时候,默认 Configurable 是 true
- 通过通过属性描述符定义一个属性时 Configurable 默认添加为 false
- Enumerable:表示属性是否可以通过枚举 for-in 或者 Object.keys() 返回该属性
- Writable:表示是否可以修改属性的值
- value:返回自己写入的 value
var obj = {name: "lili", // configurable: trueage: 18}Object.defineProperty(obj, "name", {configurable: false, // 告诉js引擎, obj对象的name属性不可以被删除enumerable: false, // 告诉js引擎, obj对象的name属性不可枚举(for in/Object.keys)writable: false, // 告诉js引擎, obj对象的name属性不写入(只读属性 readonly)value: "lili" // 告诉js引擎, 返回这个value})delete obj.nameconsole.log(obj)// 通过Object.defineProperty添加一个新的属性Object.defineProperty(obj, "address", {})delete obj.addressconsole.log(obj)console.log(Object.keys(obj))obj.name = "kobe"console.log(obj.name)
18.5 存取属性描述符
-
Configurable 和 Enumerable 方法
-
get:获取属性时会执行的函数,默认为 undefined,会监听读取的过程
-
set:function(value){log(“哈哈”,value)}
- set:设置属性时会执行的函数,默认为 undefined,会监听写入的过程
// vue2响应式原理var obj = {name: "lili"}// 对obj对象中的name添加描述符(存取属性描述符)var _name = ""//_有特殊含义,别乱用我啊Object.defineProperty(obj, "name", {configurable: true,enumerable: false,set: function(value) {console.log("set方法被调用了", value)_name = value//把值保存在_name里面},get: function() {console.log("get方法被调用了")return _name}})obj.name = "kobe"obj.name = "jame"obj.name = "curry"obj.name = "lili"// 获取值console.log(obj.name)
18.6 定义多个属性
- Object.defineProperties() 方法直接在一个对象上定义 多个 新的属性或修改现有属性,并且返回该对象
var obj = {name: "lili",age: 18,height: 1.88}// Object.defineProperty(obj, "name", {})// Object.defineProperty(obj, "age", {})// Object.defineProperty(obj, "height", {})// 新增的方法Object.defineProperties(obj, {name: {configurable: true,enumerable: true,writable: false},age: {},height: {}})
18.7 对象方法补充
-
获取对象的属性描述符
- getOwnPropertyDescriptor
- getOwnPropertyDescriptors
-
禁止对象扩展新属性:preventExtensions
- 给一个对象添加新的属性会失败(在严格模式下会报错)
-
密封对象,不允许配置和删除属性:seal
- 实际是调用 preventExtensions
- 并且将现有属性的 configurable:false
-
冻结对象,不允许修改现有属性: freeze
- 实际上是调用 seal
- 并且将现有属性的 writable: false
var obj = {name: "LIli",age: 18}// 1.获取属性描述符console.log(Object.getOwnPropertyDescriptor(obj, "name"))console.log(Object.getOwnPropertyDescriptors(obj))// 2.阻止对象的扩展Object.preventExtensions(obj)obj.address = "广州市"console.log(obj)// 3.密封对象(不能进行配置)Object.seal(obj)delete obj.nameconsole.log(obj)// 4.冻结对象(不能进行写入)Object.freeze(obj)obj.name = "kobe"console.log(obj)
这篇关于循序渐进学 JavaScript <三>的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!