gzl的博客

  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

RN-《JavaScript忍者秘籍》(上)

发表于 2019-12-07 更新于 2020-02-06 分类于 读书笔记

第一章 无处不在的 JavaScript

性能分析,可以使用 console.time 和 console.timeEnd 在 node 和 浏览器环境下可以看到运行的时间

1
2
3
4
5
6
7
8
9
console.time("My operation");

for(let i = 0; i < 100000; i ++) {
// execute opeation
}

console.timeEnd("My operation");

// My operation: 2.27099609375ms

这本书集中探讨了核心 JavaScript 的机制,例如函数、函数闭包和原型,还有一些新的 JavaScript 特性,例如生成器、promise、代理、映射、集合和模块。

第二章 运行时的页面构建过程

事件

客户端 Web 应用的两个生命周期是页面构建和事件处理

页面构建

当Web应用能被展示或交互之前,其页面必须根据服务器获取的响应(通常是HTML、CSS和JavaScript代码)来构建。页面构建阶段的目标是建立Web应用的UI,其主要包括两个步骤:

  1. 解析HTML代码并构建文档对象模型(DOM);
  2. 执行JavaScript代码。

步骤1会在浏览器处理HTML节点的过程中执行,步骤二会在HTML解析到一种特殊节点——脚本节点(包含或引用JavaScript代码的节点)时执行。页面构建阶段中,只要还有没处理完的HTML元素和没执行完的JavaScript代码,两个步骤都会一直交替执行。

当浏览器处理完所有HTML元素后,页面构建阶段就结束了。随后浏览器就会进入应用生命周期的第二部分:事件处理。

事件处理

客户端 Web 应用是一种 GUI 应用,也就是说这种应用会对不同类型的事件作响应,如鼠标移动、单击和键盘按压等。因此,在页面构建阶段执行的JavaScript代码,除了会影响全局应用状态和修改DOM外,还会注册事件监听器(或处理器)。这类监听器会在事件发生时,由浏览器调用执行。有了这些事件处理器,我们的应用也就有了交互能力。

事件可能会以难以预计的时间和顺序发生,如以下的事件。

  • 网络事件,例如来自服务器的响应(Ajax 事件和服务器端事件)
  • 用户事件,例如鼠标单击、鼠标移动和键盘事件
  • 计时器事件,当 timeout 时间到期或又触发了一次时间间隔

这里就是熟悉的 EventLoop,不再详述,与第十三章结合起来。个人认为第二章是从宏观上看,第十三章是从微观上看。

第三章 函数课

回调函数定义

1
2
3
document.body.addEventListener('click', function () {
console.log(123)
})

上面是一个典型的回调函数,是异步的。但回调函数不是异步函数,是其他代码会在随后的某个合适时间点 “回过来调用” 的函数,下面的代码就是一个同步的回调函数。这里的概念要搞清楚。

1
2
3
4
5
6
7
8
9
function example(cb) {
cb()
}

function cb() {
console.log(123)
}

example(cb)

函数作为对象的乐趣

存储函数

比如下面的代码,利用函数也可以拥有属性,可以存储唯一函数集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var store = {
nextId: 1,
cache: {},
add: function (fn) {
if (!fn.id) { // 检测fn有没有id这个属性
fn.id = this.nextId++;
this.cache[fn.id] = fn;
return true;
}
}
};

function ninja() {}

store.add(ninja)
console.log(store.cache)
// { '1': { [Function: ninja] id: 1 } } node环境下,key为1,value为函数,函数的属性id为1
store.add(ninja)
console.log(store.cache)
// { '1': { [Function: ninja] id: 1 } }

自记忆函数

利用函数也可以拥有属性实现简单的素数判断,如果已经有结果,就不用计算了,直接取出缓存的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function isPrime(value) {
if (!isPrime.answers) {
isPrime.answers = {};
}

if (isPrime.answers[value] !== undefined) {
return isPrime.answers[value];
}

var prime = value !== 1;

for (var i = 2; i < value; i++) {
if (value % i === 0) {
prime = false;
break;
}
}

return isPrime.answers[value] = prime;
}

console.log(isPrime(5)) // true
console.log(isPrime(5)) // true 第二次就不用经过for循环运算了

第四章 函数进阶

构造函数返回值

执行下面的代码,会看到如果将 Ninja 作为一个函数调用,的确会返回 1,但如果通过 new 关键字将其作为构造函数调用,会构造并返回一个新的 ninja 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Ninja() {
this.skulk = function () {
return true;
};

return 1;
}

console.log(Ninja() === 1) // true

var ninja = new Ninja();

console.log(ninja) // Ninja { skulk: [Function] }

console.log(typeof ninja) // object

console.log(typeof ninja.skulk) // function

但如果做一些改变,一个构造函数返回另一个对象,如下面代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var puppet = {
rules: false
};

function Emperor() {
this.rules = true;
return puppet;
}

var emperor = new Emperor();

console.log(emperor === puppet); // true

console.log(emperor.rules); // false

结果表明:puppet 对象最终作为构造函数调用的返回值,在构造函数中对函数上下文的操作都是无效的。最终返回的将是 puppet。

总结:

  • 如果构造函数返回一个对象,则该对象将作为整个表达式的值返回,而传入构造函数的this将被丢弃。
  • 如果构造函数返回的是非对象类型,则忽略返回值,返回新创建的对象。

函数的命名

函数和方法的命名通常以描述其行为的动词开头(doSomethingWonderful),且第一个字母小写。而构造函数则通常以描述所构造对象的名词命名,并以大写字母开头:Ninja

箭头函数的this

箭头函数没有单独的 this 值,箭头函数的 this 与声明所在的上下文的相同,在创建时就确定了 this 的指向。

在构造函数内部时

1
2
3
4
5
6
7
8
9
10
11
12
13
<button id="test">Click Me!</button>
<script>
function Button() {
this.clicked = false;
this.click = () => {
this.clicked = true;
console.log(button.clicked) // true
}
}
var button = new Button();
var elem = document.getElementById("test");
elem.addEventListener("click", button.click);
</script>

如上所示,button.clicked 为 true。

调用箭头函数时,不会隐式传入 this 参数,而是从定义时的函数继承上下文,在上面的代码中,箭头函数在构造函数内部,this 指向新创建的对象本身,因此无论何时调用 click 函数,this 都将指向新创建的 button 对象。

使用对象字面量时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<button id="test">Click Me!</button>
<script type="text/javascript">
console.log(this === window) // true
var button = {
clicked: false,
click: () => {
this.clicked = true;
console.log(button.clicked) // false
console.log(this); // window
console.log(window.clicked) // true
}
}

var elem = document.getElementById("test");
elem.addEventListener("click", button.click);
</script>

click 箭头函数是作为对象字面量的属性定义的,对象字面量在全局代码中定义,因此,箭头函数内部的 this 值与全局代码的 this 值相同。

第五章 闭包

闭包是 JavaScript 作用域规则的副作用。当函数创建时所在的作用域消失后,仍然能够调用函数。

第六章 未来的函数:生成器和promise

对迭代器进行迭代

用 while 循环迭代生成器的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 定义一个生成器
function* WeaponGenerator() {
yield "Katana";
yield "Wakizashi";
}

const weaponsIterator = WeaponGenerator(); // 新建一个迭代器

var item; // 这个变量来保存生成器产生的值

// 每次循环从生成器中取出一个值,当生成器不再生成值的时候,停止迭代
while (!(item = weaponsIterator.next()).done) {
console.log(item.value)
}

// 依次打印
// Katana
// Wakizashi

上面的代码就是 for of 循环的原理。for of 循环不过是对迭代器进行迭代的语法糖。

1
2
3
for(var item of WeaponGenerator()) {
console.log(item)
}

不同于手动调用迭代器的 next 方法,for of 循环同时还要查看生成器是否完成,在后台自动做了相同的工作。

把执行权交给下一个生成器

for of 循环不会关心 WarriorGenerator 委托到另一个生成器上,它只关心在 done 状态到来之前都一直调用 next 方法。

注意这里的 yield*

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function* WarriorGenerator() {
yield "Sun Tzu";
yield* NinjaGenerator();
yield "Genghis Khan";
}

function* NinjaGenerator() {
yield "Hatori";
yield "Yoshi";
}

for (let warrior of WarriorGenerator()) {
console.log(warrior)
}

// 依次打印
// Sun Tzu
// Hatori
// Yoshi
// Genghis Khan

用生成器生成ID序列

测试结果结尾 true,id 仅能在生成器中被访问,while 的每次迭代都能生成一个新的 id 值并挂起执行,直到下一次调用 next 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function* IdGenerator() {
let id = 0;
while (true) {
yield ++id;
}
}

const idIterator = IdGenerator();

const ninja1 = {
id: idIterator.next().value
};
const ninja2 = {
id: idIterator.next().value
};
const ninja3 = {
id: idIterator.next().value
};

console.log(ninja1.id === 1)
console.log(ninja2.id === 2)
console.log(ninja3.id === 3)

迭代器遍历 DOM 树

一般的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div id="subTree">
<form>
<input type="text" />
</form>
<p>Paragraph</p>
<span>Span</span>
</div>

<script>
function traverseDOM(element, callback) {
callback(element);
element = element.firstElementChild;
while (element) {
traverseDOM(element, callback);
element = element.nextElementSibling;
}
}

const subTree = document.getElementById("subTree");
traverseDOM(subTree, function (element) {
console.log(element.nodeName)
});
</script>

使用生成器写法,不用再写回调函数了,一个 for of 循环就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div id="subTree">
<form>
<input type="text" />
</form>
<p>Paragraph</p>
<span>Span</span>
</div>

<script>
function* DomTraversal(element) {
yield element;
element = element.firstElementChild;
while (element) {
yield* DomTraversal(element);
element = element.nextElementSibling;
}
}

const subTree = document.getElementById("subTree");
for (let element of DomTraversal(subTree)) {
console.log(element.nodeName)
}
</script>

与生成器交互

注意第二个 next 传递了参数,imposter 随之改变,进行了交互,可以想象到和 promise 结合时,next 传递的参数可以改变 imposter,然后进行下一步的请求(这个请求需要用到 imposter)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* NinjaGenerator(action) {
const imposter = yield ("Hatori " + action);

console.log(imposter === "Hanzo") // 这里是关键

yield ("Yoshi (" + imposter + ") " + action);
}

const ninjaIterator = NinjaGenerator("skulk");

const result1 = ninjaIterator.next();
console.log(result1.value === "Hatori skulk")

const result2 = ninjaIterator.next("Hanzo"); // 这里进行了传参
console.log(result2.value === "Yoshi (Hanzo) skulk")

与promise结合

这只是个简单的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
async (function* () {
try {
const ninjas = yield getJSON("data/ninjas.json");
const missions = yield getJSON(ninjas[0].missionsUrl);
const missionDescription = yield getJSON(missions[0].detailsUrl);

console.log(ninjas !== null && missions !== null && missionDescription !== null)
} catch (e) {
console.log(e);
}
});

function async (generator) {
const iterator = generator();

function handle(iteratorResult) {
if (iteratorResult.done) {
return;
}

const iteratorValue = iteratorResult.value;

if (iteratorValue instanceof Promise) {
iteratorValue.then(res => handle(iterator.next(res)))
.catch(err => iterator.throw(err))
}
}

try {
handle(iterator.next());
} catch (e) {
iterator.throw(e);
}
}

第七章 面向对象与原型

理解原型

在 JavaScript 中,对象的原型属性是内置属性(使用标记[[prototype]]),无法直接访问。内置方法 Object.setPrototypeOf 需要传入两个对象作为参数,并将第二个对象设置为第一个对象的原型。

1
2
3
4
5
6
7
8
9
10
const xiaoming = {
speak: true
}
const xiaohong = {
eat: true
}

Object.setPrototypeOf(xiaoming, xiaohong)

console.log("eat" in xiaoming) // true

每个对象都可以有一个原型,每个对象的原型也可以拥有一个原型,以此类推,形成一个原型链,查找特定属性将会被委托在整个原型链上,只有当没有更多的原型可以进行查找时,才会停止查找。

constructor

下面的代码中,我们使用第一个实例对象的 constructor 属性创建第二个实例。验证表明第二个 Ninja 对象被创建成功,并且与第一个是完全不同的两个实例。

有趣的是利用 constructor,我们不需要访问原始构造函数就可以直接创建对象,即使原始构造函数已经不在作用域内。

1
2
3
4
5
6
function Ninja() {}
const ninja = new Ninja();
const ninja2 = new ninja.constructor();

console.log(ninja2 instanceof Ninja) // true
console.log(ninja !== ninja2) // true

JS深入prototype

instanceof

instanceof 操作符的真正语义:检测右边的函数原型是否存在于操作符左边的对象的原型链上

1
2
3
4
5
6
7
8
9
function Ninja() {}

const ninja = new Ninja();

console.log(ninja instanceof Ninja) // true

Ninja.prototype = {};

console.log(ninja instanceof Ninja) // false

使用关键字 class

class 是语法糖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Ninja {
constructor(name) {
this.name = name;
}

swingSword() {
return true;
}
}

const ninja = new Ninja("Yoshi");

console.log(ninja instanceof Ninja) // true
console.log(ninja.name === "Yoshi") // true
console.log(ninja.swingSword()) // true

上面的代码可以转换成下面的ES5代码:

1
2
3
4
5
6
7
function Ninja(name) {
this.name = name
}

Ninja.prototype.swingSword = function () {
return true;
}

静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Ninja {
constructor(name, level) {
this.name = name;
this.level = level;
}

swingSword() {
return true;
}

static compare(ninja1, ninja2) {
return ninja1.level - ninja2.level;
}
}

const ninja1 = new Ninja("Yoshi", 4);
const ninja2 = new Ninja("Hatori", 3);

console.log(ninja1.compare === undefined && ninja2.compare === undefined) // true

console.log(Ninja.compare(ninja1, ninja2) > 0) // true

console.log(Ninja.compare) // [Function: compare]

console.log(Ninja.swingSword === undefined) // true

通过 static 关键字定义了一个静态方法 compare,实例不可访问 compare 方法,而 Ninja 类可以访问 compare 方法。

ES6 之前要像下面这样实现 “静态” 方法:

1
2
function Ninja() {}
Ninja.compare = function(ninja1, ninja2) {}

class实现继承

super + extends

console.log 除了注释中的 false,其余全为 true。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Person {
constructor(name) {
this.name = name;
}

dance() {
return true;
}
}

class Ninja extends Person {
constructor(name, weapon) {
super(name);
this.weapon = weapon;
}

wieldWeapon() {
return true;
}
}

const person = new Person("Bob");

console.log(person instanceof Person)
console.log(person.dance())
console.log(person.name === "Bob")
console.log(person instanceof Ninja) // false
console.log(person.wieldWeapon === undefined)

const ninja = new Ninja("Yoshi", "Wakizashi");

console.log(ninja instanceof Ninja)
console.log(ninja.wieldWeapon())
console.log(ninja instanceof Person)
console.log(ninja.name === "Yoshi")
console.log(ninja.dance())

第八章 控制对象的访问

getter 和 setter

定义 getter 和 setter

如果一个属性具有 getter 和 setter 方法,访问该属性时将隐式调用 getter 方法,为该属性赋值时将隐式调用 setter 方法。

在对象字面量中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const ninjaCollection = {
ninjas: ["Yoshi", "Kuma", "Hattori"],
get firstNinja() {
return this.ninjas[0];
},
set firstNinja(value) {
this.ninjas[0] = value;
}
};

console.log(ninjaCollection.firstNinja === "Yoshi") // true

ninjaCollection.firstNinja = "Hachi";

console.log(ninjaCollection.firstNinja === "Hachi" && ninjaCollection.ninjas[0] === "Hachi") // true

在ES6的class中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class NinjaCollection {
constructor() {
this.ninjas = ["Yoshi", "Kuma", "Hattori"];
}
get firstNinja() {
return this.ninjas[0];
}
set firstNinja(value) {
this.ninjas[0] = value;
}
}

const ninjaCollection = new NinjaCollection();

console.log(ninjaCollection.firstNinja === "Yoshi") // true

ninjaCollection.firstNinja = "Hachi";

console.log(ninjaCollection.firstNinja === "Hachi" && ninjaCollection.ninjas[0] === "Hachi") // true

通过Object.defineProperty定义getter和setter

在第五章中我们已经知道 Javascript 没有私有对象属性。我们可以通过闭包模拟私有属性,通过定义变量和指定对象包含这些变量。由于对象字面量与类、getter和setter方法不是在同一个作用域中定义的,因此那些希望作为私有对象属性的变量是无法实现的。幸运的是,可以通过Object.defineProperty方法实现。

在第7章中我们看到Object.defineProperty方法可以用于定义新的属性,传入属性描述对象即可。属性描述对象可以包含get和set来定义getter和setter方法。我们使用这种特性重新编写上面的示例,来实现内置的getter和setter,控制私有对象属性的访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Ninja() {
let _skillLevel = 0;

Object.defineProperty(this, 'skillLevel', {
get: () => {
return _skillLevel;
},
set: value => {
_skillLevel = value;
}
});
}

const ninja = new Ninja();

console.log(ninja._skillLevel === undefined) // true

console.log(ninja.skillLevel === 0) // true

ninja.skillLevel = 10;

console.log(ninja.skillLevel === 10) // true

由于我们希望通过skillLevel属性控制访问私有变量,因此我们定义了set和get方法。

注意,与对象字面量和类中的getter和setter不同,通过Object.defineProperty创建的get和set方法,与私有skillLevel变量处于相同的作用域中。get和set方法分别创建了含有私有变量的闭包,我们只能通过get和set方法访问私有变量。

剩下的代码运行的效果与前面的示例一致。我们创建新的ninja实例,验证无法直接访问私有变量。所有的交互都必须通过getter和setter,与标准对象属性无差异。

正如你所看到的,Object.defineProperty方法比对象字面量或类更为复杂。但是,当我们需要实现私有对象属性时,Object.defineProperty方法派上了用场。

使用 getter 和 setter 校验属性值

下面这段代码展示了如何规避指定属性发生类型错误异常。

无论何时对skillLevel属性赋值,我们都会校验该值是否是整型。如果不是,则抛出异常,并且不会修改属性_skillLevel 的值。如果是整型,则对属性 _skillLevel 赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function Ninja() {
let _skillLevel = 0;

Object.defineProperty(this, 'skillLevel', {
get: () => _skillLevel,
set: (value) => {
if (!Number.isInteger(value)) {
throw new TypeError("Skill level should be a number");
}
_skillLevel = value;
}
});
}

const ninja = new Ninja();

ninja.skillLevel = 10;

console.log(ninja.skillLevel === 10) // true

try {
ninja.skillLevel = "Great";
} catch (e) {
console.log(e) // TypeError: Skill level should be a number
}

使用 getter 和 setter 定义计算属性值

下面的测试结果都是 true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const shogun = {
name: "Yoshiaki",
clan: "Ashikaga",
get fullTitle() {
return this.name + " " + this.clan;
},
set fullTitle(value) {
const segments = value.split(" ");
this.name = segments[0];
this.clan = segments[1];
}
};

console.log(shogun.name === "Yoshiaki" && shogun.clan === "Ashikaga")
console.log(shogun.fullTitle === "Yoshiaki Ashikaga")

shogun.fullTitle = "Ieyasu Tokugawa";

console.log(shogun.name === "Ieyasu" && shogun.clan === "Tokugawa")
console.log(shogun.fullTitle === "Ieyasu Tokugawa")

代理

使用代理控制访问

下面的测试结果都是 true

通过 emperor 直接访问 name 属性,则返回 Komei。但是,若通过代理对象访问,则隐式调用 get 方法。由于在目标对象上可以找到 name 属性,因此也会返回 Komei。

通过 emperor 直接访问不存在的属性 nickname,返回 undefined。但是如果通过代理对象访问不存在的属性 nickname,将会激活 get,由于目标对象不具有 nickname 属性,get 方法将会返回消息 Don’t bother the emperor!

要点:通过 Proxy 构造器创建代理对象,通过代理对象访问目标对象属性时要执行指定的操作(访问时 get 、赋值时 set)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const emperor = {
name: "Komei"
};
const representative = new Proxy(emperor, {
get: (target, key) => {
return key in target ? target[key] : "Don’t bother the emperor!"
},
set: (target, key, value) => {
target[key] = value;
}
});

console.log(emperor.name === "Komei")
console.log(representative.name === "Komei")
console.log(emperor.nickname === undefined)
console.log(representative.nickname === "Don’t bother the emperor!")

representative.nickname = "Tenno";

console.log(emperor.nickname === "Tenno")
console.log(representative.nickname === "Tenno")

使用代理记录日志

我们创建了 ninja 对象,将其传入 makeLoggable 函数,作为要创建的代理对象的目标对象,将代理对象重新赋值给 ninja 标识符。

这样做的好处就是不需要为每个对象属性添加单独的日志,记录日志的代码只需要写一个函数,解决了如果有很多个对象都要写单独日志的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function makeLoggable(target) {
return new Proxy(target, {
get: (target, property) => {
console.log(`Reading ${property}`);
return target[property];
},

set: (target, property, value) => {
console.log(`Writing value ${value} to ${property}`)
target[property] = value;
}
});
}

let ninja = {
name: "Yoshi"
};
ninja = makeLoggable(ninja);

console.log(ninja.name == "Yoshi")
ninja.weapon = "sword";

// 依次打印:
// Reading name
// true
// Writing value sword to weapon

代理的性能消耗

代理效率不高,所以在需要执行多次的代码中,比如 for 循环很多次,建议谨慎使用代理。

使用代理可以实现 日志记录、性能测量、数据校验、自动填充对象、数组负索引,这里只记录了日志记录。

第九章 处理集合

数组

访问

如果试图访问数组长度范围之外的索引,不会抛出异常,而是返回 undefined,这个结果表明,Javascript 的数组是对象。

1
2
3
let arr = [1, 2, 3]

console.log(arr[4]) // undefined

数组排序

array.sort((a, b) => a - b),JavaScript 提供了 sort 方法,我们需要提供回调函数,告诉排序算法相邻的两个数组元素的关系。可能的结果有如下几种。

  • 如果回调函数的返回值 < 0,元素 a 应该出现在元素 b 之前
  • 如果回调函数的返回值 = 0,元素 a 和元素 b 出现在相同位置
  • 如果回调函数的返回值 > 0,元素 a 应该出现在元素 b 之后

reduce

1
2
3
4
5
6
7
8
const numbers = [1, 2, 3, 4];
let sum = 0;

numbers.forEach(number => {
sum = sum + number;
})

console.log(sum)

对于数组的求和可以使用 reduce

1
2
3
4
5
6
const numbers = [1, 2, 3, 4];

const sum = numbers.reduce((aggregated, number) =>
aggregated + number, 0);

console.log(sum)

复用内置数组函数

比如下面的代码,就复用了数组中的 push,find 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<input id="first" />
<input id="second" />
<script>
const elems = {
length: 0,
add: function (elem) {
Array.prototype.push.call(this, elem);
},
gather: function (id) {
this.add(document.getElementById(id));
},
find: function (callback) {
return Array.prototype.find.call(this, callback);
}
};

elems.gather("first");
console.log(elems)

elems.gather("second");
console.log(elems)

const found = elems.find(elem => elem.id === "second");
console.log(found)
</script>

Map

why use Map

为什么要使用 Map,是因为对象字面量存在一些问题,比如下面访问 constructor 属性发现不为空,原因是 {} 的原型上存在 constructor 属性

1
2
3
const dictionary = {}

console.log(dictionary.constructor) // [Function: Object]

比如映射 HTML 节点,因为 {} 的 key 必须是字符串,如果想映射为其他类型,会默默转化为字符串,没有任何提示,比如下面试图使用 HTML 元素作为 key 时,其值被 toString 方法静默转换为字符串类型。HTML 元素转换为字符串后的值为 [object HTMLDivElement]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<div id="firstElement"></div>
<div id="secondElement"></div>
<script>
const firstElement = document.getElementById("firstElement");
const secondElement = document.getElementById("secondElement");

const map = {};

map[firstElement] = {
data: "firstElement"
};

console.log(map[firstElement].data === "firstElement") // true

map[secondElement] = {
data: "secondElement"
};

console.log(map[secondElement].data === "secondElement") // true
console.log(map[firstElement].data === "firstElement") // false
</script>

综上,由于这两个原因:原型继承属性以及key仅支持字符串,所以要用 Map 类型。

示例

firstLink 和 secondLink 的内容相同,但这两个对象仍然不相等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const map = new Map();
const currentLocation = location.href;

const firstLink = new URL(currentLocation);
const secondLink = new URL(currentLocation);

console.log(firstLink === secondLink) // false

map.set(firstLink, {
description: "firstLink"
});
map.set(secondLink, {
description: "secondLink"
});

console.log(map.get(firstLink).description === "firstLink") // true
console.log(map.get(secondLink).description === "secondLink") // true
console.log(map.size === 2) // true

遍历 Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const directory = new Map();

directory.set("Yoshi", "+81 26 6462");
directory.set("Kuma", "+81 52 2378 6462");
directory.set("Hiro", "+81 76 277 46");

for (let item of directory) {
console.log(item); // 数组形式 ["Yoshi", "+81 26 6462"]
}

for (let key of directory.keys()) {
console.log(key); // Yoshi
}

for (let value of directory.values()) {
console.log(value) // +81 26 6462
}

Set

并集

1
2
3
4
5
6
7
const ninjas = ["Kuma", "Hattori", "Yagyu"];
const samurai = ["Hattori", "Oda", "Tomoe"];

const warriors = new Set([...ninjas, ...samurai]);

console.log(warriors) // Set { 'Kuma', 'Hattori', 'Yagyu', 'Oda', 'Tomoe' }
console.log(warriors.size) // 5

交集

[…ninjas] 将 Set 转换为数组

1
2
3
4
5
6
7
8
9
const ninjas = new Set(["Kuma", "Hattori", "Yagyu"]);
const samurai = new Set(["Hattori", "Oda", "Tomoe"]);

const ninjaSamurais = new Set(
[...ninjas].filter(ninja => samurai.has(ninja))
);

console.log(ninjaSamurais) // Set { 'Hattori' }
console.log(ninjaSamurais.size) // 1

差集

1
2
3
4
5
6
7
8
9
const ninjas = new Set(["Kuma", "Hattori", "Yagyu"]);
const samurai = new Set(["Hattori", "Oda", "Tomoe"]);

const pureNinjas = new Set(
[...ninjas].filter(ninja => !samurai.has(ninja))
);

console.log(pureNinjas) // Set { 'Kuma', 'Yagyu' }
console.log(pureNinjas.size) // 2

笔记基本完成,除了第八章代理部分只记录了日志记录

RN-《JavaScript忍者秘籍》(下)

发表于 2019-12-03 更新于 2020-03-11 分类于 读书笔记

第十章 正则表达式

用到再看

第十一章 代码模块化

AMD

AMD 的设计明确基于浏览器,最流行的实现是 RequireJS,有以下几项优点:

  • 自动处理依赖,无需考虑模块引入的顺序
  • 异步加载模块,避免阻塞
  • 在同一个文件中可以定义多个模块

CommonJS

CommonJS 的设计是面向通用 JavaScript 环境。在 Node.js 社区具有最多的用户,不显示地支持浏览器。

CommonJS 使用基于文件的模块,文件同步加载,每个文件中只能定义一个模块。

CommonJS 在服务端更流行,因为模块加载相对更快,只需要读取文件系统,而在客户端则必须从远程服务器下载文件。

ES6 模块

ES6 模块结合了两者的有点:

  • 与 CommonJs 类似,ES6 模块语法相对简单,并且基于文件(每个文件就是一个模块)
  • 与 AMD 类似,ES6 模块支持异步模块加载

更多 JavaScript模块化

第十二章 DOM 操作

DOM 的特性和属性

当访问元素的特性值时,我们可以使用 DOM 方法: setAttribute getAttribute,或使用 DOM 对象上与之相对应的属性(div.id)。

下面的代码说明并非所有的元素特性都能被属性表示,自定义的特性并不能被元素属性表示,必须通过 setAttribute getAttribute 才能访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div data-a="a"></div>
<script>
document.addEventListener("DOMContentLoaded", () => {
const div = document.querySelector("div");

div.setAttribute("id", "ninja-1");

console.log(div.id === "ninja-1")
console.log(div.getAttribute('id') === "ninja-1")

div.id = "ninja-2";

console.log(div.id === "ninja-2")
console.log(div.getAttribute('id') === "ninja-2")

console.log(div['data-a']) // undefined
console.log(div.getAttribute('data-a')) // a
});
</script>

HTML5 中,建议使用 data- 作为自定义属性的前缀。

获取计算后的属性

元素的 style 属性并不包含它在样式表中继承的样式信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<style>
div {
font-size: 1.8em;
border: 0 solid gold;
}
</style>

<body>

<div style="color:#000;" title="Ninja power!">
忍者パワー
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
const div = document.querySelector("div");

console.log(div.style.color === 'rgb(0, 0, 0)' || div.style.color === '#000'); // true
console.log(div.style.fontSize === '1.8em'); // false
console.log(div.style.borderWidth === '0'); // false

div.style.borderWidth = "4px";

console.log(div.style.borderWidth === '4px'); // true
});
</script>
</body>

使用 window.getComputedStyle 获取计算后的值,最后的 border 是自己加的,在 computedStyles 中是没有的,但还是打印出了 1px solid rgb(220, 20, 60),正则表达式把驼峰转为中横线分隔,因为 getPropertyValue 只接受中横线分隔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<style type="text/css">
div {
background-color: #ffc;
display: inline;
font-size: 1.8em;
border: 1px solid crimson;
color: green;
}
</style>

<body>
<div style="color:crimson;" id="testSubject" title="Ninja power!">
忍者パワー
</div>
<script>
function fetchComputedStyle(element, property) {
// 获取计算样式的接口,保存在一个变量中稍后引用,这样可以提升性能
const computedStyles = window.getComputedStyle(element);
if (computedStyles) {
// 将驼峰转为中横线分隔
property = property.replace(/([A-Z])/g, '-$1').toLowerCase();
return computedStyles.getPropertyValue(property);
}
}
document.addEventListener("DOMContentLoaded", () => {
const div = document.querySelector("div");
console.log("background-color: " + fetchComputedStyle(div, 'background-color'));
console.log("display: " + fetchComputedStyle(div, 'display'));
console.log("font-size: " + fetchComputedStyle(div, 'fontSize'));
console.log("color: " + fetchComputedStyle(div, 'color'));
console.log("border-top-color: " + fetchComputedStyle(div, 'borderTopColor'));
console.log("border-top-width: " + fetchComputedStyle(div, 'border-top-width'));
console.log("border: " + fetchComputedStyle(div, 'border')); // 自己加的
});
</script>
</body>

测量元素的高度和宽度

height 和 width 这样的 style 属性造成了另外一个特殊问题,在不指定值的情况下,它们的默认值是 auto,以便让元素的大小根据其内容进行决定。因此,除非显式提供特性字符串,不能使用 height 和 width 来获取准确的值的。

值得庆幸的是,offsetHeight和offsetWidth都提供了这样的功能:可以相当可靠地访问实际元素的高度和宽度。但是请注意,这两个属性的值都包含了元素的padding值。

然而,需要当心的是,在高度交互的网站中,元素的隐藏(display值设置为none时),可能会花一些时间,而且一个元素如果不显示的话,它就没有尺寸。在非显示元素上,尝试获取offsetWidth或offsetHeight属性值,结果都是0。

对于这样的隐藏元素,如果需要获取它在非隐藏状态时的尺寸,我们可以使用一个技巧,暂时取消元素的隐藏,然后获取值,然后再将其隐藏。当然,我们希望这种做法不要在视觉上漏出破绽,而是在幕后操作。那如何才能将一个隐藏元素,在不可见的情况下编程不隐藏呢?

具体方法如下:

  1. 将display属性设置为block。
  2. 将visibility设置为hidden。
  3. 将position设置为absolute。
  4. 获取元素尺寸。
  5. 恢复先前更改的属性。

将display属性修改为block,可以让我们获取offsetHeight和offsetWidth的真实值,但元素会变成可见。为了使元素不可见,我们将visibility属性设置为hidden。但是这种做法会导致在元素的位置上显示一片空白,所以我们需要将position属性设置为absolute,以便将元素移出正常的可视区。

检查offsetWidth和offsetHeight属性值是否为0,可以非常有效地确定一个元素的可见性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<style type="text/css">
div {
width: 100px;
height: 100px;
display: none;
background: red;
}
</style>

<div id="testSubject">
忍者
</div>
<script>
(function () {
const PROPERTIES = {
position: "absolute",
visibility: "hidden",
display: "block"
};
window.getDimensions = element => {
const previous = {};
for (let key in PROPERTIES) {
previous[key] = element.style[key];
element.style[key] = PROPERTIES[key];
}

const result = {
width: element.offsetWidth,
height: element.offsetHeight
};

for (let key in PROPERTIES) {
element.style[key] = previous[key];
}
return result;
};
})();

const testSubject = document.getElementById('testSubject');
console.log(testSubject.offsetWidth)
console.log(testSubject.offsetHeight)

setTimeout(() => { // 如果是图片的话,需要加上 setTimeout,因为网络请求原因
const after = getDimensions(testSubject)
console.log(after.width)
console.log(after.height)
}, 3000)

避免布局抖动

布局抖动的核心问题在于,每当我们修改DOM时,浏览器必须在读取任何布局信息之前先重新计算布局。这种对性能的损耗十分巨大。

避免布局抖动的一种方法,就是尽量使用不会导致浏览器重排的方式编写代码。

元素的尺寸之间不存在依赖关系时可以进行批量读取和写入,这样可以让浏览器进行批量修改DOM的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
const ninja = document.getElementById("ninja");
const samurai = document.getElementById("samurai");
const ronin = document.getElementById("ronin");

// 批量读取所有的布局属性
const ninjaWidth = ninja.clientWidth;
const samuraiWidth = samurai.clientWidth;
const roninWidth = ronin.clientWidth;

// 批次写入所有的布局属性
ninja.style.width = ninjaWidth/2 + "px";
samurai.style.width = samuraiWidth/2 + "px";
ronin.style.width = roninWidth/2 + "px";

布局抖动对于精简页面无需过分考虑,但是在开发复杂的Web应用程序时需要特别注意,特别是在移动设备上。因此最好能记住所有会引起布局抖动的方法和属性。

第十三章 历久弥新的事件

深入事件循环

宏任务、微任务和事件循环

宏任务(或通常称为任务)。

宏任务的例子很多,包括创建主文档对象、解析HTML、执行主线(或全局)JavaScript代码,更改当前URL以及各种事件,如页面加载、输入、网络事件和定时器事件。从浏览器的角度来看,宏任务代表一个个离散的、独立工作单元。运行完任务后,浏览器可以继续其他调度,如重新渲染页面的UI或执行垃圾回收。

而微任务是更小的任务。微任务更新应用程序的状态,但必须在浏览器任务继续执行其他任务之前执行,浏览器任务包括重新渲染页面的UI。微任务的案例包括promise回调函数、DOM发生变化等。微任务需要尽可能快地、通过异步方式执行,同时不能产生全新的微任务。微任务使得我们能够在重新渲染UI之前执行指定的行为,避免不必要的UI重绘,UI重绘会使应用程序的状态不连续。

事件循环基于两个基本原则:

  1. 一次处理一个任务。
  2. 一个任务开始后直到运行完成,不会被其他任务中断。

如图所示:

两个原则

事件循环通常至少需要两个事件队列:宏任务队列和微任务队列。

细节:

  • 全局来看,图中展示了在一次迭代中,事件循环将首先检查宏任务队列,如果宏任务等待,则立即开始执行宏任务。直到该任务运行完成(或者队列为空),事件循环将移动去处理微任务队列。如果有任务在该队列中等待,则事件循环将依次开始执行,完成一个后执行余下的微任务,直到队列中所有微任务执行完毕。注意处理宏任务和微任务队列之间的区别:单次循环迭代中,最多处理一个宏任务(其余的在队列中等待),而队列中的所有微任务都会被处理。

  • 当微任务队列处理完成并清空时,事件循环会检查是否需要更新UI渲染,如果是,则会重新渲染UI视图。至此,当前事件循环结束,之后将回到最初第一个环节,再次检查宏任务队列,并开启新一轮的事件循环。

  • 因为JavaScript基于单线程执行模型,所以这两类任务都是逐个执行的。当一个任务开始执行后,在完成前,中间不会被任何其他任务中断。除非浏览器决定中止执行该任务,例如,某个任务执行时间过长或内存占用过大。所有微任务会在下一次渲染之前执行完成,因为它们的目标是在渲染前更新应用程序状态。浏览器通常会尝试每秒渲染60次页面,以达到每秒60帧(60 fps)的速度。60fps通常是检验体验是否平滑流畅的标准,比方在动画里——这意味着浏览器会尝试在16ms内渲染一帧。需要注意图中所示的“更新渲染”是何时发生在事件循环内的,因为在页面渲染时,任何任务都无法再进行修改。这些设计和原则都意味着,如果想要实现平滑流畅的应用,我们是没有太多时间浪费在处理单个事件循环任务的。理想情况下,单个任务和该任务附属的所有微任务,都应在16ms内完成。

因此提醒我们注意事件处理函数的发生频率以及执行耗时。例如,处理鼠标移动(mouse-move)事件时应当特别小心。因为移动鼠标将导致大量的事件进入队列,因此在鼠标移动的处理函数中执行任何复杂操作都可能导致Web应用的糟糕体验。

仅含宏任务的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
<button id="firstButton">First button</button>
<button id="secondButton">Second button</button>
<script>
const firstButton = document.getElementById("firstButton");
const secondButton = document.getElementById("secondButton");
firstButton.addEventListener("click", function firstHandler(){
/*Some click handle code that runs for 8 ms*/
});
secondButton.addEventListener("click", function secondHandler(){
/*Click handle code that runs for 5ms*/
});
/*Code that runs for 15ms*/
</script>

代码需要发挥一些想象空间,避免添加不必要的聚合代码,要求读者想象以下内容。

主线程JavaScript代码执行时间需要15ms。第一个单击事件处理器需要运行8ms。第二个单击事件处理器需要运行5ms。假设一个手快的用户在代码执行后5ms时单击第一个按钮,随后在12ms时单击第二个按钮。

如图所示:

事件

由于JavaScript基于单线程执行模型,单击firstButton并不会立即执行对应的处理器。(记住,一个任务一旦开始执行,就不会被另一个任务中断)firstButton的事件处理器则进入任务队列,等待执行。当单击secondButton时发生类似的情况:对应的事件处理器进入队列,等待执行。注意,事件监测和添加任务是独立于事件循环的,尽管主线程仍在执行,仍然可以向队列添加任务。

本示例强调如果其他任务正在执行,那么事件则需要按顺序等待执行。例如,尽管在第12ms时单击secondButton,但是其对应的事件处理任务在第23ms时才开始执行。

同时含有宏任务和微任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<button id="firstButton">First button</button>
<button id="secondButton">Second button</button>
<script>
const firstButton = document.getElementById("firstButton");
const secondButton = document.getElementById("secondButton");
firstButton.addEventListener("click", function firstHandler(){
Promise.resolve().then(function(){
/*Some promise handling code that runs for 4 ms*/
});

/*Some click handle code that runs for 8 ms*/
});

secondButton.addEventListener("click", function secondHandler(){
/*Click handle code that runs for 5ms*/
});
/*Code that runs for 15ms*/
</script>

事件

与之前的示例唯一的区别是,在firstHandler代码中我们创建立即兑现的promise,并需要运行4ms的传入回调函数。因为promise表示当前未知的一个未来值,因此promise处理函数总是异步执行。

在本例中,我们创建立即兑现的promise。说实话,JavaScript引擎本应立即调用回调函数,因为我们已知promise成功兑现。但是,为了连续性,JavaScript引擎不会这么做,仍然会在firstHandler代码执行(需要运行8ms)完成之后再异步调用回调函数。通过创建微任务,将回调放入微任务队列。

除了宏任务队列之外,本例重点关注微任务队列,在第12ms时微任务队列仍为空。

处理计算复杂度高的任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<table><tbody></tbody></table>
<script>
"use strict";
const tbody = document.querySelector("tbody");
for (let i = 0; i < 60000; i++) {
let tr = document.createElement("tr");
for (let t = 0; t < 6; t++) {
const td = document.createElement("td");
td.appendChild(document.createTextNode(i + "," + t));
tr.appendChild(td);
}
tbody.appendChild(tr);
}
</script>

个人理解的就是创建了这么多的微任务(DOM操作),会使页面在渲染前等待的时间特别长。

上面的代码中,我们创建了240 000个DOM节点,创建一个20 000行、每行6列的表格,表格中的每个单元格都包含一个文本节点。这个操作的消耗是惊人的,会导致浏览器挂起一段时间,这段时间内用户无法正常操作(如同Bruce叔叔主宰了家庭聚会中的谈话)。

我们需要做的就是让Bruce叔叔定期闭嘴,这样其他人才有机会加入谈话。在代码中,我们可以引入定时器来创建这样的“中断谈话”。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<table><tbody></tbody></table>
<script>
"use strict";

const tbody = document.querySelector("tbody");
const rowCount = 60000;
const divideInto = 4;
const chunkSize = rowCount/divideInto;
let iteration = 0;

const table = document.getElementsByTagName("tbody")[0];
setTimeout(function generateRows(){
const base = chunkSize * iteration;
for (let i = 0; i < chunkSize; i++) {
const tr = document.createElement("tr");
for (let t = 0; t < 6; t++) {
const td = document.createElement("td");
td.appendChild(
document.createTextNode((i + base) + "," + t +
"," + iteration));
tr.appendChild(td);
}
table.appendChild(tr);
}
iteration++;
if (iteration < divideInto)
setTimeout(generateRows, 0);
},0);
</script>

令人印象深刻的是,可以使用异步的方法(setTimeout)来优化代码。在本例中,我们使用0作为超时时间。如果关注事件循环是如何工作的,就会知道这并不意味着将在0ms时执行回调。使用0,意味着通知浏览器尽快执行回调,但仍然必须在UI更新之后执行。

通过这种技术,从用户的角度可察觉的最显著的变化是,一个长时间的浏览器挂起,替代为4次(次数可修改)页面更新。尽管浏览器尝试尽可能快地执行代码片段,但仍然是依次执行DOM渲染。在这段代码的初始版本中,页面更新需要等待很长时间。

个人理解的就是借助setTimeout,将这么多的微任务分解成了四个宏任务,每一段宏任务后面有原来总量四分之一的微任务,这样浏览器渲染前等待的时间就会明显变少了。

事件

除了下面的记录,笔记基本完成

第十章 正则表达式用到再看

第十二章 DOM 注入 HTML ,闭合标签用到再看。

第十三章自定义事件用到再看

pipenv常用命令

发表于 2019-11-18 更新于 2019-12-04 分类于 python

官网:https://pipenv.kennethreitz.org/en/latest/

安装

1
pip install pipenv

查看版本

1
pipenv --version

第一个工程项目

1
2
mkdir myweb
cd myweb

初始化python3虚拟环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
myweb>pipenv --python 3
Creating a virtualenv for this project…
Pipfile: myweb\Pipfile
Using D:/python/python3.7.3/python.exe (3.7.3) to create virtualenv…
[== ] Creating virtual environment...Already using interpreter D:\python\python3.7.3\python.exe
Using base prefix 'D:\\python\\python3.7.3'
New python executable in C:\Users\主机名\.virtualenvs\myweb-hLv3G6Vy\Scripts\python.exe
Installing setuptools, pip, wheel...
done.
Running virtualenv with interpreter D:/python/python3.7.3/python.exe

Successfully created virtual environment!
Virtualenv location: C:\Users\主机名\.virtualenvs\myweb-hLv3G6Vy
Creating a Pipfile for this project…

下面是虚拟环境目录

1
C:\Users\主机名\.virtualenvs

查看虚拟环境目录

1
2
myweb>pipenv --venv
C:\Users\主机名\.virtualenvs\myweb-hLv3G6Vy

安装django

安装django(为什么这么慢……),如果没有上面的初始化python3虚拟环境,下面的pipenv install django也会先进行初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
myweb>pipenv install django
Installing django…
Adding django to Pipfile's [packages]…
Installation Succeeded
Pipfile.lock not found, creating…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Success!
Updated Pipfile.lock (4f9dd2)!
Installing dependencies from Pipfile.lock (4f9dd2)…
================================ 3/3 - 00:00:12
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.

To activate this project’s virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.

注意上面的两句话,也就是比如 pipenv run pip list 是查看当前虚拟环境安装的依赖,那么也可以先pipenv shell进入虚拟环境,再输入pip list。

激活虚拟环境

1
2
3
4
myweb>pipenv shell
Launching subshell in virtual environment…
Microsoft Windows [版本 10.0.18362.476]
(c) 2019 Microsoft Corporation。保留所有权利。

查看项目内的包

1
myweb>pipenv run pip list
1
2
3
4
5
6
7
8
9
(myweb-hLv3G6Vy) myweb>pip list
Package Version
---------- -------
Django 2.2.7
pip 19.3.1
pytz 2019.3
setuptools 41.6.0
sqlparse 0.3.0
wheel 0.33.6

退出虚拟环境

1
(myweb-hLv3G6Vy) myweb>exit

查看目前安装的库及其依赖关系

1
2
3
4
myweb>pipenv graph
Django==2.2.7
- pytz [required: Any, installed: 2019.3]
- sqlparse [required: Any, installed: 0.3.0]

安装开发专用包

1
myweb>pipenv install --dev requests

运行python文件

1
myweb>pipenv run python main.py

删除虚拟环境

在删除项目前要记得删除虚拟环境,前面有目录。

1
myweb>pipenv --rm

安装所有依赖

如果在有Pipfile的情况下,可以运行下面的命令安装相关的依赖

1
myweb1>pipenv install --dev # 如果不加--dev,就不能安装开发专用包

更换国内源

https://github.com/pypa/pipenv/blob/master/docs/advanced.rst

把下面的url改为清华源就可以,以后安装包就会很快。

1
2
3
4
5
6
7
8
9
10
11
12
[[source]]
name = "pypi"
url = "https://pypi.tuna.tsinghua.edu.cn/simple/"
verify_ssl = true

[dev-packages]

[packages]
requests = "*"

[requires]
python_version = "3.8"

定义执行脚本

在 Pipfile 下写入下面的内容,那么现在 pipenv run python main.py 直接可以简写成 pipenv run start,pipenv run list 就可以查看虚拟环境中安装的包。

1
2
3
4
[scripts]
start = "python main.py"
test = "pytest"
list = "pip list"

导入 requirements.txt

If you only have a requirements.txt file available when running pipenv install, pipenv will automatically import the contents of this file and create a Pipfile for you.

You can also specify $ pipenv install -r path/to/requirements.txt to import a requirements file.

上面的文档还是要读的仔细一点,如果已经有了 Pipfile 和 requirements.txt 的话,命令应该是 pipenv install -r requirements.txt ,如果没有 Pipfile 的话(即尚未初始化虚拟环境),可以使用 pipenv install。

生成 requirements.txt

1
2
3
4
5
6
# 生成requirements.txt文件 generate a requirements.txt out of it:
$ pipenv lock -r

# 生成dev-packages的requirements.txt文件
# If you wish to generate a requirements.txt with only the development requirements you can do that too!
$ pipenv lock -r -d
1…678…32

gzl

96 日志
14 分类
37 标签
© 2020 gzl
由 Hexo 强力驱动 v3.7.1
|
主题 – NexT.Pisces v7.2.0