我个人在2022年9月份的笔记的笔记,不定期更新
具有相对特性的无依赖 absolute 绝对定位
请回答下面这个问题:一个绝对定位元素,没有任何 left/top/right/bottom 属性设置,并且其祖先元素全部都是非定位元素,其位置在哪里?
很多人都认为是在浏览器窗口左上方。实际上,还是当前位置,不是在浏览器左上方
这是关于 absolute 绝对定位最典型的错误认知。正是这种错误认知导致凡是使用absolute 绝对定位的地方,一定父容器 position:relative,同时 left/top 等属性定位,甚至必同时使用 z-index 属性设置层级。 在我看来,这是非常严重的认知和使用错误!
请牢记下面这句话:absolute 是非常独立的 CSS 属性值,其样式和行为表现不依赖其他任何 CSS 属性就可以成。
我们来看一个例子感受一下。下图左上角有一个紫色的盒子,请问如何布局?
想必很多人是这么实现的:
.father {
position: relative;
}
.shape {
position: absolute;
top: 0; left: 0;
}
如果你也是这么实现的,就要注意了,因为这说明你对 absolute 认知还是太浅薄了。实际上,只用下面一行 CSS 就足够了:
.shape {
position: absolute;
}
看到没?absolute 定位效果实现完全不需要父元素设置 position为 relative 或其他什么属性就可以实现,我把这种没有设置 left/top/right/bottom 属性值的绝对定位称为 “无依赖绝对定位”。
很多场景下,“无依赖绝对定位”要比使用 left/top 之类属性定位实用和强大很多,因 为其除了代码更简洁外,还有一个很棒的特性,就是“相对定位特性”。
明明 absolute 是‘绝对定位’的意思,怎么又扯到‘相对定位特性’了呢?没错,“无 依赖绝对定位”本质上就是“相对定位”,仅仅是不占据 CSS 流的尺寸空间而已。
看到这里,你可又突发奇想,如果我在最外面在嵌套一个相对定位的盒子,结果又如何呢?
.grand {
position: relative;
width: 500px;
height: 500px;
background-color: blue;
padding: 20px;
}
.wrapper {
width: 200px;
height: 200px;
border: 2px solid red;
}
.inner {
position: absolute;
width: 50px;
height: 50px;
background-color: blueviolet;
}
<div class="grand">
<div class="wrapper">
<div class="inner"></div>
</div>
</div>
你是不是认为就跑到grand的左上角去了?
实际上,它的位置并无任何变化。因为无依赖的绝对定位之和它的父元素绑定。
Node下对于ES Modules的支持
ES Modules,在Node.js v13.2.0以后的版本中可行
ES Modules的向下兼容
在 Node.js 环境中,ES Modules 向下兼容 CommonJS,因此用import方式可以引入 CommonJS 方式导出的 API,但是会以 default 方式引入。
因此以下写法:
// abc.js 这是一个 CommonJS 规范的模块
const a = 1;
const b = 2;
const c = () => a + b;
module.exports = {a, b, c};
它可以用ES Modules的import引入:
import abc from './test.js';
console.log(abc.a, abc.b, abc.c()); // 1 2 3
但是不能用
import {a, b, c} from './test.js';
因为 module.exports = {a, b, c} 相当于:
const abc = {a, b, c};
export default abc;
ES Modules与CommonJS的主要区别
ES Modules与CommonJS的主要有四个区别。
第一个区别前面也提到过,如果要在导出时使用别名,ES Modules要写成:
export {
a as d,
b as e,
c as f,
}
而对应的CommonJS的写法是:
module.exports = {
d: a,
e: b,
f: c,
}
第二个区别是,CommonJS在require文件的时候采用文件路径,并且可以忽略 .js 文件扩展名。也就是说,require(‘./ziyue.js’)也可以写成require(‘./ziyue’)。但是,ES Modules在import的时候采用的是 URL 规范就不能省略文件的扩展名,而必须写成完整的文件名import {ziyue} from ‘./ziyue.mjs’,.mjs的扩展名不能省略。
💡如果你使用 Babel 编译的方式将ES Modules编译成CommonJS,因为 Babel 自己做了处理,所以可以省略文件扩展名,但是根据规范还是应该保留文件扩展名。
第三个区别是,ES Modules的import和export都只能写在最外层,不能放在块级作用域或函数作用域中。比如:
if(condition) {
import {a} from './foo';
} else {
import {a} from './bar';
}
这样的写法,在ES Modules中是不被允许的。但是,像下面这样写在CommonJS中是被允许的:
let api;
if(condition) {
api = require('./foo');
} else {
api = require('./bar');
}
事实上,CommonJS 的 require 可以写在任何语句块中。
第四个区别是,require 是一个函数调用,路径是参数字符串,它可以动态拼接,比如:
const libPath = ENV.supportES6 ? './es6/' : './';
const myLib = require(`${libPath}mylib.js`);
但是ES Modules的import语句是不允许用动态路径的。
对于Node fs的一些补充
fs.readFile(path[, options], callback)
- 若不指定编码格式,则返回一个Buffer(以16进制输出,以二进制格式存储的文件流对象)
- 若options是字符串,则它表示的输出编码格式
fs.readFile('/etc/passwd', 'utf8', callback);
文件路径问题
开局一张图,没得说。
在 bullit-generator-js 文件下运行 node index.js肯定是一切正常。 那我如果上一层目录运行呢?
cd ..
E:\study\node> node .\bullit-generator-js\index.js
哦豁,报错了
这是因为,我们使用的相对路径./corpus/data.json是相对于脚本的运行目录(即,node执行脚本的目录),而不是脚本文件的目录。
所以当我们在bullshit_generator当前目录运行时,读取的文件路径是bulhit_generator目录下的/corpus/data.json,这没有问题。如果我们在上一级目录运行它时,读取的文件路径实际变成了../bulhit_generator目录下的/corpus/data.json,因为这个路径下文件不存在,这样就找不到文件了。
这也就意味着,如果使用相对路径./,我们在不同的目录下运行脚本命令, ./corpus/data.json实际上表示的是不同的文件路径。
要让这个命令在任何目录下运行都能正确找到文件,我们必须要修改路径的方式,从相对于脚本运行的目录改为相对于脚本文件的目录。
在commonjs规范下:
const path = resolve(__dirname, 'corpus/data.json')
const data = readFileSync(path, {encoding: 'utf-8'});
console.log(data);
现在我们使用的ES Module 规范,怎么搞?
import {readFileSync} from 'fs';
import {fileURLToPath} from 'url';
import {dirname, resolve} from 'path';
const url = import.meta.url; // 获取当前脚本文件的url
const path = resolve(dirname(fileURLToPath(url)), 'corpus/data.json');
const data = readFileSync(path, {encoding: 'utf-8'});
console.log(data);
url是 Node.js 的内置模块,用来解析 url 地址。fileURLToPath是这个模块的方法,可以将 url 转为文件路径。然后再通过内置模块path的dirname方法就可以取到当前 JS 文件目录。
如何获取命令行中用户输入的参数?
我们首先想到的肯定是process.argv。
第一种方法
function parseOptions(options = {}) {
const argv = process.argv;
for (let i = 2; i < argv.length; i++) {
const cmd = argv[i - 1];
const value = argv[i];
if (cmd === "--title") {
options.title = value;
} else if (cmd === "--min") {
options.min = Number(value);
} else if (cmd === "--max") {
options.max = Number(value);
}
}
return options;
}
方式二
内置的 process 模块无法方便地检查用户的输入,所以我们需要使用三方库 command-line-args 替代 process.argv,它不仅能获得用户的输入,还能检测用户的输入是否正确。
使用三方库 command-line-usage 输出帮助说明
import commandLineArgs from "command-line-args";
import commandLineUsage from "command-line-usage";
const optionDefinitions = [
{ name: "help" },
{ name: "title", alias: "t", type: String },
{ name: "min", type: Number },
{ name: "max", type: Number },
];
const options = commandLineArgs(optionDefinitions); // 获取命令行的输入
// 定义帮助的内容
const sections = [
{
header: "主题",
content: "for your test ",
},
{
header: "Options",
optionList: [
{
name: "title",
typeLabel: "{underline string}",
description: "文章的主题。",
},
{
name: "min",
typeLabel: "{underline number}",
description: "文章最小字数。",
},
{
name: "max",
typeLabel: "{underline number}",
description: "文章最大字数。",
},
],
},
];
const usage = commandLineUsage(sections); // 生成帮助文本
if ("help" in options) {
console.log(usage);
process.exit();
}
export { options };
关于JS中函数参数传递的几个题
基本数据作为函数参数
let a = 1;
function fna(a) {
a = 2;
console.log(a, "inner a");
}
fna(a);
console.log(a, "outter a");
毫无疑问、相当于拷贝一份,互不相干
引用数据作为函数参数
未改变指针指向的情况下,指向的是同一内存空间
let a = { x: 1 };
function fna(a) {
a.x = 2;
a.y = 2;
console.log(a, "inner a");
}
fna(a);
console.log(a, "outter a");
// { x: 2, y: 2 } inner a
// { x: 2, y: 2 } outter a
改变了指针指向的情况下,创建一个新的对象
let a = { x: 1 };
function fna(a) {
a = { b: 3 };
console.log(a, "inner a");
}
fna(a);
console.log(a, "outter a");
// { b: 3 } inner a
// { x: 1 } outter a
关于Node中ReadableStream的一些看法
- stream Node.js 流(stream):你需要知道的一切
推荐先看上文。
这些内置对象都实现了Stream类
如何用 stdin 和 readline 实现命令行交互?
用户的输入与监听
在命令行运行时,通过 process 模块的 stdin 对象可以获取用户输入
console.log("请输入一个要求和的整数,以0结束输入");
process.stdin.setEncoding("utf8");
let sum = 0;
process.stdin.on("readable", () => {
const chunk = process.stdin.read(); // 获取当前输入的字符,包含回车
const n = Number(chunk.slice(0, -1));
sum += n;
if (n === 0) process.stdin.emit("end");
process.stdin.read(); // 再调用一次,返回的是null,并继续监听
});
process.stdin.on("end", () => {
console.log(`求和结果是:${sum}`);
});
使用 readline 模块
用 process.stdin 实现命令行交互,需要在readable事件中多调一次process.stdin.read()方法,这看起来似乎很奇怪,代码的可读性不高。幸好,node.js 为我们提供了一个比 process.stdin 更好用的内置模块—— readline,它是专门用来实现可交互命令行的。
import readline from 'readline';
function question(rl, {text, value}) {
const q = `${text}(${value})\n`;
return new Promise((resolve) => {
rl.question(q, (answer) => {
resolve(answer || value);
});
});
}
export async function interact(questions) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const answers = [];
for(let i = 0; i < questions.length; i++) {
const q = questions[i];
const answer = await question(rl, q); // 等待问题的输入
answers.push(answer);
}
rl.close();
return answers;
}