学习笔记 2020-10-24
JavaScript 高级程序设计(第4版) 阅读记录
代理与反射
代理捕获器与反射方法
代理可以捕获 13 种不同的基本操作。这些操作有各自不同的反射 API 方法、参数、关联 ECMAScript 操作和不变式。
有几种不同的 JavaScript 操作会调用同一个捕获器处理程序。不过,对于在代理对象上执行的任何一种操作,只会有一个捕获处理程序被调用。
get()
get() 捕获器会在获取属性值的操作中被调用。对应的反射 API 方法为 Reflect.get() 。
返回值
返回值无限制。
拦截的操作
proxy.propertyproxy[property]Object.create(proxy)[property]Reflect.get(proxy, property, receiver)
捕获器处理程序参数
target目标对象property引用的目标对象上的字符串键参数receiver处理对象或继承代理对象的对象
捕获器不变式
如果
target.property不可写且不可配置,则处理程序返回的值必须与target.property匹配。如果
target.property不可配置且[[Get]]特性为undefined,处理程序也必须返回undefined。
set()
set() 捕获器会在设置属性值的操作中被调用。对应的反射 API 方法为 Reflect.set() 。
返回值
返回
true表示成功,返回false表示失败,严格模式下会抛出TypeError。拦截的操作
proxy.property = valueproxy[property] = valueObject.create(proxy)[property] = valueReflect.set(proxy, property, value, receiver)
捕获器处理程序参数
target目标对象。property引用的目标对象上的字符串键属性。value要赋给属性的值。receiver接收最初赋值的对象。
has()
has() 捕获器会在 in 操作符被调用。对应的反射 API 方法为 Reflect.has() 。
返回值
has()必须返回布尔值,表示属性是否存在。返回非布尔值会被转型为布尔值。拦截的操作
property in proxyproperty in Object.create(proxy)with(proxy) {( property ); }Reflect.has(proxy, property)
捕获器处理程序参数
target目标对象。property引用的目标对象上的字符串键属性。
捕获器不变式
如果
target.property存在且不可配置,则处理程序必须返回true。如果
target.property存在且目标对象不可拓展,则处理程序必须返回true。
defineProperty()
defineProperty() 捕获器会在 Object.defineProperty() 被调用。对应的反射 API 方法为 Reflect.defineProperty() 。
返回值
defineProperty()必须返回布尔值,表示属性是否成功定义。返回非布尔值会被转型为布尔值。拦截的操作
Object.defineProperty(proxy, property, descriptor)Reflect.defineProperty(proxy, property, descriptor)
捕获器处理程序参数
target目标对象。property引用的目标对象上的字符串键属性。descriptor包含可选的enumerable、configurable、writable、value、get和set定义的对象。
捕获器不变式
如果目标对象不可拓展,则无法定义属性。
如果目标对象有一个可配置的属性,则不能添加同名的不可配置属性。
如果目标对象有一个不可配置的属性,则不能添加同名的可配置属性。
getOwnPropertyDescriptor()
getOwnPropertyDescriptor() 捕获器会在 Object.getOwnPropertyDescriptor() 中被调用。对应的反射 API 方法为 Reflect.getOwnPropertyDscriptor() 。
返回值
getOwnPropertyDescriptor()必须返回对象,或者在属性不存在时返回undefined。拦截的操作
Object.getOwnpropertyDescriptor(proxy, property)Reflect.getOwnPropertyDescriptor(proxy, property)
捕获器处理程序参数
target目标对象。property引用的目标对象上的字符串键属性
捕获器不变式
如果自有的
target.property存在且不可配置,则处理程序必须返回一个表示该属性存在的对象。如果自有的
target.property存在且可配置,则处理程序必须返回表示该属性可配置的对象。如果
target.property存在且target不可拓展,则处理程序必须返回undefined表示该属性不存在。如果
target.property不存在,则处理程序不能返回表示该属性可配置的对象。
deleteProperty()
deleteProperty() 捕获器会在 delete 操作符中被调用。对应的反射 API 方法为 Reflect.deleteProperty() 。
返回值
deleteProperty()必须返回布尔值,表示删除属性是否成功。返回非布尔值会被转型为布尔值。拦截的操作
delete proxy.propertydelete proxy[property]Reflect.deleteProperty(proxy, property)
捕获器处理程序参数
target目标对象。property引用的目标对象上的字符串键属性。
捕获器不变式
如果自有的
target.property存在且不可配置,则处理程序不能删除这个属性。
ownKeys()
ownKeys() 捕获器会在 Object.keys() 及类似方法中被调用。对应的反射 API 方法为 Reflect.ownKeys() 。
返回值
ownKeys()必须返回包含字符串或符号的可枚举对象。拦截的操作
Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)Reflect.ownKeys(proxy)
捕获器处理程序参数
target目标对象
捕获器不变式
返回的可枚举对象必须包含
target的所有不可配置的自由属性。如果
target不可拓展,则返回可枚举对象必须准确地包含自有属性键。
getPrototypeOf()
getPrototypeOf() 捕获器会在 Object.getPrototypeOf() 中被调用。对应的反射 API 方法为 Reflect.getPrototypeOf() 。
返回值
getPrototypeOf()必须返回对象或null。拦截的操作
Object.getPrototypeOf(proxy)Reflect.getPrototypeOf(proxy)proxy.__proto__Object.prototype.isPrototypeOf(proxy)proxy instanceof object
捕获器处理程序参数
target目标对象。捕获器不变式
如果
target不可拓展,则Object.getPrototypeOf(proxy)唯一有效的返回值就是Object.getPrototypeOf(target)的返回值。
setPrototypeOf()
setPrototypeOf() 捕获器会在 Object.setPrototypeOf() 中被调用。对应的反射 API 方法为 Reflect.setPrototypeOf() 。
返回值
setPrototypeOf()必须返回布尔值,表示原型赋值是否成功。返回非布尔值会被转型为布尔值。拦截的操作
Object.setPrototypeOf(proxy)Reflect.setPrototypeOf(proxy)
捕获器处理程序参数
target目标对象。prototypetarget的替代原型,如果是顶级原型则为null。
捕获器不变式
如果
target不可拓展,则唯一有效的prototype参数就是Object.getPrototypeOf(target)的返回值。
isExtensible()
isExtensible() 捕获器会在 Object.isExtensible() 中被调用。对应的反射 API 方法为 Reflect.isExtensible() 。
返回值
isExtensible()必须返回布尔值,表示target是否可拓展。返回非布尔值会被转型为布尔值。拦截的操作
target.isExtensible(proxy)Reflect.isExtensible(proxy)
捕获器处理程序参数
target目标对象
捕获器不变式
如果
target可拓展,则处理程序必须返回true。如果
target不可拓展,则处理程序必须返回false。
preventExtensions()
preventExtensions() 捕获器会在 Object.preventExtensions() 中被调用。对应的反射 API 方法为 Reflect.preventExtensions() 。
返回值
preventExtensions()必须返回布尔值,表示target是否已经不可拓展。返回非布尔值会被转型为布尔值。拦截的操作
Object.preventExtensions(proxy)Reflect.preventExtensions(proxy)
捕获器处理程序参数
target目标对象
捕获器不变式
如果
Object.isExtensible(proxy)是false,则处理程序必须返回true。
apply()
apply() 捕获器会在调用函数时被调用。对应的反射 API 方法为 Reflect.apply() 。
返回值
返回值无限制
拦截的操作
proxy(...argumentsList)Function.prototype.apply(thisArg, argumentsList)Function.prototype.call(thisArg, ...argumentsList)Reflect.apply(target, thisArgument, argumentsList)
捕获器处理程序参数
target目标对象。thisArg调用函数时的this参数。argumentsList调用函数时的参数列表。
捕获器不变式
target必须是一个函数对象。
construct()
construct() 捕获器会在 new 操作符中被调用。对应的反射 API 方法为 Reflect.construct() 。
返回值
construct()必须返回一个对象。拦截的操作
new proxy(...argumentsList)Reflect.construct(target, argumentsList, newTarget)
捕获器处理程序参数
target目标构造函数。argumentsList传给目标构造函数的参数列表。newTarget最初被调用的构造函数。
捕获器不变式
target必须可以用作构造函数。
代理模式
跟踪属性访问
通过捕获 get 、set 和 has 等操作,可以知道对象属性什么时候被访问、被查询。把实现相应捕获器的某个对象代理放到应用中,可以监控这个对象何时在何处被访问过:
const user = {
name: 'Jake'
};
const proxy = new Proxy(user, {
get(target, property, receiver) {
console.log(`Getting ${property}`);
return Reflect.get(...arguments);
},
set(target, property, value, receiver) {
console.log(`Setting ${property}=${value}`);
return Reflect.set(...arguments);
}
});
proxy.name; // Getting name
proxy.age = 27; // Setting age=27
隐藏属性
代理的内部实现对外部代码是不可见的,因此要隐藏目标对象上的属性也轻而易举。
const hiddenProperties = ['foo', 'bar'];
const targetObject = {
foo: 1,
bar: 2,
baz: 3
};
const proxy = new Proxy(targetObject, {
get(target, property) {
if (hiddenProperties.includes(property)) {
return undefined;
} else {
return Reflect.get(...arguments);
}
},
has(target, property) {
if (hiddenProperties.includes(property)) {
return false;
} else {
return Reflect.has(...arguments);
}
}
});
// get()
console.log(proxy.foo); // undefined
console.log(proxy.bar); // undefined
console.log(proxy.baz); // 3
// has()
console.log('foo' in proxy); // false
console.log('bar' in proxy); // false
console.log('baz' in proxy); // true
属性验证
所有赋值操作都会触发 set() 捕获器,可以根据所赋值决定允许还是拒绝赋值。
const target = {
onlyNumbersGoHere: 0
};
const proxy = new Proxy(target, {
set(target, property, value) {
if (typeof value !== 'number') {
return false;
} else {
return Reflect.set(...arguments);
}
}
});
proxy.onlyNumbersGoHere = 1;
console.log(proxy.onlyNumbersGoHere); // 1
proxy.onlyNumbersGoHere = '2';
console.log(proxy.onlyNumbersGoHere); // 1
函数与构造函数参数验证
可以对函数和构造函数参数进行审查。
function median(...nums) {
return nums.sort()[Math.floor(nums.length / 2)];
}
const proxy = new Proxy(median, {
apply(target, thisArg, argumentsList) {
for (const arg of argumentsList) {
if (typeof arg !== 'number') {
throw 'Non-number argument provided';
}
}
return Reflect.apply(...arguments);
}
});
console.log(proxy(4, 7, 1)); // 4
console.log(proxy(4, '7', 1));
// Error: Non-number argument provided
class User {
constructor(id) {
this.id_ = id;
}
}
const proxy = new Proxy(User, {
construct(target, argumentsList, newTarget) {
if (argumentsList[0] === undefined) {
throw 'User cannot be instantiated without id';
} else {
return Reflect.construct(...arguments);
}
}
});
new proxy(1);
new proxy();
// Error: User cannot be instantiated without id
现代 JavaScript 教程
垃圾回收
可达性
JS 中主要的内存管理概念是 可达性。
当一个值以某种方式可访问或可用,都不能被释放。
MDN 学习记录
Web 字体
可以选择自定义一个 @font-face 块指定远程资源字体文件,然后使用该字体应用在页面中。
@font-face {
font-family: "myFont";
src: url("myFont.ttf");
}
html {
font-family: "myFont", "Bitstream Vera Serif", serif;
}
大多数现代浏览器都支持 WOFF / WOFF2 (Web Open Font Format versions 1 and 2,Web开放字体格式版本1和2) 字体。旧版本 IE 只支持 EOT (Embedded Open Type,嵌入式开放类型) 的字体。
获得字体的网站:
- 免费的字体经销商:这是一个可以下载免费字体的网站(可能还有一些许可条件,比如对字体创建者的信赖)。比如: Font Squirre,dafont 和 Everything Fonts。
- 收费的字体经销商:这是一个收费则字体可用的网站,例如fonts.com或myfonts.com。您也可以直接从字体铸造厂中购买字体,例如Linotype,Monotype 或 Exljbris。
- Google Fonts
生成所需代码
对于每种字体,遵循以下步骤:
- 确保已经满足了任何许可证的要求,如果打算在一个商业和/或Web项目中使用它。
- 前往 Fontsquirrel Webfont Generator.
- 使用上传字体按钮上传你的两个字体文件。
- 勾选复选框,“是的,我上传的字体符合网络嵌入的合法条件。
- 点击下载套件(kit)。
在生成器完成处理之后,得到一个ZIP文件,将它保存在与HTML和CSS相同的目录中。
CSS 布局
弹性盒子
当父元素设置 display: flex 时,所有子元素变成了 flex items ,按照父元素的 flex 初值进行排列。flex-direction 初值为 row 。所以子元素一般默认按行排列。 align-items 初值为 stretch ,所以子元素会被拉伸至最高的元素高度。
子元素设置 flex: 1 ,会使得所有子元素伸展并填充容器,不会留下空白。
grid 布局
grid 布局用于同时在两个维度上把元素按行按列排序整齐。弹性盒子用于设计横向或纵向的布局。
设置父元素的 display 值为 grid 即可进行 grid 布局。
可以使用 grid-template-rows 和 grid-template-columns 来定义行和列的轨道。
.wrapper {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 100px 100px;
grid-gap: 10px;
}
<div class="wrapper">
<div class="box1">One</div>
<div class="box2">Two</div>
<div class="box3">Three</div>
<div class="box4">Four</div>
<div class="box5">Five</div>
<div class="box6">Six</div>
</div>

也可以自定义子元素的 grid-column 和 grid-row 属性来设置子元素在哪里摆放。
.wrapper {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 100px 100px;
grid-gap: 10px;
}
.box1 {
grid-column: 2 / 4; /* 从第2列开始 到第4列结束 */
grid-row: 1; /* 第1行 */
}
.box2 {
grid-column: 1;
grid-row: 1 / 3;
}
.box3 {
grid-row: 2;
grid-column: 3;
}
<div class="wrapper">
<div class="box1">One</div>
<div class="box2">Two</div>
<div class="box3">Three</div>
</div>

浮动
浮动会使得元素从正常布局流中移除。其他的周围内容就会被这个浮动元素环绕。
float 的值:
leftrightnone默认值inherit
定位技术
static静态定位relative相对定位,相对于元素在正常文档流中的位置。absolute绝对定位,从正常文档流移除,相对于该元素最近的被定位祖先元素或是页面的html元素。fixed固定定位,相对于浏览器视口固定。sticky粘性定位,先保持静态定位,当它的相对视口位置(offset from the viewport)达到某一个预设值时,会变成固定定位。
表格布局
<form>
<p>First of all, tell us your name and age.</p>
<div>
<label for="fname">First name:</label>
<input type="text" id="fname">
</div>
<div>
<label for="lname">Last name:</label>
<input type="text" id="lname">
</div>
<div>
<label for="age">Age:</label>
<input type="text" id="age">
</div>
</form>
html {
font-family: sans-serif;
}
form {
display: table;
margin: 0 auto;
}
form div {
display: table-row;
}
form label, form input {
display: table-cell;
margin-bottom: 10px;
}
form label {
width: 200px;
padding-right: 5%;
text-align: right;
}
form input {
width: 300px;
}
form p {
display: table-caption;
caption-side: bottom;
width: 300px;
color: #999;
font-style: italic;
}

多列布局
多列布局可以实现把内容按列排序,就像文本在报纸上排列那样。
使用 column-count 属性来告诉浏览器需要把一个多列容器转成多少列。
使用 column-width 属性来告诉浏览器以至少某个宽度的尽可能多的列来填充容器。
<div class="container">
<h1>Multi-column layout</h1>
<p>Paragraph 1.</p>
<p>Paragraph 2.</p>
</div>
.container {
column-width: 200px;
}

正常布局流
正常布局流是一套在浏览器视口内放置、组织元素的系统。默认的,块级元素按照基于其父元素的书写顺序(默认值 horizontal-tb )的块流动方向放置。内联元素会在父级块元素的宽度内放置,溢出的文本或元素移到新的一行。
弹性盒子
行内元素设置为弹性盒子,可以设置 display 的值为 inline-flex 。
flex 模型
当元素表现为 flex 框时,它们沿着两个轴来布局:

- 主轴(main axis)是沿着 flex 元素放置的方向延伸的轴(比如页面上的横向的行、纵向的列)。该轴的开始和结束被称为 main start 和 main end。
- 交叉轴(cross axis)是垂直于 flex 元素放置方向的轴。该轴的开始和结束被称为 cross start 和 cross end。
- 设置了
display: flex的父元素(在本例中是section)被称之为 flex 容器(flex container)。 - 在 flex 容器中表现为柔性的盒子的元素被称之为 flex 项(flex item)(本例中是
article元素。
弹性盒子提供了 flex-direction 来指定主轴的方向。
rowcolumnrow-reversecolumn-reverse
换行
当弹性盒子子元素定宽或定高时,可能会导致子元素溢出父元素。
此时,可以设置父元素的 flex-wrap 属性为 wrap 来自动换行。
子元素设置 flex 值为宽度时,会使得子元素的宽度大于等于这个值,当父元素某一行有多余内容时,会自动伸展来填充空余内容。
flex-flow 缩写
flex-flow 是 flex-direction 和 flex-wrap 的缩写属性。
flex-direction: row;
flex-wrap: wrap;
/* === */
flex-flow: row wrap;
flex 项的动态尺寸
设置子元素的 flex: 1 。会使得子元素占用空间都相等。占用空间指在设置 padding 和 margin 之后剩余的空间。
可以设置子元素的 flex: 1 xxxpx 来设定最小宽度。
这会使得子元素首先分配最小宽度,剩余空间按照比例来分配。
flex 缩写与全写
flex 是三个值的缩写属性:
flex-growflex-shrinkflex-basis
水平和垂直对齐
align-items 控制 flex 项在交叉轴上的位置。
默认值是
stretch,使所有flex项沿着交叉轴的方向拉伸以填充父容器。如果父容器在交叉轴上没有固定宽度,则所有flex项将变得与最长的flex项一样长。可以设置
align-self在某个子元素上覆盖align-items的行为。
justify-content 控制 flex 项在主轴上的位置。
- 默认值是
flex-start,使得所有flex项位于主轴的开始处。 flex-end使得flex项位于结尾。center使得各项居中。space-around让各项沿着主轴均匀分布,任意一端都留一点空间。space-between类似于上一个,但不会在两端留空间。
flex 项排序
弹性盒子可以改变 flex 项的布局位置,而不影响到源顺序。
设置子元素的 order 属性即可。
order值默认为0。order值越大,在显示顺序中越靠后。- 相同
order值按源顺序显示。
网格布局
设置父元素的 display 为 grid 后默认创建一个只有一列的网格,网页布局不会马上发生变化。
设置 grid-template-columns 可以创建网格列的模板。
div {
display: grid;
grid-template-columns: 200px 200px 200px;
/* 创建了三列,每列200px */
}
fr 单位
fr 表示一个比例单位。分配父元素的可用空间。可用空间指的是除去那些确定被占用的空间外的空间。
网格间隙
grid-column-gap 定义列间隙。 grid-row-gap 定义行间隙。
grid-gap 同时设定两者。
grid- 前缀已经被修改,现在可以使用 gap 属性。
重复构建行/列
可以使用 repeat 来重复构建相同宽度配置的列。
.container {
display: grid;
grid-template-columns: repeat(3, 1fr); /* === 1fr 1fr 1fr; */
grid-gap: 20px;
}
显式网格与隐式网格
显式网格是使用 grid-template-columns 或 grid-template-rows 创建的。
隐式网格是当有内容放到网格外时才会生成。
隐式网格中生成的行/列大小是参数默认 auto ,大小会根据放入的内容自动调整。也可以使用 grid-auto-rows 和 grid-auto-columns 属性手动设定隐式网格的大小。
minmax()
minmax 函数为一个行/列的尺寸设置了取值范围。比如设定为 minmax(100px, auto),那么尺寸就至少为100像素,并且如果内容尺寸大于100像素则会根据内容自动调整。在这里试一下把 grid-auto-rows 属性设置为minmax函数。
自动使用多列填充
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
可以实现自动填充多列,auto-fill 自动确定重复次数,minmax 来确定列宽。
此时的子元素会至少大于 200px ,且自动填满父容器。
基于线的元素放置
grid-column-startgrid-column-endgrid-row-startgrid-row-end以上属性指定元素从哪条线放置到哪条线。属性值为分隔线序号。
也可以使用缩写形式:
grid-columngrid-row
序号之间需要使用 / 分开。
使用 grid-template-areas 放置元素
.container {
display: grid;
grid-template-areas: 'header header' 'sidebar content' 'footer footer';
grid-template-columns: 1fr 3fr;
grid-gap: 20px;
}
header {
grid-area: header;
}
article {
grid-area: content;
}
aside {
grid-area: sidebar;
}
footer {
grid-area: footer;
}

你需要填满网格的每个格子,对于横跨多个格子的元素,需要重复写该元素的区域名。每个区域名只能出现在一个连续的区域。一个连续的区域必须是一个矩形。使用 . 符号让格子留空。
手动实现网格系统
在过去,一般使用浮动来手动实现网格。给出外部容器的总宽度,使用 12 列布局,计算出每个网格的宽度,然后配置一个网格类。
但这样的网格系统宽度固定,若是想要实现流体网格,需要使用百分比单位。
将固定值除以预设的外部容器宽度得到百分比进行替换。同时给予外部容器 width 百分比与 max-width 预设值。
手动实现网格的方式无法控制跨越行,且难以控制元素的高度。