Iterator

概念

ES6在原有Array,Object增加了Map,Set,共有4种用于表示集合的数据解构

遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作

Iterator 的作用有三个:

  • 一是为各种数据结构,提供一个统一的、简便的访问接

  • 二是使得数据结构的成员能够按某种次序排列

  • 三是 ES6 创造了一种新的遍历命令for…of循环,Iterator 接口主要供for…of消费。

Iterator 的遍历过程是这样的。

  • 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

  • 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。

  • 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。

  • 不断调用指针对象的next方法,直到它指向数据结构的结束位置。

每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。

默认 Iterator 接口

当使用for…of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。

ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性.

一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)。

原生具备 Iterator 接口的数据结构如下。

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。不过,严格地说,对象部署遍历器接口并不是很必要,因为这时对象实际上被当作 Map 结构使用,ES5 没有 Map 结构,而 ES6 原生提供了。

调用场合

  • 解构赋值

  • 扩展运算符

  • yield*

数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口

  • for…of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()(比如new Map([[‘a’,1],[‘b’,2]]))
  • Promise.all()
  • Promise.race()

结合Generator函数

1
2
3
4
5
6
let obj = {
* [Symbol.iterator]() {
yield 'hello';
yield 'world';
}
};

遍历器对象的 return(),throw()

return()方法的使用场合是,如果for…of循环提前退出(通常是因为出错,或者有break语句),就会调用return()方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return()方法。

return()方法必须返回一个对象,这是 Generator 语法决定的。

throw()方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。

与其他遍历语法

forEach无法中途跳出

for...in循环有几个缺点。数组的键名是数字,但是for...in循环是以字符串作为键名“0”、“1”、“2”等等。

for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。某些情况下,for...in循环会以任意顺序遍历键名。

for...of有着同for…in一样的简洁语法,但是没有for…in那些缺点。不同于forEach方法,它可以与break、continue和return配合使用。提供了遍历所有数据结构的统一操作接口。

实现自己的 call(),apply(),bind(),new()

call()

第一步,改变 this 的指向, 当函数作为对象属性调用时,this 会指向对象,因此需要将参数包装成一个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Function.prototype.myCall = function (thisArg) {
let that = thisArg;
if (that === undefined || that === null) that = window;
if (typeof that === "number") that = new Number(that);
if (typeof that === "string") that = new String(that);
if (typeof that === "boolean") that = new Boolean(that);
if (typeof that === "bigint") that = new BigInt(that);
that.fn = this;
that.fn();
};

function a() {
console.log(this);
}
a.myCall(1);
a.myCall("2");

第二步,实现参数的传递,如果使用 ES6 可以使用剩余参数和扩展运算符

1
2
3
4
5
6
7
8
9
Function.prototype.myCall = function (thisArg, ...args) {
thisArg.fn = this;
thisArg.fn(...args);
};

function a(a, b, c) {
console.log(this, a, b, c);
}
a.myCall({ name: "name" }, 1, 2, 3);

在 ES5 中只用通过 eval() 实现,它可以执行一段字符串的脚本, 可以使用一个数组收集参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Function.prototype.myCall = function (thisArg) {
var that = thisArg;
if (that === undefined || that === null) that = global;
if (typeof that === "number") that = new Number(that);
if (typeof that === "string") that = new String(that);
if (typeof that === "boolean") that = new Boolean(that);
if (typeof that === "bigint") that = new BigInt(that);
if (typeof that === "symbol") that = new Symbol(that);
var args = [];

for (var i = 1; i < arguments.length; i++) {
args.push("arguments[" + i + "]");
}
that.fn = this;

// 这里args会进行隐式类型转换,调用toString方法
return eval("that.fn(" + args + ")");
};

function a(a, b, c) {
console.log(this, a, b, c);
return a + b + c;
}
console.log(a.myCall(null, 1, 2, 3));

第三步,消除副作用, 由于在执行前在 thisArg 上添加了,fn 方法所以需要清除,也可以使用 Symbol 或自定义的随机数,从创建唯一的标识

1
2
3
4
5
{
var result = eval("that.fn(" + args + ")");
delete that.fn;
return result;
}

apply()

apply 与 call 的区别只是参数的形式是数组,因此可以直接遍历参数,而不用使用 arguments

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Function.prototype.myApply = function (thisArg, args) {
var argsArr = [];
var result = void 0;
thisArg.fn = this;

if (!args) {
result = eval("that.fn()");
} else {
for (var i = 1; i < args.length; i++) {
argsArr.push("arguments[" + i + "]");
}
result = eval("that.fn(" + args + ")");
}

// 这里args会进行隐式类型转换,调用toString方法
delete that.fn;
return result;
};

bind()

第一步, 验证 this 必须是一个函数, 这是为了避免 bind 方法 this 被修改 fn.bind.call(null), 把 this 包装成新的函数并返回

1
2
3
4
5
6
7
8
9
Function.prototype.myBind = function (thisArg) {
if (typeof this !== "function")
throw new TypeError("Bind must be called on a function");
var boundTargetFunction = this;
var args =
return function boundFunction() {
return boundTargetFunction.apply(newThis);
};
};

第二步,处理参数,新函数调用时的参数需要和 bind 执行时的参数合并,共同传递给原函数

1
2
3
4
5
6
7
8
9
10
11
Function.prototype.myBind = function (thisArg) {
if (typeof this !== "function")
throw new TypeError("Bind must be called on a function");
var targetFunction = this;
var args = Array.prototype.slice.call(arguments, 1);
return function boundFunction() {
return targetFunction.apply(
args.concat(Array.prototype.slice.call(arguments, 1))
);
};
};

第三步,新函数需要保留原函数的原型方法,并区分调用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Function.prototype.myBind = function (thisArg) {
if (typeof this !== "function")
throw new TypeError("Bind must be called on a function");
var targetFunction = this;
var args = Array.prototype.slice.call(arguments, 1);
function boundFunction() {
return targetFunction.apply(
// 如果bind之后的函数用 new 调用,那么原函数中的 this 指向的应该是对象实例
this instanceof boundFunction
? this
: // 如果以普通函数调用,原函数this指向bind绑定时传入的值
thisArg,
args.concat(Array.prototype.slice.call(arguments, 1))
);
}
// 拷贝源函数的原型链
boundFunction.prototype = this.prototype;

return boundFunction;
};

new

new 操作接受一个函数

  • 创建一个新对象这个对象就是 this
  • 设置对象的原型为构造函数的原型
  • 将函数的执行上下文添加进 this
  • 执行函数,如果返回值是一个对象就直接返回
  • 如果不是对象就返回创建的 this
1
2
3
4
5
6
7
8
9
function myNew(Constructor, ...args) {
const newObj = {};

Object.setPrototypeOf(newObj, Constructor.prototype);

let result = Constructor.apply(newObj, args);

return result instanceof Object ? result : newObj;
}

1.函数式

面向对象编程(OO)通过封装变化使得代码更易理解。

函数式编程(FP)通过最小化变化使得代码更易理解。

函数式思想

JavaScript是一种拥有很多共享状态的动态语言,用不了多久,代码就会积累足够的复杂性。面向对象的编程思想可以解决一部分问。

我们需要的是一个可以对数据处理,并能处理交互,IO的编程范式,函数式编程在处理数据流转很有帮助,下一步我们希望深入函数式编程的理念,能让其处理异步交互,并且解决代码的复杂性

在应用设计时应该考虑一下几点:

  • 可扩展性——我是否需要不断地重构代码来支持额外的功能?
  • 易模块化——如果我更改了一个文件,另一个文件会不会受到影响?
  • 可重用性——是否有很多重复的代码?
  • 可测性——给这些函数添加单元测试是否让我纠结?
  • 易推理性——我写的代码是否非结构化严重并难以推理?

使用Javascript语言的其中一个问题是,缺乏一些能够妥当管理状态的原生解构,需要开发人员自身把控。随着复杂度的增高,变得难以控制。

声明式编程

命令式编程是很具体的告诉计算机如何执行某个任务,而声明式编程是将程序的描述和求值分离。

副作用和纯函数

纯函数

  • 仅取决于提供的输入,而不依赖于任何在函数求值期间或调用间隔时可能变化的隐藏状态和外部状态。

  • 不会造成超出其作用域的变化,例如修改全局对象或引用传递的参数。

副作用的发生情况

  • 改变一个全局的变量、属性或数据结构。this的使用容易发生问题。

  • 改变一个函数参数的原始值。

  • 处理用户输入。

  • 抛出一个异常,除非它又被当前函数捕获了。

  • 屏幕打印或记录日志。

  • 查询 HTML 文档、浏览器的 cookie或访问数据库。

引用透明和可置换性

引用透明使得开发者可以用这种系统的甚至是数理的方法来推导程序

1
2
3
increment();
increment();
print(counter);//引用不透明,调用期间如果被修改,会影响结果
1
2
const plus = run(increment,increment);
print(run(0))//总是为初始值加2

存储不可变数据

JavaScript 开发人员面临的问题都是由大量使用严重依赖外部共享变量的、存在太多分支的以及没有清晰的结构大函数所造成的。

即便是一些由很多文件组成并执行得很成功的应用,也会形成一种共享的可变全局数据网,难以跟踪和调试。

函数式编程的优点

  • 促使将任务分解成简单的函数。

  • 使用流式的调用链来处理数据。

  • 通过响应式范式降低事件驱动代码的复杂性。

复杂异步应用中的响应

总结

  • 使用纯函数的代码绝不会更改或破坏全局状态,有助于提高代码的可测试性和可维护性。

  • 函数式编程采用声明式的风格,易于推理。这提高了应用程序的整体可读性,通过使用组合和lambda表达式使代码更加精简。

  • 集合中的数据元素处理可以通过链接如map和reduce这样的函数来实现。

  • 函数式编程将函数视为积木,通过一等高阶函数来提高代码的模块化和可重用性。

  • 可以利用响应式编程组合各个函数来降低事件驱动程序的复杂性。

函数

与解构赋值默认值结合使用

参数mustBeProvided的默认值等于throwIfMissing函数的运行结果(注意函数名throwIfMissing之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行。如果参数已经赋值,默认值中的函数就不会运行。

1
2
3
4
5
6
7
8
9
10
function throwIfMissing() {
throw new Error('Missing parameter');
}

function foo(mustBeProvided = throwIfMissing()) {
return mustBeProvided;
}

foo()
// Error: Missing parameter

函数的 length 属性

没有默认值的时候,函数的length属性是形式参数的个数

指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。

这是因为length属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,后文的 rest 参数也不会计入length属性。

1
(function(...args) {}).length // 0

如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。

1
2
(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1

作用域

一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域

1
2
3
4
5
6
7
8
9
var x = 1;
function foo(x, y = function() { x = 2; }) {
var x = 3;
y();
console.log(x);
}

foo() // 3
x // 1

上面代码中,函数foo的参数形成一个单独作用域。这个作用域里面,首先声明了变量x,然后声明了变量y,y的默认值是一个匿名函数。这个匿名函数内部的变量x,指向同一个作用域的第一个参数x。函数foo内部又声明了一个内部变量x,该变量与第一个参数x由于不是同一个作用域,所以不是同一个变量,因此执行y后,内部变量x和外部全局变量x的值都没变。

如果将var x = 3的var去除,函数foo的内部变量x就指向第一个参数x,与匿名函数内部的x是一致的,所以最后输出的就是2,而外层的全局变量x依然不受影响。

1
2
3
4
5
6
7
8
9
var x = 1;
function foo(x, y = function() { x = 2; }) {
x = 3;
y();
console.log(x);
}

foo() // 2
x // 1

rest

rest 参数之后不能再有其他参数

函数的length属性,不包括 rest 参数

严格模式

ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。

name

函数的name属性,返回该函数的函数名。

Function构造函数返回的函数实例,name属性的值为anonymous。

1
(new Function).name // "anonymous"
1
2
3
4
function foo() {};
foo.bind({}).name // "bound foo"

(function(){}).bind({}).name // "bound "

箭头函数

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

1
2
let foo = () => { a: 1 };
foo() // undefined

上面代码中,原始意图是返回一个对象{ a: 1 },但是由于引擎认为大括号是代码块,所以执行了一行语句a: 1。这时,a可以被解释为语句的标签,因此实际执行的语句是1;,然后函数就结束了,没有返回值。

如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。

1
let fn = () => void doesNotReturn();

箭头函数注意点

  • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

  • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

  • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

不适用的场景

  • 定义对象的方法,且该方法内部包括this

  • 需要动态this的时候,也不应使用箭头函数

1
2
3
4
var button = document.getElementById('press');
button.addEventListener('click', () => {
this.classList.toggle('on');
});

尾调用优化

尾调用(Tail Call)是函数式编程的一个重要概念,就是指某个函数的最后一步是调用另一个函数。

即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。

只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。

目前只有 Safari 浏览器支持尾调用优化,Chrome 和 Firefox 都不支持。

尾递归

“尾调用优化”对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6 亦是如此,第一次明确规定,所有 ECMAScript 的实现,都必须部署“尾调用优化”。这就是说,ES6 中只要使用尾递归,就不会发生栈溢出(或者层层递归造成的超时),相对节省内存。

1
2
3
4
5
function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
if( n <= 1 ) {return ac2};

return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}

尾递归的两种形式:

柯里化(currying)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function currying(fn, n) {
return function (m) {
return fn.call(this, m, n);
};
}

function tailFactorial(n, total) {
if (n === 1) return total;
return tailFactorial(n - 1, n * total);
}

const factorial = currying(tailFactorial, 1);

factorial(5) // 120

ES6 的函数默认值

1
2
3
4
5
6
function factorial(n, total = 1) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}

factorial(5) // 120

严格模式

ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。

这是因为在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈。

  • func.arguments:返回调用时函数的参数。

  • func.caller:返回调用当前函数的那个函数。

尾调用优化发生时,函数的调用栈会改写,因此上面两个变量就会失真。严格模式禁用这两个变量,所以尾调用模式仅在严格模式下生效。

1
2
3
4
5
6
function restricted() {
'use strict';
restricted.caller; // 报错
restricted.arguments; // 报错
}
restricted();

尾递归的实现

tco函数是尾递归优化的实现,它的奥妙就在于状态变量active
默认情况下,这个变量是不激活的。一旦进入尾递归优化的过程,这个变量就激活了。然后,每一轮递归sum返回的都是undefined,所以就避免了递归执行;而accumulated数组存放每一轮sum执行的参数,总是有值的,这就保证了accumulator函数内部的while循环总是会执行。这样就很巧妙地将“递归”改成了“循环”,而后一轮的参数会取代前一轮的参数,保证了调用栈只有一层。

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
function tco(f) {
var value; //记录返回值
var args = []; //记录参数
var active = false; //记录执行状态
// 通过返回新函数,形成闭包共享状态
return function accumulator() {
//每次目标函数执行前保存参数
args.push(arguments);
//上一次执行的状态
if (!active) {
active = true;
// 因为每次执行前缓存了参数,所以下一次执行时一定会有参数
// 通过while实现递归的效果
while (args.length) {
value = f.apply(this, args.shift());
}
active = false;
}
}
}

var sum = tco(function (x, y) {
if (y > 0) {
return sum(x + 1, y - 1)
} else {
console.log(x);
return x
}
});

函数参数的尾逗号

ES2017 允许函数的最后一个参数有尾逗号(trailing comma)。函数参数与数组和对象的尾逗号规则,保持一致了。

其他命令

查找文件

find 命令功能非常强大,通常用来在 特定的目录下 搜索 符合条件的文件

序号 命令 作用
01 find [ 路径 ] -name “*.py” 查找指定路径下扩展名是 .py 的文件,包括子目录

如果省略路径,表示在当前文件夹下查找

之前学习的通配符,在使用 find 命令时同时可用

软链接

序号 命令 作用
01 ln -s 被链接的源文件 链接文件(名称) 建立文件的软链接,用通俗的方式讲类似于 Windows 下的快捷方式

注意:

  1. 没有 -s 选项建立的是一个 硬链接文件
  2. 两个文件占用相同大小的硬盘空间,工作中几乎不会建立文件的硬链接
  3. 源文件要使用绝对路径 ,不能使用相对路径,这样可以方便移动链接文件后,仍然能够正常使用

打包/解压

tar 是 Linux 中最常用的 备份 工具,此命令可以 把一系列文件 打包到 一个大文件中 ,也可以把一个 打包的大文件恢复成一系列文件

选项 含义
c 生成档案文件,创建打包文件
x 解开档案文件
v 列出归档解档的详细过程,显示进度
f 指定档案文件名称, f 后面一定是 .tar 文件,所以必须放选项最后

注意:f 选项必须放在最后,其他选项顺序可以随意

打包

1
tar -cvf 打包名称.tar 被打包的文件/路径 ...

解包

1
tar -xvf 打包文件.tar

解包到指定路径

1
tar -xvf [文件名].tar -C  /path

压缩

  • tar 与 gzip 命令结合可以使用实现文件 打包和压缩

  • tar 只负责打包文件,但不压缩

  • 用 gzip 压缩 tar 打包后的文件,其扩展名一般用 xxx.tar.gz

在 Linux 中,最常见的压缩文件格式就是 xxx.tar.gz

  • 在 tar 命令中有一个选项 -z 可以调用 gzip ,从而可以方便的实现压缩和解压缩的功能

压缩文件

1
tar -zcvf 打包文件.tar.gz 被压缩的文件/路径 ...

解压文件

1
tar -zxvf 打包文件.tar.gz

解压缩到指定路径

1
tar -zxvf 打包文件.tar.gz -C 目标路径 
选项 含义
-C 解压缩到指定目录,注意:要解压缩的目录必须存在

bzip2(two)

  • tar 与 bzip2 命令结合可以使用实现文件 打包和压缩 (用法和 gzip 一样)

  • tar 只负责打包文件,但不压缩,

  • 用 bzip2 压缩 tar 打包后的文件,其扩展名一般用 xxx.tar.bz2

  • 在 tar 命令中有一个选项 -j 可以调用 bzip2 ,从而可以方便的实现压缩和解压缩的功能

压缩

1
tar -jcvf 打包文件.tar.bz2 被压缩的文件/路径 ...

解压

1
tar -jxvf 打包文件.tar.bz2

软件安装

  • apt 是 Advanced Packaging Tool ,是 Linux 下的一款安装包管理工具

  • 可以在终端中方便的 安装 /卸载 / 更新软件包

安装软件

1
sudo apt install 软件包

卸载软件

1
sudo apt remove 软件名

升级软件

1
sudo apt upgrade

如果希望在 ubuntu 中安装软件,更加快速 ,可以通过设置镜像源 ,选择一个访问网速更快的服务器,来提供软件下载/安装服务
提示:更换服务器之后,需要一个相对比较长时间的更新过程,需要耐心等待。更新完成后,再安装软件都会从新设置的服务器下载软件了

下载

1
wget 下载地址

Reflect

概念

Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。

修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 老写法
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// failure
}

// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}

Object操作都变成函数行为。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。

1
2
3
4
5
// 老写法
'assign' in Object // true

// 新写法
Reflect.has(Object, 'assign') // true

Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

Proxy

简介

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

可以理解成 Proxy 代理了点运算符,和赋值运算符

通用用法: ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。

1
var object = { proxy: new Proxy(target, handler) };

proxy对象是obj对象的原型,obj对象本身并没有time属性,所以根据原型链,会在proxy对象上读取该属性,导致被拦截。

1
2
3
4
5
6
7
8
var proxy = new Proxy({}, {
get: function(target, propKey) {
return 35;
}
});

let obj = Object.create(proxy);
obj.time // 35

get()

get 方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。

如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修改该属性,否则通过 Proxy 对象访问该属性会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const target = Object.defineProperties({}, {
foo: {
value: 123,
writable: false,
configurable: false
},
});

const handler = {
get(target, propKey) {
return 'abc';
}
};

const proxy = new Proxy(target, handler);

proxy.foo
// TypeError: Invariant check failed

set()

set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。

严格模式下,set代理如果没有返回true,就会报错。

has()

如果原对象不可配置或者禁止扩展,这时has()拦截会报错。

has()拦截只对in运算符生效,对for…in循环不生

1
2
3
4
5
6
7
8
9
10
var obj = { a: 10 };
Object.preventExtensions(obj);

var p = new Proxy(obj, {
has: function(target, prop) {
return false;
}
});

'a' in p // TypeError is thrown

construct()

construct()方法返回的必须是一个对象,否则会报错。

1
2
3
4
5
6
7
8
const p = new Proxy(function() {}, {
construct: function(target, argumentsList) {
return 1;
}
});

new p() // 报错
// Uncaught TypeError: 'construct' on proxy: trap returned non-object ('1')

由于construct()拦截的是构造函数,所以它的目标对象必须是函数,否则就会报错。

1
2
3
4
5
6
7
8
const p = new Proxy({}, {
construct: function(target, argumentsList) {
return {};
}
});

new p() // 报错
// Uncaught TypeError: p is not a constructor

construct()方法中的this指向的是handler,而不是实例对象。

1
2
3
4
5
6
7
8
9
const handler = {
construct: function(target, args) {
console.log(this === handler);
return new target(...args);
}
}

let p = new Proxy(function () {}, handler);
new p() // true

deleteProperty()

目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错。

系统信息

时间和日期

序号 命令 作用
01 date 查看系统时间
02 cal calendar 查看日历,-y 选项可以查看一年的日历

磁盘信息

序号 命令 作用
01 df -h disk free 显示磁盘剩余空间
02 du -h [目录名] disk usage 显示目录下的文件大小

-h 以人性化的方式显示文件大小

进程信息

所谓 进程,通俗地说就是 当前正在执行的一个程序

序号 命令 作用
01 ps aux process status 查看进程的详细状况
02 top 动态显示运行中的进程并且排序
03 kill [-9] 进程代号 终止指定代号的进程,-9 表示强行终止

ps 默认只会显示当前用户通过终端启动的应用程序

ps 选项说明

选项 含义
a 显示终端上的所有进程,包括其他用户的进程
u 显示进程的详细状态
x 显示没有控制终端的进程

提示:使用 kill 命令时,最好只终止由当前用户开启的进程,而不要终止 root 身份开启的进程,否则可能导致系统崩溃
要退出 top 可以直接输入 q

用户管理

创建用户/设置密码/删除用户

提示:创建用户 / 删除用户 / 修改其他用户密码 的终端命令都需要通过 sudo 执行

序号 命令 作用 说明
01 useradd -m -g 组 新建用户名 添加新用户 -m 自动建立用户家目录-g 指定用户所在的组,否则会建立一个和同名的组
02 passwd 用户名 设置用户密码 使用sudo运行,如果是普通用户,直接用 passwd 可以修改自己的账户密码
03 userdel -r 用户名 删除用户 -r 选项会自动删除用户家目录
04 cat /etc/passwd | grep 用户名 确认用户信息 新建用户后,用户信息会保存在 /etc/passwd 文件中

提示:

  • 创建用户时,如果忘记添加 -m 选项指定新用户的家目录,最简单的方法就是删除用户

  • 重新创建创建用户时,默认会创建一个和用户名同名的组名,用户信息保存在 /etc/passwd 文件中

查看用户信息

序号 命令 作用
01 id [用户名] 查看用户 UID 和 GID 信息,没写用户名默认是当前用户信息
02 who 查看当前所有登录的用户列表
03 whoami 查看当前登录用户的账户名

passwd 文件

  • /etc/passwd 文件存放的是用户的信息,由 6 个分号组成的 7 个信息,分别是
  1. 用户名
  2. 密码(x,表示加密的密码)
  3. UID(用户标识)
  4. GID(组标识)
  5. 用户全名或本地帐号
  6. 家目录
  7. 登录使用的 Shell,就是登录之后,使用的终端命令,ubuntu 默认是 dash

usermod

  • usermod 可以用来设置 用户 的 主组 / 附加组 和 登录 Shell

  • 主组:通常在新建用户时指定,在 etc/passwd 的第 4 列 GID 对应的组

  • 附加组:在 etc/group 中最后一列表示该组的用户列表,用于指定 用户的附加权限

提示:设置了用户的附加组之后,需要重新登录才能生效!

修改用户的主组(passwd 中的 GID)

1
usermod -g 组 用户名

修改用户的附加组

1
usermod -G 组 用户名

修改用户登录 Shell

由于新建用户默认使用的shell是dash,会导致windows下终端中没有文件高亮,不能使用方向键,所以要修改新建用户的默认shell

提示:设置了用户登录 Shell之后,需要重新登录才能生效!

1
usermod -s /bin/bash 用户名

which

/etc/passwd 是用于保存用户信息的文件

/usr/bin/passwd 是用于修改用户密码的程序

which 命令可以查看执行命令所在位置,例如:

1
which ls
1
2
which useradd
///usr/sbin/useradd

bin 和 sbin

  • 在 Linux 中,绝大多数可执行文件都是保存在 /bin、/sbin、/usr/bin、/usr/sbin

  • /bin(binary)是二进制执行文件目录,主要用于具体应用

  • /sbin(system binary)是系统管理员专用的二进制代码存放目录,主要用于系统管理

  • /usr/bin(user commands for applications)后期安装的一些软件

  • /usr/sbin(super user commands for applications)超级用户的一些管理程序

cd 这个终端命令是内置在系统内核中的,没有独立的文件,因此用 which 无法找到 cd 命令的位置

切换用户

序号 命令 作用 说明
01 su - 用户名 切换用户,并且切换目录 - 可以切换到用户家目录,否则保持位置不变
02 exit 退出当前登录账户
  • su 不接用户名,可以切换到 root,但是不推荐使用,因为不安全

  • exit 示意图如下:

  • Copyrights © 2015-2026 SunZhiqi

此时无声胜有声!

支付宝
微信