续<二>
绘制的过程,可以将局部后的元素绘制到多个合成图层中
标准流里面的元素 -> layout tree -> render layer1
absolute->render layer2
-----> 多个图层合并,合成(合成图层)12
默认情况下,标准流中的内容都是被绘制在同一个图层中
而一些特殊的属性,会创建一个新的合成层(CompositingLayer),并且新的图层可以利用 GPU(处理图形的渲染) 来加速绘制,提高页面性能
分层确实可以提高性能,但是它以内存管理为代价,因此不应作为 web 性能优化策略的一部分过度使用
遇到 link 元素还是会下载对应的 css 文件,但还是会继续 html 的解析
但是,浏览器在解析 HTML 的过程中,遇到了 script 元素是不能继续构建 DOM 树的
它会停止继续构建,首先下载 JavaScript 代码,并且执行 JavaScript 的脚本
只有等到 JavaScript 脚本执行结束后,才会继续解析 HTML ,构建 DOM 树
为什么要这样做呢?
这个也往往会带来新的问题,特别是现代页面开发中
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>Documenttitle>
<script src="./js/test.js" defer>script>
<script src="./js/demo.js" defer>script>
head>
<body>
<div id="app">appdiv>
<div class="box">div>
<div id="title">titlediv>
<div id="nav">navdiv>
<div id="product">productdiv>
<script>
// 总结三: defer 代码是在 DOMContentLoaded事件发出之前执行
window.addEventListener("DOMContentLoaded", () => {
console.log("DOMContentLoaded")
})
script>
<h1>哈哈哈哈啊h1>
body>
html>
垃圾
因为内存的大小是有限的,所以当内存不再需要的时候,需要对其进行释放,以便腾出更多的内存空间
在手动管理内存的语言中,需要通过一些方式自己来释放不再需要的内存,比如 free 函数
所以大部分现代的编程语言都是有自己的垃圾回收机制
但是这里又出现了另外一个很关键的问题:GC怎么知道哪些对象是不再使用的呢?
obj1 = {}
obj2 = {}
obj1.info = obj2
obj2.info = obj1
JavaScript是支持函数式编程的
在JavaScript中,函数是非常重要的,并且是一等公民
所以JavaScript存在很多的高阶函数
目前在 vue3+react
开发中,也都在趋向于函数式编程
// 非函数式:面向过程
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)
之所以存在这么多对象,作用域和作用域链,都是为了闭包。闭包是通过作用域链实现的。
在 计算机科学中 和在 JavaScript 中
在计算机科学中对闭包的定义(维基百科)
闭包的概念出现于60年代,最早实现闭包的程序是 Scheme,那么就可以理解为什么 JavaScript 中有闭包
MDN 对 JavaScript 闭包的解释
总结
应用
// 求员工工资:基本工资 + 绩效
function getSalary(base){
return function(performance){
return base + performance
}
}
let level1 = getSalary(10000)
const allSalart = level1(1200)
adder=null
adder=[]/null
function foo() {
var name = "foo"
var age = 18
var height = 1.88
function bar() {
debugger
console.log(name)
}
return bar
}
var fn = foo()
fn()
默认函数对象中已经有自己的属性
console.log(foo.name)
// 将两个函数放到数组中(了解)
var fns = [foo, bar]
for (var fn of fns) {
console.log(fn.name)
}
function demo(...args) {
}
demo("abc", "cba", "nba")
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)
}
是一个对象类型
// 数组 filter
for (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)
// 1.箭头函数不绑定arguments
// var bar = () => {
// console.log(arguments)
// }
// bar(11, 22, 33)
// 2.函数的嵌套箭头函数
function foo() {
var bar = () => {
console.log(arguments)
}
bar()
}
foo(111, 222)
ES6 中引用了 rest parameter,可以将不定数量的参数放入到一个数组中
…otherNums 最后一个参数是… 为前缀的,那么它会将剩余的参数放到该参数中,是一个数组
剩余参数需要写到最后
以前的时候为了拿到其它的参数,会使用 arguments
用来替代 arguments,直接使用 rest
// 剩余参数: rest parameters
function 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)
// 注意事项: 剩余参数需要写到其他的参数最后
Pure
函数式编程中有一个非常重要的概念叫纯函数,JavaScript 符合函数式编程的范式,所以也有纯函数的概念
纯函数的维基百科定义
总结:确定的输入一定产生确定的输出;函数在执行过程中,不能产生副作用
函数副作用
依赖于外部变量
在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响,比如修改了全局变量,修改参数或者改变外部的存储
// 不是一个纯函数
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) {
}
举例
作用和优势
React 中就要求无论是函数还是 class 声明一个组件,这个组件都必须像纯函数一样,保护它们的 props 不被修改
纯函数的好处
柯里化也是属于函数式编程里面一个非常重要的概念
维基百科的解释
// 因为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)©
优势
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
}
// 第一步对数字*2
function double(num) {
return num * 2
}
// 第二步对数字**2
function 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.length
if (length <= 0) return
for (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
}
}
var obj = {
message: "Hello World"
}
with (obj) {
console.log(message)
}
内建函数 eval 允许执行一个代码字符串
不建议在开发中使用 eval
JavaScript 历史的局限性
在 ECMAScript5 标准中,JavaScript 提出了严格模式的概念(Strict Mode)
严格模式对正常的 JavaScript 语义进行了一些限制
<script>
// 给整个script开启严格模式
"use strict"
// 粒度化的控制
// 给一个函数开启严格模式
function foo() {
"use strict"
}
//默认就是在严格模式
class Person {
}
</script>
无法意外地创建全局变量
严格模式会使引起静默失败(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.name
console.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对象
// 在严格模式下, 不绑定全局对象而是undefined
foo()
默认情况下属性都是没有特别的限制
可以对对象中的属性进行某些限制,使用的不多
对一个属性进行比较精准的控制操作,使用属性描述符
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
三个参数
返回值:返回被修改后的对象
var obj = {
name: "lili", // configurable: true
age: 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.name
console.log(obj)
// 通过Object.defineProperty添加一个新的属性
Object.defineProperty(obj, "address", {})
delete obj.address
console.log(obj)
console.log(Object.keys(obj))
obj.name = "kobe"
console.log(obj.name)
Configurable 和 Enumerable 方法
get:获取属性时会执行的函数,默认为 undefined,会监听读取的过程
set:function(value){log(“哈哈”,value)}
// 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)
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: {
}
})
获取对象的属性描述符
禁止对象扩展新属性:preventExtensions
密封对象,不允许配置和删除属性:seal
冻结对象,不允许修改现有属性: freeze
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.name
console.log(obj)
// 4.冻结对象(不能进行写入)
Object.freeze(obj)
obj.name = "kobe"
console.log(obj)