qunzi0214 / blog Goto Github PK
View Code? Open in Web Editor NEWpersonal blog
personal blog
function foo() {
console.log(a); // 2
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();
如果javascript具有动态作用域,则应该在调用处获得变量 a 的值(3)。而javascript中的 this 的机制和动态作用域很像
实际上,谷歌维护的 Traceur 编译器会把块级作用域编译成 try...catch
(在es5或以下环境)
非官方标准
let (a = 2) {
console.log(a); // 2
}
console.log(a); // ReferenceError
class Person {
constructor(sex) {
this.sex = sex;
}
speak() {
console.log(`hi, i'm a person`);
}
}
class Rango extends Person {
constructor(sex, age, name) {
super(sex);
this.age = age;
this.name = name;
}
speak() {
super.speak();
console.log(`hi, i'm ${this.name}`);
}
}
const r = new Rango('male', 18, 'Rango');
r.speak();
本质上是 prototype 机制的语法糖,子类仍然不是父类的复制,而是委托。同时 super 的绑定机制存在问题(待测试)。
书中提倡正确包涵 this 机制(比如通过bind显式声明),而不是使用箭头函数等方式来混淆 this 绑定规则和词法作用域。个人觉得大同小异...
书中对于强行将 javascript 的对象委托机制实现为类风格提出了批评。考虑到目前绝大多数前端框架仍然会使用类风格来编写程序,此处存疑,还需要后续知识储备更完善后重新判断
在 Typescript 存在3个常用的基本类型:string
, number
, boolean
。它们的命名和 Javascript 中对一个变量使用 typeof
操作符获得的值相同
使用
string
number
boolean
来做类型声明而非通过String
Number
Boolean
,因为后三者是某些场景下特定的内置类型
想要指定一个数组的元素类型(比如 [1, 2, 3]
),可以使用以下两种语法:
number[]
Array<number>
需要注意,
[number]
是完全不同的东西,这种语法是用来声明元组的(确定元素数量与类型的数组)
在 Typescript 中存在一个特殊类型:any
,一旦某个变量被声明为 any
,Typescript 会在编译阶段放弃对它进行类型检查
这同时意味着,可以读写这个变量的任意属性、函数调用该变量、将任何类型的值复制给该变量或将该变量赋值给任意类型的其他变量
let obj: any = { x: 0 };
// None of the following lines of code will throw compiler errors.
// Using `any` disables all further type checking, and it is assumed
// you know the environment better than TypeScript.
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;
需要注意,一旦你没有指定某个变量的类型且 Typescript 无法通过上下文对其进行类型推论,编译器会默认该变量类型为 any。如果需要避免这种情况,可以设置
noImplicitAny
选项
如果通过 var
let
const
来声明一个变量,那么类型注释是可选的:
let myName: string = "Alice";
// No type annotation needed -- 'myName' inferred as type 'string'
let myName = "Alice";
在 Typescript 中,允许你同时指定函数参数以及返回值的类型:
// Parameter type annotation
function greet(name: string) {
console.log("Hello, " + name.toUpperCase() + "!!");
}
// Would be a runtime error if executed!
greet(42);
function getFavoriteNumber(): number {
return 26;
}
和变量类似,通常不需要对函数返回值进行类型注释,因为 Typescript 会根据 return
语句进行类型推论
匿名函数会有些不同,Typescript 会根据匿名函数如何被调用来决定此函数入参的类型:
// No type annotations here, but TypeScript can spot the bug
const names = ["Alice", "Bob", "Eve"];
// Contextual typing for function
names.forEach(function (s) {
console.log(s.toUppercase());
// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
// Contextual typing also applies to arrow functions
names.forEach((s) => {
console.log(s.toUppercase());
// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
对象类型是除基本类型之外最常见的类型,定义一个对象类型,只需简单的罗列出它的属性和属性类型:
// The parameter's type annotation is an object type
function printCoord(pt: { x: number; y: number }) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 3, y: 7 });
定义对象类型,使用
;
或,
分隔符都是合法的
可选属性:
function printName(obj: { first: string; last?: string }) {
// ...
}
// Both OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });
在 Javascript 中,如果在对象上访问一个不存在的属性,会得到值 undefined
而不是一个运行时报错。鉴于此,在使用可选属性时,需要考虑到 undefined
:
function printName(obj: { first: string; last?: string }) {
// Error - might crash if 'obj.last' wasn't provided!
console.log(obj.last.toUpperCase());
// Object is possibly 'undefined'.
if (obj.last !== undefined) {
// OK
console.log(obj.last.toUpperCase());
}
// A safe alternative using modern JavaScript syntax:
console.log(obj.last?.toUpperCase());
}
一种方式结合不同的类型是使用联合类型:
function printId(id: number | string) {
console.log("Your ID is: " + id);
}
// OK
printId(101);
// OK
printId("202");
// Error
printId({ myID: 22342 });
// Argument of type '{ myID: number; }' is not assignable to parameter of type 'string | number'.
// Type '{ myID: number; }' is not assignable to type 'number'.
当在 Typesccript 中使用联合类型,只有当联合类型的每个成员都拥有某个方法和属性时,才允许访问:
function printId(id: number | string) {
console.log(id.toUpperCase());
// Property 'toUpperCase' does not exist on type 'string | number'.
// Property 'toUpperCase' does not exist on type 'number'.
}
解决办法是通过代码 narrow(变窄)
联合类型。Narrowing
通常发生在 Typescript 能通过代码结构确定一个更特定的类型时:
function printId(id: number | string) {
if (typeof id === "string") {
// In this branch, id is of type 'string'
console.log(id.toUpperCase());
} else {
// Here, id is of type 'number'
console.log(id);
}
}
另一个例子是 Array.isArray
:
function welcomePeople(x: string[] | string) {
if (Array.isArray(x)) {
// Here: 'x' is 'string[]'
console.log("Hello, " + x.join(" and "));
} else {
// Here: 'x' is 'string'
console.log("Welcome lone traveler " + x);
}
}
如果联合类型中的成员同时具有某个方法或属性,不需要通过 Narrowing
也可以直接访问:
// Return type is inferred as number[] | string
function getFirstThree(x: number[] | string) {
return x.slice(0, 3);
}
类型别名仅仅是给希望多次使用的任意类型一个名字,语法如下:
type Point = {
x: number;
y: number;
};
// Exactly the same as the earlier example
function printCoord(pt: Point) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 100, y: 100 });
可以给任意类型赋予一个类型别名,不仅仅是对象类型。比如给联合类型赋予别名:
type ID = number | string;
需要注意,别名仅仅是别名,无法通过别名给同样的类型定义一个不同的、有区别的版本。
另一种方式给一个对象类型起名是通过接口定义:
interface Point {
x: number;
y: number;
}
function printCoord(pt: Point) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 100, y: 100 });
Type Aliases
和 Interfaces
大多数情况非常类似,主要区别是别名无法通过重复定义的方式来新增属性:
interface
interface Animal {
name: string
}
interface Bear extends Animal {
honey: boolean
}
const bear = getBear()
bear.name
bear.honey
type
(通过交集)type Animal = {
name: string
}
type Bear = Animal & {
honey: Boolean
}
const bear = getBear();
bear.name;
bear.honey;
interface
新增字段:interface Window {
title: string
}
interface Window {
ts: TypeScriptAPI
}
const src = 'const a = "Hello World"';
window.ts.transpileModule(src, {});
type
新增字段:type Window = {
title: string
}
type Window = {
ts: TypeScriptAPI
}
// Error: Duplicate identifier 'Window'.
某些情况下,你会比 Typescript 更了解某个值的类型信息。例如通过 document.getElementById
,Typescript 只知道会返回一个 HTMLElement
类型的值,但是你可能知道你的页面上会返回一个 HTMLCanvasElement
类型的值。
这种场景下,可以使用类型断言来指定一个更特定的类型:
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
和类型注释一样,类型断言会被编译器移除,不会影响代码运行时
另一种方式使用类型断言是通过尖括号语法( .tsx
文件不适用):
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
Typescript 仅允许通过类型断言将类型转换为更具体或更不具体的类型。该规则可防止出现“不可能”的强制转换:
const x = "hello" as number;
// Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
有时候,这条规则过于保守,禁止了一些更复杂但有效的强制转换。解决办法是通过两次类型断言(先断言为 any
或 unknown
):
const a = (expr as any) as T;
除了基本类型 string
和 number
,有时候需要指定某个更具体的字符串或数字
一种方式是 const
:
let changingString = "Hello World";
changingString = "Olá Mundo";
// Because `changingString` can represent any possible string, that
// is how TypeScript describes it in the type system
// let changingString: string
const constantString = "Hello World";
// Because `constantString` can only represent 1 possible string, it
// has a literal type representation
// const constantString: "Hello World"
鉴于通常一个可变的变量不会只持有一个值,以下这种方式不是很有价值:
let x: "hello" = "hello";
// OK
x = "hello";
// Type '"howdy"' is not assignable to type '"hello"'.
x = "howdy";
如果通过联合类型结合字面量类型,可以表达出一个非常有用的概念:例如函数只能接受某些确定的值
function printText(s: string, alignment: "left" | "right" | "center") {
// ...
}
printText("Hello, world", "left");
printText("G'day, mate", "centre");
// Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.
数字的字面量类型工作方式是一样的:
function compare(a: string, b: string): -1 | 0 | 1 {
return a === b ? 0 : a > b ? 1 : -1;
}
当然,也可以结合字面量类型和非字面量类型:
interface Options {
width: number;
}
function configure(x: Options | "auto") {
// ...
}
configure({ width: 100 });
configure("auto");
configure("automatic");
// Argument of type '"automatic"' is not assignable to parameter of type 'Options | "auto"'.
还有最后一种字面量类型:布尔值字面量类型,类型
boolean
本身就是布尔值字面量类型true | false
的别名
在定义一个对象时,Typescript 不会将该对象的属性推论为字面量类型,例如以下例子中 counter
被推论为 number
类型而不是 0
类型:
const obj = { counter: 0 };
if (someCondition) {
obj.counter = 1;
}
考虑如下场景,req.method
被推论为 string
,在 req
被初始化,和 handleRequest
被调用之间,该值可能会被重新赋予一个字符串,Typescript会认为这是个错误:
function handleRequest(url: string, method: "GET" | "POST")
const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);
// Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
解决办法如下:
// Change 1:
const req = { url: "https://example.com", method: "GET" as "GET" };
// Change 2
handleRequest(req.url, req.method as "GET");
Change 1 表示,
req.method
会保持持有字面量类型GET
,此字段被重新赋值GUESS
是不可能发生的(如果发生了编译会不通过)
Change 2 表示,我能确定因为某些原因,req.method
肯定持有值GET
as const
将整个对象所有属性转变为字面量类型:const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);
在 Javascript 中,有两个基本值 null
、undefined
代表缺省或未初始化,Typescript 中同样有两个名字一致相应的类型,这两种类型如何表现取决于 strictNullChecks
选项是否打开。
strictNullChecks
关闭时:null
、undefined
值可以正常访问,也可以用来赋予声明为任意类型的变量或属性。然而缺少了类型检查,这些值会导致大量bug,推荐打开 strictNullChecks
选项
strictNullChecks
打开时:当一个值是 null
或 undefined
,在使用这个值之前必须确保这个值拥有相应的属性或方法,类似于通过 Narrowing
来使用可选属性:
function doSomething(x: string | null) {
if (x === null) {
// do nothing
} else {
console.log("Hello, " + x.toUpperCase());
}
}
Non-null
类型断言操作符:
Typescript 中有一个特殊的语法,用来移除某个类型的 null
和 undefined
,即表示,此值不可能为 null
或 undefined
:
function liveDangerously(x?: number | null) {
// No error
console.log(x!.toFixed());
}
和其他类型断言一样,这段代码不会对运行时产生影响,因此在使用 !
操作符前,必须确保你知道这个值真的不可能是 null
或 undefined
枚举类型是对 Javascript 的一个扩展,允许给一组可以命名的值增加描述(太绕了,看例子),和其他 Typescript 特性不同,枚举类型是一个运行时的特性:
enum Color { Red, Blue, Green }
编译后的 Javascript :
var Color;
(function (Color) {
Color[Color["Red"] = 0] = "Red";
Color[Color["Blue"] = 1] = "Blue";
Color[Color["Green"] = 2] = "Green";
})(Color || (Color = {}));
bigint
:ES2020之后,推出的一个新基本类型,用来存储一个非常大的整数
// Creating a bigint via the BigInt function
const oneHundred: bigint = BigInt(100);
// Creating a BigInt via the literal syntax
const anotherHundred: bigint = 100n;
symbol
:通过 Symbol()
函数创建一个全局独一无二的引用类型:
const firstName = Symbol("name");
const secondName = Symbol("name");
if (firstName === secondName) {
// This condition will always return 'false' since the types 'typeof firstName' and 'typeof secondName' have no overlap.
// Can't ever happen
}
复习下字面量和构造初始化区别:boolean、string、number字面量与对应的内置对象不等同,null和undefined没有构造形式,而Object、Array、Function和RegExp无论使用字面量还是构造形式来声明,他们都是对象。同时,javascript在操作基本类型的时候自动将之转化为内置对象
可以用 typeof
操作符来查看值的类型
typeof
对 null
的处理有问题,但是这个bug由来已久,今后也未必会修复
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof 42 // 'number'
typeof '42' // 'string'
typeof {} // 'object'
typeof Symbol() // 'symbol'
typeof null // 'object'
函数和数组都是 object
的子类型。函数是可调用对象,不仅如此,函数同样可以拥有属性
typeof [1, 2, 3] // 'object'
typeof function a (b, c) {} // 'function'
a.length // 2 (参数的个数)
javascript中变量是没有类型的,只有值才有。变量在未持有值的时候是 undefined
。此时 typeof
返回 "undefined"
。而没有在作用域中声明的变量,是 undeclared
的(运行时会报错)。同时,typeof
有一个特殊的安全防范机制,可以使其对 undeclared
的变量进行操作时同样返回 undefined
而不报错,可以利用这个机制来处理变量是否存在的问题
var helper = (typeof doSomething !== 'undefined')
? doSomething
: function() { /* do something */ }
helper()
还有一种方式是,通过全局对象 window
属性访问的方式来避免报错,只不过这种方式不适用于多 javascript
环境,因为此时无法保证全局对象是 window
var helper = window.doSomething
? window.doSomething
: function() { /* do something */ }
helper()
var
关键字会导致变量提升的本质:javascript是在运行前极短的时间内被编译的,var a = 2
实际上是两个动作:编译器会在当前作用域声明变量a(如果之前没被声明过),紧接着运行时引擎在作用域内寻找该变量,如果能找到就对它进行赋值。
LHS
和 RHS
var a = 2 // 此时对变量a是LHS查询,即找到变量本身的容器
console.log(a) // 此时对变量a是RHS查询,即找到变量对应的值(引用)
function foo(a) {
console.log(a + b); // ReferenceError: 对b进行RHS报错
b = a; // 对b进行LHS会在全局创建一个b
}
foo(2);
// 严格模式下对未声明的变量进行 LHS 或 RHS 都会报错
ReferenceError 和作用域判别失败相关,而 TypeError 代表作用域判别成功了,但是对结果的操作是非法的
window.a
的方式访问那些被同名变量遮蔽的全局变量foo.bar.baz
只会查找到 foo
,后续会交给对象属性访问规则function foo(str, a) {
eval(str); // 严格模式下有自己的词法作用域
console.log(a, b);
}
var b = 2;
foo('var b = 3;', 1);
function foo(obj) {
with(obj) {
a = 2;
}
}
var obj1 = { a: 1 };
foo(obj1);
console.log(obj1.a); // 2
console.log(a); // ReferenceError
var obj2 = { b: 3 };
foo(obj2);
console.log(obj2.a); // undefined
console.log(a); // 2
// with会基于传入的对象生成一个全新的词法作用域,当obj2作为作用域时,其中没有a标识符,因此进行了正常的LHS查找,最终在全局创建了一个变量a
词法分析阶段基本能够知道全部的标识符在什么位置,从而可以预测以及优化运行时对它们的查找。因此欺骗词法会导致性能问题
如果function是声明中的第一个词,那么就是一个函数声明,否则是一个函数表达式。它们的区别是函数声明会将函数绑定在所在作用域中,而函数表达式会将函数绑定在自身的函数中。
arguments.callee
(递归或是事件监听器需要解绑)function process(data) {
// do something
}
{
let bigData = {};
process(bigData);
}
var btn = document.getElementById('btn');
btn.addEventListener('click', function click(e) {
// do something
}, false);
回调函数 click
形成了一个覆盖整个作用域的闭包,javascript引擎极有可能在 process
函数执行后还保留着
bigData
(?!)。如果使用块级作用域,可以告诉引擎不需要保留 bigData
了
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
var foo = (function CoolModule(id) {
function change() {
publicAPI.identify = identify2;
}
function identify1() {
console.log(id);
}
function identify2() {
console.log(id.toUpperCase());
}
var publicAPI = {
change: change,
identify: identify1,
};
return publicAPI;
})('some string');
foo.identify(); // some string
foo.change();
foo.identify; // SOME STRING
var MyModule = (function Manager() {
var modules = {};
function define(name, deps, impl) {
for (var i = 0; i < deps.length; i++) {
deps[i] = modules[deps[i]];
}
modules[name] = impl.apply(impl, deps);
}
function get(name) {
return modules[name];
}
return {
define: define,
get: get,
};
})();
MyModule.define('bar', [], function () {
function hello(who) {
return 'hello ' + who;
}
return {
hello: hello,
}
});
MyModule.define('foo', ['bar'], function (bar) {
function awesome() {
console.log(bar.hello('Rango').toUpperCase());
}
return {
awesome: awesome,
}
});
var foo = MyModule.get('foo');
foo.awesome(); // HELLO RANGO
TODO:等手上有闲置机器来折腾,先用macOS继续学习
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
示例 1:
输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4.
示例 2:
输入: n = 13
输出: 2
解释: 13 = 4 + 9.
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/perfect-squares
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
n
的所有平方数,存入数组 squaresNum
;'0000...' (长度等于squaresNum长度)
入队;n
终止循环;visited
中或计算后大于 n
,跳过。否则加入队列;var numSquares = function(n) {
const squaresNum = []; // 找出所有小于n的平方数
let i = 1;
while(i * i <= n) {
if(i * i === n) {
return 1;
}
squaresNum.push(i * i);
i++;
}
const visited = new Set();
const queue = [];
let countStr = new Array(squaresNum.length).fill(0).join('');
queue.push(countStr);
visited.add(countStr);
while(queue.length) {
const currentStr = queue.shift();
if(calculateSum(squaresNum, currentStr, n) === 0) {
countStr = currentStr;
break;
}
for(let i = 0; i < currentStr.length; i++) {
const nextStr = getNextStr(currentStr, i);
if(calculateSum(squaresNum, nextStr, n) <= 0 && (!visited.has(nextStr))) {
queue.push(nextStr);
visited.add(nextStr);
}
}
}
return countStr.split('').reduce((a, b) => parseInt(a) + parseInt(b));
};
// 计算总值和目标值关系 大于返回1, 等于返回0, 小于返回-1
function calculateSum(squaresNum, countStr, target) {
let sum = 0;
for(let i = 0; i < squaresNum.length; i++) {
sum += squaresNum[i] * parseInt(countStr[i]);
}
return sum === target ? 0 : sum > target ? 1 : -1;
}
// 生成下一轮字符串
function getNextStr(currentStr, index) {
const arr = currentStr.split('');
arr[index] = parseInt(arr[index]) + 1 + '';
return arr.join('');
}
问题:当传入n足够大时,每次出队组合对应的新组合太多,会超时
类似斐波那契数列优化后,用迭代代替递归计算第 n
位的数字
保存中间值以免反复计算
dp
为长度 n+1
的数组,并填充0;dp[0] = 0
对应了0是0个平方数的和,即0的最优解为0;i
时,先假设最优解 dp[i] = i (i个1的和)
;i
的平方数 k
, dp[i] = min(dp[i - k] + 1); ∀k∈小于等于i的平方数
;dp[i]
动态转移方程的理解:
对于正整数 i
,它的最优解肯定是之前某一整数(包括0)的最优解加上1(之前某一整数加一个平方数),遍历所有小于 i
的平方数 k
,找到最小的那个 dp[i - k]
,那么加上1就是 i
的最优解
var numSquares = function(n) {
if(n === 0) {
return 0;
}
const dp = new Array(n + 1).fill(0);
for(let i = 1; i <= n; i++) {
dp[i] = i; // 最坏的情况 3 = 1 + 1 + 1
for(let j = 1; i - j * j >= 0; j++) {
dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
}
}
return dp[n];
};
使用命令 ls -al
查看当前目录所有文件以及详细属性
$ ls -al
drwxr-xr-x 8 rango staff 256 11 18 14:36 .npm
文件权限 | 链接数 | 拥有者 | 用户组 | 大小(Bytes) | 最后修改时间 | 文件名/目录名 |
---|---|---|---|---|---|---|
drwxr-xr-x | 8 | rango | staff | 256 | 11 18 14:36 | .npm |
第一个字符代表文件类型:
d(directory)
目录-
常规文件l(link)
链接文件c(character)
设备文件里面串行端口设备(键盘、鼠标)b(block)
设备文件里面可供存储的周边设备(硬盘)s(socket)
数据接口文件p(pipe)
数据传送文件,FIFO(first in fisrt out)接下来的字符三个为一组,切均为 rwx
三个字母的组合,代表可读(read)、可写(write)、可执行(execute),如果显示为 -
,则代表不具备此权限
权限 | 文件 | 目录 |
---|---|---|
r(read) | 可读取此文件的实际内容 | 可读取此目录结构列表(不包含查看目录下文件详细属性和内容) |
w(write) | 可以编辑文件内容(但不包含删除) | 具有改动目录的权限: 建立新文件或目录 删除已经存在的文件或目录 将已经存在的目录或文件更名 移动该目录内文件和目录的位置 |
x(execute) | 可以执行该文件 | 能否进入该目录 |
chgrp
change group修改文件所属用户组,要被修改的组名必须要在 /etc/group
中存在,-R
选项代表递归修改目录下子目录及文件的用户组
$ chgrp [-R] groupname filename/dirname
chown
change owner修改文件拥有者,要被修改的用户必须要在 /etc/passwd
中存在,-R
选项代表递归修改目录下子目录及文件的拥有者。这条命令还可以一次性同时修改拥有者和用户组
$ chown [-R] username filename/dirname
$ chown [-R] username:groupname filename/dirname
chmod
修改权限使用数字的方式:
r
的值是4, w
的值是2, x
的值是1-rwxrw---x
对应的权限数字是 761
(owner = 4 + 3 + 1 = 7, group = 4 + 2 = 6, other = 1)$ chmod [-R] 777 filename/dirname #将该文件/目录权限设置为 -rwxrwxrwx
使用符号的方式:
u
user, g
group, o
others, a
all+
加入权限, -
减去权限, =
设置权限$ chmod u=rwx,go=rx filename/dirname #-rwxr-xr-x
$ chmod a+w filename/dirname #-rwxrwxrwx
可分享(shareable) | 不可分享(unshareable) | |
---|---|---|
不变(static) | /usr(软件存放处) /opt(第三方辅助软件) |
/etc(配置文件) /boot(启动与内核文件) |
可变动(variable) | /val/mail(用户邮箱) /var/spool/news(新闻组) |
/var/run(程序相关) /var/lock(程序相关) |
实际上,FHS针对目录仅定义出三层目录下面应该放置什么数据:
/(root,根目录)
:与启动系统有关/usr(unix software resource)
:与软件安装/执行有关/var(variable)
:与系统的运行过程有关/(root)
目录 | 应放置的文件内容 |
---|---|
/bin | 一般用户可以使用、普通的系统命令:cat、chmod、chown、chgrp、date、mv、mkdir、cp等 |
/sbin | root权限可以使用,启动、修复、还原系统等基本的系统命令:shutdown、reboot等 |
/boot | 启动时会用到的文件、Linux内核文件(vmlinuz) |
/dev | 设备、接口设备:/dev/null、/dev/zero、/dev/tty、/dev/sd*等 |
/etc | 系统配置文件:/etc/modprobe.d、/etc/passwd、/etc/issue等 不应该放入可执行文件。另外FHS建议应该存在如下目录: /etc/opt:第三方辅助软件 /opt 的配置文件 /etc/X11:与 X Window 有关的配置文件 /etc/sgml:与SGML格式有关的配置文件 /etc/xml:与XML格式有关的配置文件 |
/lib | 启动时、/bin、/sbin下面的命令会调用的函数库,FHS要求 /lib/modules 必须存在以放置驱动文件 |
/media | 可删除的设备,软盘、光盘等 |
/mnt | 用于暂时挂载某些额外设备 |
/opt | 第三方辅助软件目录,通常更加习惯放于 /usr/local 目录中 |
/run | 早期的FHS规定系统启动后产生的信息应该放入 /var/run,新版改为 /run |
/srv | 网络服务启动之后需要使用的数据目录,如WWW、FTP等 |
/tmp | 正在执行的程序暂时放置文件的地方,需要定期清理 |
/usr | 软件执行、安装相关(第二层FHS) |
/var | 变动性数据(第二层FHS) |
/home | 默认的系统家目录 |
/lib | 存放与 /lib 不同的二进制函数库 |
/root | 系统管理员的家目录 |
/lost+found | 使用ext2、ext3、ext4文件系统格式产生的目录,当文件系统发生错误时,将一些遗失的片段放入其中,使用xfs文件系统不会存在这个目录 |
/proc | 这个目录本身是一个虚拟文件系统,放置内存中的系统内核、进程信息、外接设备状态、网络状态等数据 |
/sys | 作用和 /proc 类似 |
/usr
目录 | 应放置的文件内容 |
---|---|
/usr/bin/ | CentOS 7 已经将全部的用户命令放置于此,/bin 链接此目录。另外,FHS要求此目录下不应该有子目录 |
/usr/lib/ | /lib 链接此目录 |
/usr/local/ | 系统管理员下载的软件建议安装在此目录,同时安装软件产生的执行文件会放入 /usr/local/sbin/ |
/usr/sbin/ | 新版本 /sbin 链接此目录,老版本用于和 /sbin 做一个区分,放入服务器软件程序 |
/usr/share/ | 只读的数据文件、可分享的文件 |
/usr/games/ | 与游戏相关的数据 |
/usr/include/ | c/c++ 等程序语言的头文件与包含文件,使用Tarball(*.tar.gz)方式安装某些程序时会用到里面许多文件 |
/usr/libexec/ | 一般用户不常用的执行文件或脚本 |
/usr/lib/ | /lib 链接此目录 |
/usr/src/ | 源代码放置处 |
/var
目录 | 应放置的文件内容 |
---|---|
/var/cache/ | 缓存目录 |
/var/lib/ | 程序执行中,需要使用的数据文件放置目录:MySQL的数据库放置于 /var/lib/mysql/ |
/var/lock/ | 某些设备或文件同时只能被一个程序使用,因此需要上锁。目前此目录挪至 /run/lock/ |
/var/log/ | 日志文件目录 |
/var/run/ | 此目录链接 /run |
/var/mail/ | 放置个人电子邮箱的目录,通常和 /var/spool/mail 互为链接 |
/var/spool/ | 队列数据,即排队等待使用的数据 |
给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。
示例 1:
输入: "aba"
输出: True
示例 2:
输入: "abca"
输出: True
解释: 你可以删除c字符。
注意:
字符串只包含从 a-z 的小写字母。字符串的最大长度是50000。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-palindrome-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
对于这个加强版验证回文,首先很容易想到的就是在判别普通回文方法的基础上升级。
当字符串不符合回文格式时,遍历每一个字符串删除后是否符合回文格式。
很显然的问题是,极大提高了时间复杂度,有没有更好的方式呢?
设定左右两个游标,通过逐一比对的方式验证是否满足回文格式,同时如果不满足,左或者右游标可以跳过一次当前字符(即删除当前字符)。
同时考虑一下边界情况,如果left = right - 1 且 删除机会并没有使用,那么意味着前面的比对都通过了,且字符串长度为偶数,那么任意删除当前左右游标的字符,肯定能生成一个满足回文格式的字符串。代码如下
var validPalindrome = function(s) {
let left = 0;
let right = s.length - 1;
let chance = 1;
while(left < right - 1){
if(s[left] === s[right]){
left++;
right--;
}
else if(s[left + 1] === s[right] && chance > 0){
console.log('左边删除', s[left]);
left++;
chance--;
}
else if(s[right - 1] === s[left] && chance > 0){
console.log('右边删除', s[right]);
right--;
chance--;
}
else {
return false;
}
}
return true;
};
然而事情并没有结束
其中一条没有通过的测试用例是这样的
"aguokepatgbnvfqmgmlcupuufxoohdfpgjdmysgvhmvffcnqxjjxqncffvmhvgsymdjgpfdhooxfuupuculmgmqfvnbgtapekouga"
提取出问题发生的核心位置,即:
"cuppucu"
这个字符串。
很显然走我的判断函数,那么会删除掉左边的字符c
,然后无法形成回文,但是如果删除右边的字符u
,是可以生成回文的 (╬ ̄皿 ̄)
可以通过再次移动左右游标来排除这种情况,最终代码:
var validPalindrome = function(s) {
let left = 0;
let right = s.length - 1;
let chance = 1;
while(left < right - 1){
if(s[left] === s[right]){
left++;
right--;
}
else if(s[left + 1] === s[right] && s[left + 2] === s[right - 1] && chance > 0){
left++;
chance--;
}
else if(s[right - 1] === s[left] && s[right - 2] === s[left + 1] && chance > 0){
right--;
chance--;
}
else {
return false;
}
}
return true;
};
附上结果:
想象下有个函数 padLeft
,它要实现的功能是:如果参数 padding
是 number
类型,表示会在参数 input
前增加多少个空格。如果参数 padding
是 string
类型,直接加在参数 input
前:
function padLeft(padding: number | string, input: string) {
return new Array(padding + 1).join(" ") + input;
// Operator '+' cannot be applied to types 'string | number' and 'number'.
}
Typescript 报错,并提示 number
类型和 number | string
类型相加可能不是我们想要的结果。因此需要在使用前显式的检测参数 padding
是不是 number
类型:
function padLeft(padding: number | string, input: string) {
if (typeof padding === "number") {
return new Array(padding + 1).join(" ") + input;
}
return padding + input;
}
Typescript 会分析运行时代码并和静态类型结合:if/else
结构的控制流、三目表达式、循环等等。在上面的例子中,Typescript 在 if
语句中看到 typeof padding === "number"
并了解到这是一种叫 type guard
的特殊形式代码,并根据程序可能的执行路径分析出一个更具体的类型。类似这种特殊的检查被称为 Narrowing
typeof
type guards在 Javascript 中,typeof
操作符会在运行时返回操作值的基本类型信息字符串:
string
number
bigint
boolean
symbol
undefined
object
function
Typescript 甚至兼容了许多 Javascript 的“怪癖”,例如对 null
使用 typeof
操作符会返回 object
:
function printAll(strs: string | string[] | null) {
if (typeof strs === "object") {
for (const s of strs) {
// Object is possibly 'null'.
console.log(s);
}
} else if (typeof strs === "string") {
console.log(strs);
} else {
// do nothing
}
}
在这个例子中,strs
仅仅被 narrow
至类型 string[] | null
而不是 string[]
,因此 Typescript 会报错
在 Javascript 中,可以在条件判断语句( if
&&
||
!
)中使用任意表达式,例如 if
语句并不要求括号中必须是 boolean
类型,它会将括号中的值/语句强制类型转化为 boolean
类型的值去做判断。以下这些值会被转换为 false
:
0
NaN
""
0n(the bigint version of zero)
null
undefined
而其他值都会被转换为 true
。始终可以通过 Boolean
函数或者双重否定表达式 !!
来将某个值转化为 boolean
类型的值(需要注意的是,前者在 Typescript 中会被类型推论为 boolean
,而后者会被推论为一个字面量布尔类型 true
?)
// both of these result in 'true'
Boolean("hello"); // type: boolean, value: true
!!"world"; // type: true, value: true
这里自己测试结果是都推论为布尔类型,双重否定并没有推论为字面量布尔类型
修改上面的 printAll
函数,加入 truthiness narrowing
,会发现 Typescript 的报错消失了:
function printAll(strs: string | string[] | null) {
if (strs && typeof strs === "object") {
for (const s of strs) {
console.log(s);
}
} else if (typeof strs === "string") {
console.log(strs);
}
}
需要记住,对基本类型做真实性检查通常会引发错误,考虑以下代码:
function printAll(strs: string | string[] | null) {
// !!!!!!!!!!!!!!!!
// DON'T DO THIS!
// KEEP READING
// !!!!!!!!!!!!!!!!
if (strs) {
if (typeof strs === "object") {
for (const s of strs) {
console.log(s);
}
} else if (typeof strs === "string") {
console.log(strs);
}
}
}
这里用一个真实性检查包裹了整个函数体,但是有一个微妙的问题:忽略了处理空字符串场景
对于不熟悉 Javascript 的人来说,这是需要注意的情况。Typescript 可以帮助我们更早的捕获错误,但是如果我们对某个值不做任何操作,它仅能在不过度规定的情况下做这么多(没看懂,个人理解是处理不了这种情况)。如果需要,可以使用类似 linter
这样的工具
最后一点关于 Truthiness narrowing
,布尔否定 !
时,Typescript 会从否定分支进行 narrowing
:
function multiplyAll(
values: number[] | undefined,
factor: number
): number[] | undefined {
if (!values) {
return values;
} else {
return values.map((x) => x * factor);
}
}
Typescript 同样会使用 switch
语句,相等检查例如 ===
!==
==
!=
来进行 narrowing
:
function example(x: string | number, y: string | boolean) {
if (x === y) {
// We can now call any 'string' method on 'x' or 'y'.
x.toUpperCase();
y.toLowerCase();
} else {
console.log(x);
// (parameter) x: string | number
console.log(y);
// (parameter) y: string | boolean
}
}
以上代码中,当我们确保 x
和 y
值和类型都相同时,Typescript 会发现它们只有一个公用类型 string
,所以在此分支中,可以直接使用任意字符串方法
检查是否特定类型的字面量也是生效的,考虑如下代码,通过特定检查,Typescript 正确的将 null
类型从 strs
上过滤掉了:
function printAll(strs: string | string[] | null) {
if (strs !== null) {
if (typeof strs === "object") {
for (const s of strs) {
// (parameter) strs: string[]
console.log(s);
}
} else if (typeof strs === "string") {
console.log(strs);
// (parameter) strs: string
}
}
}
Javascript 的宽松相等 ==
!=
在 Typescript 中也可以正确的 narrowing
。在宽松相等中,检查某个值是否 null
不仅仅是特定值 null
本身,同样也检查是否是 undefined
,反之亦然(不管怎么说还是不要用宽松相等了...):
interface Container {
value: number | null | undefined;
}
function multiplyValue(container: Container, factor: number) {
// Remove both 'null' and 'undefined' from the type.
if (container.value != null) {
console.log(container.value);
// (property) Container.value: number
// Now we can safely multiply 'container.value'.
container.value *= factor;
}
}
in
operator narrowing在 Javascript 中,可以通过 in
操作符来判断某个属性是否存在一个对象中。Typescript 可以通过这种方式来 narrowing
:
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ("swim" in animal) {
return animal.swim();
// (parameter) animal: Fish
}
return animal.fly();
// (parameter) animal: Bird
}
需要注意的是,可选属性在 narrowing
时,会同时存在于两边:
type Fish = { swim: () => void };
type Bird = { fly: () => void };
type Human = { swim?: () => void, fly?: () => void };
function move(animal: Fish | Bird | Human) {
if ("swim" in animal) {
animal
// (parameter) animal: Fish | Human
} else {
animal
// (parameter) animal: Bird | Human
}
}
instanceof
narrowing在 Javascript 中,x instanceof Foo
可以检查 Foo.prototype
是否存在于 x
的原型链中。同样的,Typescript 中可以通过 instanceof
来 narrowing
:
function logValue(x: Date | string) {
if (x instanceof Date) {
console.log(x.toUTCString());
// (parameter) x: Date
} else {
console.log(x.toUpperCase());
// (parameter) x: string
}
}
当我们给一个变量赋值时,Typescript 会分析赋值表达式右侧,并 narrowing
合适的左侧类型:
let x = Math.random() < 0.5 ? 10 : "hello world!";
// let x: string | number
x = 1;
console.log(x);
// let x: number
x = "goodbye!";
console.log(x);
// let x: string
x = true
// Type 'boolean' is not assignable to type 'string | number'.
这里需要注意,尽管在 x = 1
这个赋值表达式之后,可以观察到 x
的类型为 number
,但仍旧可以将 string
类型的值赋予 x
。这是因为 x
的声明类型(即最初的类型)是 string | number
基于“流程上是否可到达”的分析也被称作控制流分析。Typescript 会根据控制流 narrowing
类型 — 当变量遇到类型守卫或赋值表达式。当一个变量被分析时,控制流可能一次又一次的拆分或重新合并,在每一个拆分点或合并点,变量都会被观测到不同的类型:
function example() {
let x: string | number | boolean;
x = Math.random() < 0.5;
console.log(x);
// let x: boolean
if (Math.random() < 0.5) {
x = "hello";
console.log(x);
// let x: string
} else {
x = 100;
console.log(x);
// let x: number
}
return x;
// let x: string | number
}
截至目前,我们已经通过 Javascript 的结构来处理 narrowing
,但是有时候我们可能想要更直接的在代码中操作类型变化
想要定义一个自定义 type guard
,仅需要简单的定义一个返回值是 type predicate
的函数:
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
此例中,pet is Fish
就是 type predicate(类型谓语?)
,类型谓语总是 parameterName is Type
这种形式( parameterName
必须存在于当前函数签名中)
任何时候,当 isFish
被调用,Typescript 都可以进行 narrowing
:
let pet = getSmallPet();
// let pet: Fish | Bird
if (isFish(pet)) {
pet.swim();
// let pet: Fish
} else {
pet.fly();
// let pet: Bird
}
也可以通过类型守卫 isFish
来过滤一个 Fish | Bird
数组,获得一个 Fish
数组:
const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()];
const underWater1: Fish[] = zoo.filter(isFish);
// or, equivalently
const underWater2: Fish[] = zoo.filter(isFish) as Fish[];
// The predicate may need repeating for more complex examples
const underWater3: Fish[] = zoo.filter((pet): pet is Fish => {
if (pet.name === "sharkey") return false;
return isFish(pet);
});
思考如下代码,定义一个 Shape
接口,kind
字段是一个联合字面量类型(防止拼写错误)。同时,当图形为圆时,我们关注的是 radius
字段,当图形为正方形时,则是 sideLength
,因此这两个字段是可选的:
interface Shape {
kind: "circle" | "square";
radius?: number;
sideLength?: number;
}
当我们想要编写一个 getArea
函数(基于是圆形还是正方形计算面积):
function getArea(shape: Shape) {
return Math.PI * shape.radius ** 2;
// Object is possibly 'undefined'.
}
如果设置了 strictNullChecks
配置项,会获得一个错误,radius
可能是没有定义的。如果我们尝试通过 kind
字段来优化这个问题呢?
function getArea(shape: Shape) {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2;
// Object is possibly 'undefined'.
}
}
Typescript 依旧不会理解这里到底要做什么。在此处,我们对于类型的了解比 Typescript 要多,可以尝试用 non-null
断言告诉 Typescript radius
一定存在:
function getArea(shape: Shape) {
if (shape.kind === "circle") {
return Math.PI * shape.radius! ** 2;
}
}
但是这种方式仍然不理想且容易出错。另外,如果不设置 strictNullChecks
,虽然可以随意访问任何属性,但是也同样极容易导致错误(可选属性会被假设为始终存在)
问题在于类型检查器没有任何途径根据 kind
属性知道 radius
或 sideLength
是否存在,考虑到这个问题,可以换一种方式来定义 Shape
:
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
这里,我们拆分了 Shape
变成两个类型,同时 radius
和 sideLength
不再是可选属性,而是必选属性。此时如果访问 Shape
类型的 radius
属性:
function getArea(shape: Shape) {
return Math.PI * shape.radius ** 2;
// Property 'radius' does not exist on type 'Shape'.
// Property 'radius' does not exist on type 'Square'.
}
当我们使用可选属性时,Typescript 仅能警告这条属性可能不存在。当联合两个接口时,Typescript 会告诉我们 Shape
有可能是 Square
,而 Square
上不存在 radius
属性!
与此同时,如果再次尝试检查 kind
属性:
function getArea(shape: Shape) {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2;
// (parameter) shape: Circle
}
}
错误消失了!当一个联合类型中的所有成员都有同一个属性时(此属性必须是字面量类型),Typescript 会认为这是一个 discriminated union
,并可以根据此属性来 narrow
联合类型的成员,此例中 kind
会被认为是 Shape
的“区别属性”。同样的,switch
语句也可以正常生效:
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
// (parameter) shape: Circle
case "square":
return shape.sideLength ** 2;
// (parameter) shape: Square
}
}
如果我们通过 narrowing
,在某个点排除了所有可能的类型,那么此时,Typescript 会使用 never
类型来表示这种不应该不存在的状态
never
类型可以赋予任何类型,但是任何类型都不能赋予 never
类型(除了 never
本身),可以依靠这个特性,在 switch
语句中做一个是否穷尽判断:
type Shape = Circle | Square;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
如果在此时给联合类型新增一个成员,Typescript 会报错:
interface Triangle {
kind: "triangle";
sideLength: number;
}
type Shape = Circle | Square | Triangle;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
const _exhaustiveCheck: never = shape;
// Type 'Triangle' is not assignable to type 'never'.
return _exhaustiveCheck;
}
}
应该将JavaScript中的类型转换区分为:显式强制类型转换(发生在编译阶段)、隐式强制类型转换(发生在运行时)
强制类型转换总是返回基本类型值,不会返回对象或函数。为基本类型值(除 object
)封装一个相应类型的对象,并非严格意义上的强制类型转换
var a = 42
var b = a + '' // 隐式强制类型转换
var c = String(a) // 显式强制类型转换
除了字面意思意外,两种转换在行为特征上也有一定差别
ToString
基本类型值的字符串化规则为:
null
转化为 'null'
undefined
转化为 'undefined'
true
转化为 'true'
数字遵循通用规则,不过极大或极小的数字会使用指数形式
var a = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000
a.toString() // '1.07e+21'
对于普通对象来说,除非自行定义,否则会调用 Object.prototype.toString()
返回内部属性 [[Class]]
的值
数组的 toString()
方法重新定义过
[1, 2, 3].toString() // '1,2,3'
工具函数 JSON.stringify()
在将JSON对象序列化为字符串时也会调用 toString()
,但是它并非是严格意义上的强制类型转换。对于大多数简单值来说,JSON.stringify()
和 toString()
效果相同
JSON.stringify('aa') // ""aa""
JSON.stringify(42) // "42"
JSON.stringify(null) // "null"
JSON.stringify(true) // "true"
所有安全的JSON值(JSON-safe)都可以用 JSON.stringify()
字符串化。对于不安全的JSON值( undefined function symbol 循环引用的对象
):
在对象中遇到 undefined function symbol
会自动忽略,在数组中遇到则返回 null
JSON.stringify(undefined) // undefined
JSON.stringify(() => {}) // undefined
JSON.stringify([1, undefined, () => {}, 4]) // "[1,null,null,4]"
JSON.stringify({ a: 1, b: () => {}, c: undefined }) // "{"a":1}"
对包含循环引用的对象,则会报错
如果要对含有非法JSON值的对象做字符串化,或者对象中的某些值无法被序列化时,需要定义 toJSON()
方法来返回一个安全的JSON值,JSON.stringify()
会先调用该方法并用它的返回值做序列化操作(toJSON()
应该返回一个可以被字符串化的安全的JSON值,而不是返回一个字符串)
var o = {}
var a = {
b: 42,
c: o,
d: () => {}
}
// 创建一个循环引用
o.e = a
JSON.stringify(a) // TypeError: Converting circular structure to JSON
a.toJSON = function() {
return { b: this.b }
}
JSON.stringify(a) // "{"b":42}"
JSON.stringify()
可以传递两个可选参数
replacer
:如果是数组,必须是字符串数组,包含需要序列化的属性名,其余属性会被忽略;如果是函数,会对对象本身调用一次,然后对对象中的每个属性各调用一次。如果要忽略某个键就返回 undefined
,否则返回指定的值。同时,如果遇到键对应的值也是对象,则递归调用
var a = {
b: 42,
c: '42',
d: [1, 2, 3],
e: {
b: 1,
c: 2
}
}
JSON.stringify(a, ['b', 'c']) // "{"b":42,"c":"42"}"
JSON.stringify(a, function (k, v) {
if (k !== 'c') return v
}) // "{"b":42,"d":[1,2,3],"e":{"b":1}}"
space
:用来指定缩进格式,为整数时指定每一级缩进的字符数,为字符串时前十个字符被用于每一级的缩进
var a = {
b: 42,
c: '42',
d: [1, 2, 3],
}
JSON.stringify(a, null, 3)
// "{
// "b": 42,
// "c": "42",
// "d": [
// 1,
// 2,
// 3
// ]
// }"
JSON.stringify(a, null, '-----')
// "{
// -----"b": 42,
// -----"c": "42",
// -----"d": [
// ----------1,
// ----------2,
// ----------3
// -----]
// }"
ToNumber
如果需要将非数字值当做数字使用,遵循以下规则:
true
转化为 1
false
转化为 0
undefined
转化为 NaN
null
转化为 0
NaN
(报错?)ToPrimitive
为了将值转化为相应的基本类型值,首先会去检查该值是否有 valueOf()
方法,如果有且返回基本类型值,会使用该基本类型值进行强制类型转换。如果没有则使用 toString()
的返回值来进行强制类型转换。如果这两种方式都不返回基本类型值,会产生 TypeError
错误
使用
Object.create(null)
创建的对象[[Prototype]]
属性为null
,不存在valueOf()
和toString()
方法,因此无法进行强制类型转换
var a = {
valueOf: () => '42',
}
var b = {
toString: () => '42',
}
var c = [4, 2]
c.toString = function () {
return this.join('')
}
Number(a) // 42
Number(b) // 42
Number(c) // 42
Number('') // 0
Number([]) // 0
Number(['abc']) // NaN
ToBoolean
Javascript规范具体定义了一小撮可以被强制类型转换为 false
的值,其余的则都是 true
假值列表:
undefined
null
false
+0 -0 NaN
''
字符串和数字的互相转换是通过 String()
和 Number()
两个内建函数来实现的
var a = 42
var b = String(a) // '42'
var c = '3.14'
var d = Number(c) // 3.14
// 注意这里没有new关键字,不会创建封装对象
一些其他的方式:
+
:
var a = 42
var b = a.toString() // '42'
var c = '3.14'
var d = +c // 3.14
// 不推荐用一元运算符来显式强制类型转换,会导致代码难以理解
// 同时 + 还可以将日期对象转换为时间戳,同样不推荐
var e = new Date('Mon, 18 Aug 2014 08:53:06 CDT')
+e // 1408369986000
~
:位操作符会通过抽象操作 ToInt32
强制操作数使用32位格式,位非 ~x
基本等于 -(x+1)
。对于正数,位反后拿到的是补码,需要转换为原码。对于负数,先取补码再位反。(MDN例子)
补码的存在是为了正确计算负数的加法(借助溢出),以8位有符号位数字举例
正数的补码是其本身,负数的补码符号位不变,其余按位取反并+1
补码转换为原码同样是符号位不变,其余按位取反并+1
原码 补码 1 + (-2) 00000001
+
10000010
1000011 = -300000001
+
11111110
11111111 = -11 + (-1) 00000001
+
10000001
1000010 = -200000001
+
11111111
00000000 = 0(溢出8位,从0开始)
在 -(x+1)
中唯一能得到 0
的 x
值是 -1
,这是个哨位值,通常被赋予特殊含义,例如字符串 indexOf()
方法在没有找到相应字符串时返回 -1
var a = 'Hello World'
if (a.indexOf('lo') >= 0) {
// 写法不好,抽象渗漏(暴露底层实现细节)
}
if (a.indexOf('lo') != -1) {
// 写法不好,抽象渗漏(暴露底层实现细节)
}
if (~a.indexOf('lo')) {
// 将结果强制类型转换为真假值!
}
解析字符串中的数字,和将字符串强制类型转换为数字是有区别的:解析允许字符串中含有非数字字符,而转换不允许
var a = '42'
var b = '42px'
Number(a) // 42
parseInt(a) // 42
Number(b) // NaN
parseInt(b) // 42
ES5之前,parseInt()
如果不传递第二个参数指定进制,会自行根据字符串的第一个字符来决定进制。除此之外,不要传递非字符串的其他参数,如果传入参数非字符串,会先隐式转换为字符串
parseInt(1 / 0, 19) // parseInt('Infinity', 19) 第一个字符为'I',以19位基数时值为18,第二个字符不是有效数字,解析结束
parseInt(0.000008) // 0
parseInt(0.0000008) // 8 '8e-7'
parseInt(false, 16) // 250 'fa' = 250
parseInt(parseInt, 16) // 15 'f' 来自于function () {...}
parseInt('0x10') // 16
parseInit('103', 2) // 2 到3停止,因为3不是有效的二进制数字
Boolean()
(不带new)是显式的 ToBoolean
强制类型转换
var a = '0'
var b = []
var c = {}
var d = ''
var e = 0
var f = null
var g
Boolean(a) // true
Boolean(b) // true
Boolean(c) // true
Boolean(d) // false
Boolean(e) // false
Boolean(f) // false
Boolean(g) // false
但这种方式并不常用,一元运算符 !
也可以将值显式强制类型转换为布尔值,因为 !
还会反转真假值,因此最常用的方式是 !!
,在 if
语句中,会自动隐式的进行 ToBoolean
转换,建议用显式的方式让代码更易理解
因人而异,对于自己来说不够明显的强制类型转换都可以称作隐式强制类型转换,会导致代码晦涩难懂。但从另一个角度来看,也可以减少代码的冗余,让代码更简洁
根据ES5规范,如果 +
的某个操作数是字符串或者能够通过以下步骤转换为字符串,则进行拼接操作:
ToPrimitive
抽象操作[[DefaultValue]]
否则执行数字加法
var a = '42'
var b = '0'
a + b // '420'
var c = 42
var d = 0
c + d // 42
var e = [1, 2]
var f = [3, 4]
e + f // '1,23,4' 对数组进行ToPrimitive抽象操作,发现valueOf()方法不能返回基本类型值,转而使用toString()
通过 +
空字符串来隐式强制类型转换数字为字符串的场景非常多见,但和显式强制类型转换略有不同:
a + ''
依据 ToPrimitive
抽象操作规则,先对 a
调用 valueOf()
方法,然后通过 ToString
抽象操作将返回值转换为字符串String(a)
则是直接调用 ToString
抽象操作var a = {
valueOf: () => 42,
toString: () => 4
}
a + '' // '42'
String(a) // '4'
相应的,- * /
都是数字运算符,因此会将操作数强制类型转化为数字。
var a = 1
var b = '2'
b - a // 1
a * b // 2
b / a // 2
var c = [3]
var d = [1]
c - d // 2 c和d首先被转化为字符串,然后再转化为数字
将某些复杂的布尔逻辑转化为数字加法的时候可以派上大用场,但是情况非常罕见
function onlyOne() {
var sum = 0
for (var i = 0; i < arguments.length; i++) {
if (arguments[i]) {
sum += arguments[i]
}
}
return sum == 1
}
var a = true
var b = false
onlyOne(b, a) // true
onlyOne(b, a, b, b, b, b) // true
if()
语句中的条件判断表达式for(..; ..; ..)
语句中的条件判断表达式(第二个)while()
和 do..while()
循环中的条件判断表达式?:
语句中的条件判断表达式||
和 &&
的左操作数对于javascript中的逻辑运算符(实际上更像是操作数选择器运算符),并不是返回一个布尔值,而是返回两个操作数中的一个。 ||
和 &&
首先对第一个操作数进行 ToBoolean
抽象操作,然后再执行条件判断。对于 ||
,如果第一个操作数判断结果为 true
则返回第一个操作数的值,为 false
则返回第二个操作数的值。而对于 &&
,如果第一个操作数判断结果为 true
就返回第二个操作数的值,为 false
则返回第一个操作数的值
之所以我们在
if
或其他条件判断语句中可以用&&
和||
,是因为这些语句本身会做隐式强制类型转换
常见的误区:“ ==
检查值是否相等,===
检查值和类型是否相等”。正确的解释是:“ ==
允许在相等比较中进行强制类型转换,而 ===
不允许。”延伸出来的性能结论:实际上 ==
比 ===
在比较过程中做的事情更多(相比误区说法),所以性能上确实会慢上一点点(可以忽略不计)
ES5规范如下:
NaN
不等于 NaN
,+0
等于 -0
)==
在比较两个不同类型的值时,会发生隐式强制类型转换,将其中之一或两者都转换为相同类型再比较,转换规则如下:
Type(x)
是数字,Type(y)
是字符串,则返回 x == ToNumber(y)
的结果,反之亦然Type(x)
是布尔值,则返回 ToNumber(x) == y
(Type(y)
亦然),因此不要使用布尔值的宽松相等,会和预期完全不符null
和 undefined
在比较中可以相互强制类型转换,两者等价Type(x)
是字符串或数字,Type(y)
对象,则返回 x == ToPrimitive(y)
的结果,反之亦然更改对象原型
Number.prototype.valueOf = () => 3
new Number(2) == 3 // true
// 因为数字对象拆封涉及到ToPrimitive操作
假值的相等比较
false == 0 // true
// false优先转换为0,即 0 == 0
false == '0' // true
// false优先转换为0,即 0 == '0',两个操作数分别是数字和字符串,转换为 0 == Number('0')
false == '' // true
// false优先转换为0,即 0 == '',两个操作数分别是数字和字符串,转换为 0 == Number('')
false == [] // true
// false优先转换为0,[]通过ToPrimitive转换为''
[] == ![] // true
// 右侧进行了布尔值的显式强制类型转换,即 [] == false
总结:
==
0 '' []
,尽量不要使用 ==
==
(个人理解)根据ES5规范,首先对比较双方调用 ToPrimitive
操作
ToNumber
将比较双方强制类型转换为数字比较这里原本书中的例子有问题
var a = 42
var b = '43'
a < b // true (42 < 43)
var a = ['42']
var b = ['043']
a < b // false ('42' < '043',在字母顺序上,0小于4)
需要注意,根据规范,a <= b
会被处理成 b < a
再将结果反转。所以实际上JavaScript中 <=
是不大于的意思(而不是通常理解的小于等于),>=
同理。这会产生一些悖论:
var a = {}
var b = {}
a < b // false 都是 '[object object]'
a == b // false 指向地址不是一致的!
a > b // false
a <= b // true
a >= b // true
相等比较有严格相等,但是关系比较却没有严格关系比较,想要避免在关系比较中出现隐式强制类型转换,只能确保比较两者类型相同,别无他法
Red Hat 硬件支持
openSUSE 硬件支持
Linux 对笔记本电脑的支持
设备 | 在Linux中的文件名 |
---|---|
SCSI、SATA、USB | /dev/sd[a-p] |
Virtio接口 | /dev/vd[a-p](虚拟机内) |
软盘驱动器 | /dev/fd[0-7] |
鼠标 | /dev/input/mouse[0-15] /dev/psaux(PS/2接口) /dev/mouse(当前鼠标) |
CD-ROM、DVD-ROM | /dev/scd[0-1](通用) /dev/sr[0-1](通用,CentOS常见) /dev/cdrom(当前CD-ROM) |
磁带机 | /dev/ht0(IDE接口) /dev/st0(SATA/SCSI接口) /dev/tape(当前磁带) |
早期的硬盘第一个扇区(512B)包涵启动引导程序(446B)和分区表(64B)
分区表记录整个硬盘分区状态,64B最多仅能有四组记录区,每组记录区记录了该区的起始与结束柱面号码
假设硬盘设备文件名为 /dev/sda
那么这4个分区文件名在Linux系统中如下:
最初的四组分区记录,可以设置为主要分区和扩展分区(最多一个),而扩展分区可以继续拆分为逻辑分区
扩展分区的目的是用额外的扇区来记录分区信息,本身并不能拿来格式化
假设初始分区分为:P1(Primary) P2(Extended)
,同时P2拆分为 L1、L2、L3(logical partition)
则在Linux系统中,设备文件名如下:(前面四个号码都是保留给主要分区和扩展分区使用的)
总结:
目前已经有4K的扇区设计出现,为了兼容所有硬盘,在扇区的定义上,会使用逻辑区块地址(LBA, Logical Block Address)
GPT将磁盘所有区块以LBA来规划,使用了34个LBA区块来记录分区信息,同时将磁盘最后34个LBA区块用来做备份
4 * 32 = 128
组分区记录相比于传统的BIOS,UEFI更像一个小型操作系统。某些时候,需要将UEFI的安全启动功能(secure boot)关闭,才能正常启动Linux系统。虽然UEFI可以直接获取GPT分区表,但是最好有BIOS boot分区。为了与windows兼容,并且提供其他第三方厂商使用的UEFI存储空间,需要格式化一个FAT格式的文件系统分区,通常512MB - 1GB
将磁盘分区的数据防止在某个目录下,进入该目录就可以读取分区,这个操作被称为“挂载”。整个Linux系统最终要的就是根目录 root(/)
,因此根目录一定会挂载个某个分区,其他目录则可以挂载在不同的分区。可以通过对路径反向追踪来判断某个文件在哪个分区:即哪一级目录先被查到是挂载点,则该文件属于这个挂载点对应的分区
懒人划分:
麻烦一点的划分:根据主机服务来确定硬盘的规划
/home
/var
这个目录可以考虑独立出来挂载,并加大容量所有 typeof
返回为 'object'
的对象都包含一个内部属性 [[Class]]
,这个属性无法直接访问,一般通过 Object.prototype.toString
查看
多数情况下,内部 [[Class]]
属性和创建该对象的内建原生构造函数相对应
Object.prototype.toString.call([1]) // "[object Array]"
Object.prototype.toString.call(/^&/) // "[object RegExp]"
虽然 Null()
和 Undefined()
这样的原生构造函数并不存在,但是内部的 [[Class]]
属性仍然是 'Null'
和 'Undefined'
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
其他基本类型值的情况有所不同,通常称为“包装”,即被各自的封装对象自动包装
Object.prototype.toString.call('') // "[object String]"
Object.prototype.toString.call(1) // "[object Number]"
Object.prototype.toString.call(true) // "[object Boolean]"
由于基本类型值既没有属性也没有方法,需要通过封装对象才能访问,例如 .length
或者 .toString()
。此时JavaScript会自动为基本类型值 wrap
一个封装对象,并在操作完成后重新拆封成基本类型
var a = 'abc'
a.length // 3
a.toUpperCase() // 'ABC'
a // 'abc'
通常不建议直接使用封装对象,浏览器已经为 .length
这样的常见情况作了性能优化,直接使用封装对象来“提前优化”代码反而会降低执行效率
使用封装对象时还需特别注意假值情况,因为对象始终是真值
var a = new Boolean(false)
if(!a) {
console.log('xxx') // 不会执行到这里
}
如果想要自行封装基本类型值,除了使用对应的构造函数,还可以通过 Object()
(不带new关键字)
var a = 'abc'
var b = new String(a)
var c = Object(a)
typeof b // 'object'
typeof c // 'object'
b instanceof String // true
c instanceof String // true
Object.prototype.toString.call(b) // '[object String]'
Object.prototype.toString.call(c) // '[object String]'
可以使用 valueOf()
函数来获得封装对象中的基本类型值
var a = new String('abc')
var b = new Number(42)
var c = new Boolean(true)
a.valueOf() // 'abc'
b.valueOf() // 42
c.valueOf() // true
如果需要用到封装对象中的基本类型值,会发生隐式拆封(强制类型转换)
var a = new String('abc')
var b = a + 'd'
b // 'abcd'
typeof a // 'object'
typeof b // 'string'
对于数组、对象、函数、正则表达式,使用字面量和使用构造函数来创建没有任何区别,除非十分必要,否则应该避免使用构造函数来创建,因为它们经常产生意想不到的结果
构造函数 Array()
不要求必须带 new
关键字,不带时,它会被自动补上。同时,如果只传入一个数字参数,该参数会被作为数组的预设长度。问题是,在JavaScript中数组没有预设长度这个概念。如若一个数组没有任何单元,但 length
属性却显示有单元数量,会导致一些怪异的行为(不同版本不同浏览器的控制台显示结果也不尽相同)
var a = Array(3)
var b = [undefined, undefined, undefined]
a // [empty × 3] 最新版chrome
b // [undefined, undefined, undefined]
a.join('-') // "--"
b.join('-') // "--"
a.map((v, i) => i) // [empty × 3]
b.map((v, i) => i) // [0, 1, 2]
a.map()
之所以执行失败,是因为数组中并不存在任何单元,而 a.join()
不同,它会根据 length
的值来遍历数组中的元素。总之,永远不要创建和使用“稀疏数组”(包含至少一个空单元的数组)。
可以通过如下方式来创建包含 undefined
单元的数组(而非稀疏数组)
var a = Array.apply(null, { length: 3 })
a // [undefined, undefined, undefined]
原理如下:
Array.apply()
调用 Array()
,第一个参数作为上下文传入了 null
。第二个参数传入了一个类数组对象(array-like object){ length: 3 }
apply()
在内部有一个 for
循环,从 0
开始循环到 length
(即 0 1 2)apply()
内部该数组参数名为 arr
,for
循环就去参数中取 arr[0] arr[1] arr[2]
。然而,由于 { length: 3 }
中并没有数字键对应的值,得到的结果都是 undefined
apply()
将循环结束后获得的参数传入 Array()
得到 Array(undefined, undefined, undefined)
对于 Object() Function() RegExp()
来说,仅有需要动态定义正则表达式时才需要用到构造方式来创建,其余场景无论是从可读可维护性或是性能上来看,构造方式创建都不如用字面量创建
var name = 'Kyle'
var namePattern = new RegExp('\\b' + name + '\\b', 'ig')
var txt = 'kyle xxx'
namePattern.test(txt) // true
相比于其他原生构造函数,Date() 和 Error()
的使用场景要多很多,因为它们没有对应的字面量形式。创建一个错误对象可以获得当前运行栈的上下文 .stack
属性,包含函数调用栈以及错误代码行号等重要信息
function foo() {
bar()
}
function bar() {
var err = new Error('error!')
console.log(err.stack)
}
foo()
// Error: error!
// at bar (<anonymous>:6:13)
// at foo (<anonymous>:2:3)
// at <anonymous>:10:1
在ES6中新加入了一个基本类型 Symbol(符号)
,符号是具有唯一性的特殊值(并非绝对),可以用它作为对象的属性键从而避免命名重复。ES6中有一些预定义符号,以 Symbol
的静态属性形式出现,如 Symbol.iterator
,它们作为键时有特殊用途
var myIterable = {}
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
}
// 给myIterable定义一个迭代器
[...myIterable] // [1, 2, 3]
可以使用原生构造函数来自定义符号,但是需要注意不能带 new
var mySymbol = Symbol('my symbol')
mySymbol // Symbol(my symbol)
mySymbol.toString() // 'Symbol(my symbol)'
var obj = {}
obj[mySymbol] = 'foo'
Object.getOwnPropertySymbols(obj) // [Symbol(my symbol)]
一些原生构造函数的原型并非普通对象
typeof Function.prototype // "function"
Function.prototype() // 空函数
RegExp.prototype.toString() // "/(?:)/" 空正则表达式
Array.isArray(Array.prototype) // true
可以考虑将原型作为声明变量时的默认值,这样的好处是,多处变量创建的默认值仅会被创建一次。但是需要注意,如果默认值随后会被更改,则不要使用这种方式,会引起一系列问题
var arr = arr || Array.prototype
arr.push(1)
Array.prototype // [1]
this 实际上是在函数被调用时动态发生的绑定,它指向什么完全取决于函数在哪里被调用
当显式绑定传入第一个参数为null或者undefined的时候,this会被绑定至全局
function foo() {
console.log(this.a);
}
var a = 2;
foo.call(null); // 2
当使用硬绑定来实现柯里化的时候,被绑定函数内部如果使用this,会导致操作全局对象
function foo(a, b) {
this.a = 1;
console.log(a + b);
}
var bar = foo.bind(null, 2);
bar(3); //5
console.log(a); // 1
一个好的处理方式是:需要在显式绑定忽略this时,传入 DMZ
function foo(a, b) {
this.a = 1;
console.log(a + b);
}
var ø = Object.create(null); // 不会创建Object.prototype委托,比{}更空
var bar = foo.bind(ø, 2);
bar(3); //5
console.log(a); // ReferenceError
if (!Function.prototype.softBind) {
Function.prototype.softBind = function (obj) {
var fn = this;
var curried = [].slice.call(arguments, 1);
var bound = function () {
return fn.apply(
(!this || this === (window || global)) ? obj : this,
curried.concat.apply(curried, arguments)
);
};
bound.prototype = Object.create(fn.prototype);
return bound;
}
}
function foo() {
console.log(this.name);
}
var obj = { name: 'obj' },
obj2 = { name: 'obj2' },
obj3 = { name: 'obj3' };
var bar = foo.softBind(obj);
bar(); // obj
obj2.foo = foo.softBind(obj);
obj2.foo(); // obj2
bar.call(obj3); // obj3
setTimeout(obj2.foo, 10); // obj
根据当前的词法作用域来决定this,继承外层函数调用的this指向
优先级高于new
function foo() {
setTimeout(() => {
console.log(this.a);
}, 100);
}
foo.call({
a: 1
}); // 1
基本类型和对象子类型(或称内置对象,比如String)是不等同的,javascript会在操作基本类型的时候自动将之转化为内置对象
var str = 'abc';
var strObj = new String('abc');
console.log(str === strObj); // false
console.log(typeof str); // string
console.log(typeof strObj); // object
console.log(str instanceof String); // false
console.log(strObj instanceof String); // true
基本类型中:boolean、string、number字面量与对应的内置对象不等同,null和undefined没有构造形式,而Object、Array、Function和RegExp无论使用字面量还是构造形式来声明,他们都是对象。
对象内容并不是存储在对象内部,而是通过属性名(键)来指向内存某一位置的值
最常用的场景是ES6的Symbol,因为Symbol不透明且无法预测
const key = Symbol('key');
const obj = {
[key]: 1,
};
console.log(obj[key]);
Object.assign内部就是使用=操作符来赋值,以此实现浅拷贝。因此原对象属性的一些特性(writable等)不会被复制到目标对象
var myObj = {};
Object.defineProperty(myObj, 'a', {
value: 2,
// 是否可写,如果改为false,修改值不生效,严格模式下报错 TypeError
writable: true,
// 是否可配置,如果改为false,不可再通过Object.defineProperty来配置descriptor,也不能删除此属性。否则 TypeError
configurable: true,
// 是否可枚举,如果改为false,不会出现在对象遍历中(如for...in)
enumerable: true,
});
如果目标对象引用了其他对象,这些属性特性的声明不会对子对象的内容产生作用
var obj = {};
Object.preventExtensions(obj);
obj.a = 1; // 严格模式下报错
obj.a // undefined
等于对现有对象调用Object.preventExtensions(),同时把所有现有属性设置为 configurable: false,即只能修改已有属性的值
等于对现有对象调用Object.seal(),同时把所有现有属性设置为 writable: false
当给一个属性定义getter,或setter或两者都有时,这个属性会被定义为”访问描述符“(区别于”数据描述符“),javascript会忽略它的value和writable特性,只关心set、get、configurable和enumerable(?实测会报错,不能同时设置get和value、writable)
var obj = {
get a() {
return 2;
},
_b_: 3,
};
Object.defineProperty(obj, 'b', {
get() {
return this._b_;
},
set(val) {
this._b_ *= val;
},
enumerable: true,
});
console.log(obj.a); // 2
console.log(obj.b); // 3
obj.b = 3;
console.log(obj.b); // 9
in 操作符会检查属性名是否存在于对象或原型链中,而 Object.hasOwnProperty 只会检查属性是否存在对象中
会检查属性名是否直接存在于对象上且可枚举
返回所有可枚举属性名的数组
返回一个数组,包含所有在对象上的属性,无论是否可枚举
var array = [1, 2, 3];
var it = array[Symbol.iterator]();
it.next(); // { value: 1, done: false }
it.next(); // { value: 2, done: false }
it.next(); // { value: 3, done: false }
it.next(); // { value: undefined, done: true }
Symbol.iterator 会指向一个'返回迭代器对象'的函数
for...of 会找到对象的 iterator
来处理遍历(每次调用next方法)
数组天生自带 iterator
,所以可以被for...of遍历,但是普通对象不行,可以手动给想遍历的对象定义 iterator
var obj = {
a: 1,
b: 2,
c: 3,
};
Object.defineProperty(obj, Symbol.iterator, {
enumerable: false,
writable: false,
configurable: true,
value: function () {
var o = this;
var index = 0;
var keys = Object.keys(o);
return {
next: function () {
return {
value: o[keys[index++]],
done: index > keys.length
}
}
}
}
});
for (var item of obj) {
console.log(item); // 1, 2, 3
}
javascript的机制其实和类完全不同,虽然可以用一般意义上的类来理解(比如实例化,继承等),但是javascript所做的一切本质上只是在操作对象
obj.foo = 'bar';
如果希望在3、4也屏蔽上层foo,就不能使用=操作符来赋值,可以通过Object.defineProperty()来添加 foo 属性
在面向类的语言中,类可以被复制多次(类似用模具做东西)。但是在javascript中,并没有类似的复制方式,不能创建一个类的多个实例,只是创建了多个对象。它们的 prototype 关联的是同一个对象(引用),在默认情况下并不会复制。因此,在javascript中,”实例化的本质“是 —— 让两个对象相关联
new 操作符实际上并没有直接创建关联,这个关联只是个副作用(?!)
鉴于上述javascript实例化的本质,因此不能信任这条属性,不应该把它理解为”由...构造“。
function Foo() {
}
Foo.prototype = {};
var obj = new Foo();
console.log(obj.constructor === Object); // true
寻找路径:
obj不存在 constructor
-> Foo.prototype不存在 constructor
-> Object.prototype的 constructor
指向 Object
修正这种行为,必须显式的定义Foo.prototype.constructor
重新指定 prototype.constructor 时
Bar.prototype = Foo.prototype;
// 直接引用而不是创建新对象,会导致修改父类原型
Bar.prototype = new Foo();
// 基本能满足要求,但是有一些副作用:丢弃原本原型,同时调用constructor,导致原型属性变成对象属性
Bar.prototype = Object.create(Foo.prototype);
// 正确做法(非原型属性用 Foo.call(this) 来继承),唯一缺点是会丢弃原本的原型,需要重新指定constructor
// es6
Object.setPrototypeOf(Bar.prototype, Foo.prototype); // 不需要抛弃原本的原型对象
a instanceof Foo // 在a的整条链中是否存在Foo.prototype,只能处理对象和函数的关系
Foo.prototype.isPrototypeOf(c); // 不用类的角度来解释,而是直接关注对象之间的关系
b.isPrototypeOf(c)
Object.getPrototypeOf(a); // 直接获取
function Foo() { }
function Bar() { }
function Baz() { }
Object.setPrototypeOf(Bar.prototype, Foo.prototype);
Object.setPrototypeOf(Baz.prototype, Bar.prototype);
const a = new Baz();
console.log(a instanceof Baz); // true
console.log(Baz.prototype.isPrototypeOf(a)); // true
console.log(Object.getPrototypeOf(a) === Baz.prototype); // true
console.log(Object.getPrototypeOf(Object.getPrototypeOf(a)) === Bar.prototype); // true
console.log(a.__proto__.__proto__.__proto__ === Foo.prototype); // true es6之前非标准
__proto__实际上更像一个getter/setter,并不存在当前对象中,而是存在 Object.prototype 中。
// 模拟一个 .__proto__
Object.defineProperty(Object.prototype, '__proto__', {
get: function () {
return Object.getPrototypeOf(this);
},
set: function (o) {
Object.setPrototypeOf(this, o);
return o;
}
});
在javascript中,用委托代替继承来解释原型链更为恰当,因为本质上prototype的机制就是对象之间的关联关系
var task = {
setId: function (id) {
this.id = id;
},
outputId: function () {
console.log(this.id);
}
};
var subTask = Object.create(task); // 让subTask委托task
console.log(Object.getPrototypeOf(subTask)); // task
subTask.prepareTask = function (id, label) {
this.setId(id);
this.label = label;
};
subTask.outputTask = function () {
this.outputId();
console.log(this.label);
};
subTask.prepareTask(1, 2);
subTask.outputTask(); // 1, 2
Object.create 会创建一个新对象,并把它的__proto__委托到我们指定的对象
原型风格
function Foo(who) {
this.me = who;
}
Foo.prototype.identify = function () {
return `i am ${this.me}`;
}
function Bar(who) {
Foo.call(this, who);
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.speak = function () {
alert(`hello, ${this.identify()}`);
}
var b1 = new Bar('b1');
var b2 = new Bar('b2');
b1.speak(); // hello, i am b1
b2.speak(); // hello, i am b2
对象关联风格
const Foo = {
init: function (who) {
this.me = who;
},
identify: function () {
return `i am ${this.me}`
}
};
const Bar = Object.create(Foo);
Bar.speak = function () {
alert(`hello, ${this.identify()}`);
}
var b1 = Object.create(Bar);
b1.init('b1');
var b2 = Object.create(Bar);
b2.init('b2');
b1.speak(); // hello, i am b1
b2.speak(); // hello, i am b2
关于对象关联风格的思考:乍一看 b1 的初始化变为了两个步骤,需要更多代码。但其实这也是一个优点,把构造和初始化分离,在某些情况下更灵活
ECMAScript规范包含了Annex B,其中介绍了浏览器兼容性与官方规范的差异,应该注意避免使用
主要如下:
0123(十进制83)
window.escape()
和 window.unescape()
可以将字符串转义和回转成带有 %
的十六进制转义序列,如 window.escape('?')
的结果是 %3F
,此特性已废弃String.prototype.substr()
和 String.prototype.substring()
十分相似,前者的第二个参数是截取长度,而后者是结束索引。前者没有被严格废弃,但是应该避免使用同时,Web ECMAScript规范中介绍了目前浏览器因为兼容性考虑,实现的一些内容(并未包含在官方规范内),同样应该避免使用
<!-- -->
在浏览器中是合法的String.prototype
中包含HTML格式字符串的附加方法,如 anchor()、big()...
等等RegExp
扩展 RegExp.$1 .. RegExp.$9
和 RegExp.lastMatch/RegExp["$&"]
(匹配组和最近匹配),同样是非标准Function.prototype.arguments
和 arguments.caller
均已废止,Function.caller
非标准var a = document.createElement('div')
typeof a // 'object'
Object.prototype.toString.call(a) // '[object HTMLDivElement]'
上例中,a
不仅仅是一个对象,还是个特殊的宿主对象,其内部的 [[Class]]
值来自预定义的属性,不可更改。需要注意如下问题:
boolean
时会意外的成为假值或真值,需要注意object
内建方法,比如 toString()
this
重载为其他对象的方法除此之外, console
及其各种方法也是宿主环境提供
一个不太为人所知的现象:创建带有id的DOM元素时,也会创建同名的全局变量
<div id="app"></div>
console.log(app) // HTML元素
因此尽量不要使用全局变量
除非必要,否则不要扩展原生原型。其次,扩展时必须加入判断条件:
if (!Array.prototype.foobar) {
Array.prototype.foobar = function () {
this.push('foo', 'bar')
}
}
这种情况一般称之为 polyfill/shim
<script>
多个 <script>
标签中的JavaScript代码是如何运行的:
global
对象(浏览器中是 window
)<script>foo()</script>
<script>
function foo () {
// 报错
}
</script>
保留字分为四类:关键字( function, switch
等 )、预留关键字( enum
等 )、null
常量和 true/false
布尔常量
一首关于保留字有趣的小诗:
Let this long package float,
Goto private class if short.
While protected with debugger case,
Continue volatile interface.
Instanceof super synchronized throw,
Extends final export throws.Try import double enum?
-False, boolean, abstract function,
Implements typeof transient break!
Void static, default do,
Switch int native new.
Else, delete null public var
In return for const, true, char
...Finally catch byte
这首诗中也包含了一些ES3中的保留字(byte、long)等,它们在ES5中已经不再是保留字
在ES5之前,保留字也不能用作对象属性名或键,目前已经没有这个限制
JavaScript规范对于函数中参数个数,字符串常量的长度等没有限制,但是由于JavaScript引擎实现各异,会导致一些限制:
function addAll () {
var sum = 0
for (var i = 0; i < arguments.length; i++) {
sum += arguments[i]
}
}
var nums = []
for (var i = 1; i < 100000; i++) {
nums.push(i)
}
addAll(2, 4, 6) // 12
addAll.apply(null, nums) // 499950000
// 在一些引擎中会报错
.
:代表当前目录..
:代表上一层目录,根目录的上层目录还是根目录-
:代表前一个工作目录~
:代表当前用户的家目录~account
:代表用户account的家目录cd
(change directory, 切换目录)可以使用绝对或相对路径
pwd
(print working directory, 显示目前目录)$ pwd [-P] #参数 -P 可以显示链接文件的真正路径
mkdir
(make directory, 新建目录)$ mkdir [-mp] dirname #参数 -m 可以额外设置目录权限,参数 -p 是指递归创建
rmdir
(remove directory, 删除空目录)$ rmdir [-p] dirname #参数 -p 递归删除上层空目录
当在Linux中执行命令的时候,系统会根据PATH的设置去每一个定义的目录下查找第一个可执行的命令。这个变量的内容是由一堆目录所组成,每个目录之间用 :
隔开
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
ls
$ ls [-aAdfFhilnrRSt] [--color={never,auto,always}] [--full-time] dirname/filename
-a
:全部文件,包括隐藏文件-A
:全部文件,但不包括 .
和 ..
两个目录-d
:仅列出目录-f
:直接列出结果,不进行排序-F
:根据文件类型、目录附加额外的信息 * 可执行
/ 目录
= socket文件
| FIFO文件
-h
:格式化文件容量,带上单位-i
:列出inode号码-l
:详细信息,包含属性权限等-n
:列出UID与GID而非文件拥有者、用户组-r
:将排序结果反向输出-R
:递归列举子目录及文件-S
:以文件大小排序-t
:以时间排序--color=never
:不依据文件特性给与颜色--color=always
:依据文件特性显示颜色--color=auto
:让系统自行判断是否给与颜色--full-time
:以完整时间模式输出--time={atime, stime}
:输出access时间或改变权限属性的时间cp
$ cp [-adfilprsu] source destination
$ cp [-adfilprsu] source1 source2 source3 ... directory
-a
:相当于 -dr --preserve=all
-d
:若源文件是链接文件,则复制链接文件属性而非文件本身-f
:若目标文件已经存在且无法开启,则删除后再试一次-i
:若目标文件已经存在,覆盖前会先询问-l
:建立硬链接(hard link),而非复制文件本身-p
:连同文件的属性一起复制,而非使用默认属性-r
:递归复制-s
:复制成符号链接文件(symbolic link),即快捷方式-u
:目标文件比源文件旧才更新、或目标文件不存在才复制--preserve-all
:除了 -p
功能外,还加入 SELinux 的属性,links、xattr也复制rm
$ rm [-fir] filename/dirname
-f
:强制删除,忽略不存在的文件,不会出现警告信息-i
:交互模式,删除前会询问使用者-r
:递归删除mv
$ mv [-fiu] source destination
$ mv [-fiu] source1 source2 source3 ... directory
-f
:强制移动,如果目标文件已经存在,不会询问直接覆盖-i
:交互模式,当目标文件存在时,会询问是否覆盖-u
:若目标文件存在且source交新,才会更新basename/dirname
$ dirname ~/Documents/my-project/test-shell/test.txt
/Users/rango/Documents/my-project/test-shell
$ basename ~/Documents/my-project/test-shell/test.txt
test.txt
cat
$ cat [-AbEnTv] filename
-A
:相当于 -vET
-b
:列出行号,空白行不标注行号-E
:打印结尾换行符 $
-n
:打印行号,包括空白行-T
:将 tab
以 ^i
打印出来-v
:列出一些看不出来的特殊字符是否在犹豫到底句尾加不加分号?
是否有过和同事争执哪种写法更优雅更易维护的经历?
是否争执过后痛不欲生的发现人和人之间无法互相理解?
参考下 standard
的哲学吧!
Just use
standard
and move on. There are actual real problems that you could spend your time solving! :P
确定一个开箱即用的规范然后就严格执行吧!(如果你有拍板能力的话...
尽管在代码格式化上面有许多好用的命令行工具: prettier
、 standard
、 tslint
...
但是考虑到不同技术栈对格式化要求不同、想要同时使用某几种风格、灵活性等情况,个人还是更加倾向基于 eslint
通过扩展和插件来配置项目代码规范的方式。目前各类热门的格式化工具或风格也都有 eslint
的扩展或者插件。
同时,基于 eslint
来扩展这种方式,无需为不同业务场景或项目安装额外的IDE插件或命令行工具
关于各种类型的 Eslint
配置文件如果同时出现,优先级如下:(通常不会这么做)
.eslintrc.js
.eslintrc.yaml
.eslintrc.yml
.eslintrc.json
.eslintrc
package.json
以 .eslintrc.json
为例,常见的核心配置项如下:
.eslintrc.json
{
// 解析器
"parser": "esprima",
// 定义一组预定义的全局变量
"env": { "es6": true },
// 解析器选项(不同的解析器选项可能会不同)
"parserOptions": {
// ECMAScript版本(只启用语法,不启用新全局变量,如Set)
"ecmaVersion": 5,
// 额外的语言特性
"ecmaFeatures": {},
// ECMAScript模块
"sourceType": "module"
},
// 可共享的配置包
"extends": [],
// 插件提供额外的校验规则
"plugins": [],
// 具体的规则
"rules": {
// ...
}
}
关于 extends
和 plugins
:
传入 plugins
选项的插件包会提供额外的规则实现,原理是在 eslint
将代码解析为AST后将上下文传入规则对应的回调函数,回调函数会进行一系列的校验操作。
传入 extends
选项的扩展包本身并不提供额外的规则实现,仅仅是提供一个预设的 .eslintrc.json
与本地的 .eslintrc.json
进行增量合并(本地的配置项优先级高于扩展包) 。如果传入多个扩展包的数组,相同的配置项后者会覆盖前者。
扩展包
eslint-config-standard
源码
{
"parserOptions": {
"ecmaVersion": 2021,
"ecmaFeatures": {
"jsx": true
},
"sourceType": "module"
},
"env": {
"es2021": true,
"node": true
},
"plugins": [
"import",
"node",
"promise"
],
"globals": {
"document": "readonly",
"navigator": "readonly",
"window": "readonly"
},
"rules": {
"no-var": "warn",
// ...
}
}
因此本地安装依赖后,只需要在配置文件中声明扩展,无需任何额外配置项。需要注意的是,扩展包一般会将依赖声明为 peerDependencies
,npm
不同版本对于 peerDependencies
处理方式不太一样。
本地 .eslintrc.json
{
"extends": ["standard"] // eslint-config- 可省略
}
ESLint
setting.json
,添加如下配置{
"vetur.format.enable": false, // 关闭其他插件的format
"editor.tabSize": 2,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true // 保存的时候fix可以格式化的eslint问题
},
"editor.formatOnSave": true, // 开启保存格式化
"eslint.format.enable": true, // 将eslint作为formatter
"eslint.validate": [ // 哪些语言需要被eslint校验
"javascript",
"javascriptreact"
],
}
standard + standard-jsx
npm install --save-dev eslint-config-standard eslint-config-standard-jsx eslint-plugin-promise eslint-plugin-import eslint-plugin-node eslint-plugin-react
react-hooks
npm install --save-dev eslint-plugin-react-hooks
jsx-a11y
npm install --save-dev eslint-plugin-jsx-a11y
.eslintrc.json
{
"extends": [
"standard",
"standard-jsx"
],
"plugins": [
"react-hooks",
"jsx-a11y"
],
"rules": {
"no-console": "warn",
"comma-dangle": [
"error",
"always-multiline"
]
}
}
standard-with-typescript
npm install --save-dev eslint@7 eslint-plugin-promise@4 eslint-plugin-import@2 eslint-plugin-node@11 @typescript-eslint/eslint-plugin@4 eslint-config-standard-with-typescript
react + react-hooks + jsx-a11y
npm install --save-dev eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y
tsconfig.json
{
"compilerOptions": {
"target": "es6",
"strict": true,
"module": "esnext",
"moduleResolution": "node",
"experimentalDecorators": true,
"jsx": "react",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"lib": [
"es2015",
"es2016",
"es2017",
"dom"
],
"resolveJsonModule": true
},
"includes": [
"./declare/externals.d.ts"
],
"exclude": [
"node_modules"
]
}
.eslintrc.json
{
"extends": [
"standard-with-typescript"
],
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": [
"react",
"react-hooks",
"jsx-a11y"
],
"rules": {
"no-console": "warn",
"comma-dangle": [
"error",
"always-multiline"
]
}
}
babel-eslint + eslint-plugin-vue
npm install --save-dev eslint-plugin-vue babel-eslint
airbnb-base
npx install-peerdeps --dev eslint-config-airbnb-base
.eslintrc.js
module.exports = {
root: true,
env: {
node: true,
},
extends: ['plugin:vue/recommended', 'airbnb-base'],
parserOptions: {
parser: 'babel-eslint',
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'import/no-unresolved': 'off',
'vue/max-attributes-per-line': ['error', {
singleline: 3,
multiline: {
max: 1,
allowFirstLine: false,
},
}],
},
};
需要注意,空白单元可能会导致出人意料的结果,和将数组元素显式赋值为 undefined
是有所区别的(在不同浏览器上表现也不一致)
var a = []
a[0] = 1
a[2] = 3
a[1] // undefined
a.length // 3
由于数组也是对象,所以也可以包含字符串键,但是不会被计算在数组长度内。如果字符串键能被强制类型转换为十进制数字的话,它会被当做数字键处理
var a = []
a[0] = 1
a['foo'] = 'bar'
a.length // 1
a['foo'] // bar
a.foo // bar
a['1'] = 3
a.length // 2
a[1] // 3
一些DOM查询操作,或是 arguments
对象得到的都是类数组,可以将他们转化为真正的数组。
function foo(a, b) {
var arr = Array.prototype.slice.call(arguments)
// ES6: var arr = Array.from(arguments)
arr.push('bam')
console.log(arr)
}
foo('bar', 'baz') // ['bar', 'baz', 'bam']
javascript中字符串是不可变更的,即字符串的成员函数不会改变其原始值,而总是创建并返回一个新的字符串。同时字符串也是类数组,可以借用数组的非变更方法来处理字符串
var str = 'abc'
var arr = Array.prototype.slice.call(str)
arr // ['a', 'b', 'c']
var newStr = Array.prototype.join.call(str, '-')
newStr // 'a-b-c'
如果借用数组的变更方法来处理字符串(和书中结论不一致,推测是最新浏览器js引擎对字符串对象的键 setter
做了限制)
var str = Array.prototype.reverse.call('abc')
// Uncaught TypeError: Cannot assign to read only property '0' of object '[object String]'
javascript只有一种数值类型:number,没有真正意义上的整数。javascript中的整数即没有小数的十进制数,所以 42.0 === 42
。
特别大和特别小的的数字默认用指数格式显示,与 toExponential()
函数输出的结果相同。
var a = 5e10
a // 50000000000
a.toExponential() // '5e+10'
toFixed()
方法可以指定小数部分的显示位数,但是需要注意,返回的是给定数字处理后的字符串
var a = 42.59
a.toFixed(0) // '42'
a.toFixed(1) // '42.6'
a.toFixed(2) // '42.59'
a.toFixed(3) // '42.590'
a.toFixed(4) // '42.5900'
toPrecision()
方法用来指定有效数位的显示位数,同样返回的是字符串
var a = 42.59
a.toPrecision(1) // '4e+1'
a.toPrecision(2) // '43'
a.toPrecision(3) // '42.6'
a.toPrecision(4) // '42.59'
a.toPrecision(5) // '42.590'
a.toPrecision(6) // '42.5900'
数字字面量也可以使用相应的方法(运算时字面量会被Number对象封装),但是需要注意 .
运算符,因为它是一个有效的数字字符
42.toFixed(3) // SyntaxError
(42).toFixed(3) // '42.000'
0.42.toFixed(3) // '0.420'
42..toFixed(3) // '42.000'
42 .toFixed(3) // '42.000'
数字字面量同样可以表示二进制、八进制、十六进制,推荐表示进制的字符统一采用小写
0xf3 // 243 十六进制
0o363 // 243 八进制
0b11110011 // 243 二进制
0363 // 243 八进制,但是严格模式下已经不支持,不建议使用
二进制浮点数(IEEE 754规范)最大的问题是,小数并不精确
0.1 + 0.2 === 0.3 // false
可以通过设置一个误差值的方式来处理小数,通常称为机器精度(machine epsilon)。在javascript中,这个值是 2^-52(2.220446049250313e-16)
,在ES6中,这个值被定义在 Number.EPSILON
中。
if(!Number.EPSILON) {
Number.EPSILON = Math.pow(2, -52)
}
function numberEqual(n1, n2) {
return Math.abs(n1 - n2) < Number.EPSILON
}
numberEqual(0.1 + 0.2, 0.3) // true
能够呈现的最大浮点数大约是 1.798e+308
,它定义在 Number.MAX_VALUE
中。最小浮点数大约是 5e-324
,它定义在 Number.MIN_VALUE
中。能够被安全呈现的最大整数是 2^53 - 1
,在ES6中,被定义在 Number.MAX_SAFE_INTEGER
,同理最小整数是 Number.MIN_SAFE_INTEGER
。可以通过 Number.isInteger
和 Number.isSafeInteger
来检测一个数是否是整数/安全整数。
需要注意的是,有些数字操作(如数位操作)只适用于32位数字,此时数字的安全范围为 Math.pow(-2, 31) 到 Math.pow(2, 31) - 1
在非严格模式下,可以为全局标识符 undefined
赋值(千万不要这么做!)
function foo() {
undefined = 2
}
function bar() {
'use strict'
undefined = 2 // TypeError
}
运算符 void
并不改变表达式的结果,只是让表达式不返回值
var a = 42
console.log(void a, a) // undefined 42
void
使用场景:
function doSomething() {
if(!APP.ready) {
return void setTimeout(doSomething, 100) // 和下方注释代码等价
// setTimeout(doSomething, 100)
// return
}
// 返回处理完成后的结果
var result
return result
}
if(doSomething()) {
// 继续做其他事情
}
NaN(not a number)
是唯一一个非自反的值,即 NaN !== NaN
。检查一个数值是否是 NaN
有两种方式,其中全局 isNaN
函数有个严重的缺陷,应该使用ES6提供的 Number.isNaN
,或者通过唯一一个非自反的值这个特性来判断
var a = 2 / 'foo'
var b = 'bar'
window.isNaN(a) // true
window.isNaN(b) // true
Number.isNaN(a) // true
Number.isNaN(b) // false
在JavaScript中,存在无穷数 Infinity(Number.POSITIVE_INFINITY) 和 -Infinity(Number.NEGATIVE_INFINITY)
,当运算结果溢出时,会在无穷数和最大数之间做一个“就近取整”。同时 Infinity / Infinity
是一个未定义操作,结果为 NaN
var a = Number.MAX_VALUE // 1.7976931348623157e+308
a + Math.pow(2, 970) // Infinity (距离Infinity更近)
a + Math.pow(2, 969) // 1.7976931348623157e+308 (距离Number.MAX_VALUE更近)
0
在JavaScript中是区分正负的。这在某些场景下是必要的,例如“动画帧的移动速度”,数字的符号位可以用来记录移动方向,假设某个时刻速度值为 0
,那么保留正负号不会丢失移动方向。
var a = 0 / -3 // -0
var b = 0 // 0
a.toString() // '0'
a + '' // '0'
String(a) // '0'
JSON.stringify(a) // '0'
// 有意思的是,反向操作是正确的
+ '-0' // -0
Number('-0') // -0
JSON.parse('-0') // -0
// 比较操作
a == b // true
a === b // true
b > a // false
// 区分0和-0
function isNegZero(n) {
return (n === 0) && (1 / n === -Infinity)
}
isNegZero(a) // true
isNegZero(b) // false
ES6中,可以通过 Object.is()
来判断两个值是否绝对相等,可以借用这个方法来判断特殊数值(性能比运算符差)
var a = 2 / 'foo'
var b = 0 / -1
Object.is(a, NaN) // true
Object.is(b, -0) // true
JavaScript中没有指针,变量不可能成为另一个变量的引用,引用指向的是某一个具体的值。赋值和引用完全根据值的类型来决定:null, undefined, string, number, boolean, symbol
总是通过值复制的方式赋值/传递,object(以及其各种子类型 function, array, regexp)
总是通过引用复制的方式来赋值/传递。
由于引用指向的是值而非变量,所以一个引用无法更改另一个引用的指向
var a = [1]
var b = a
a // [1]
b // [1]
b = [2]
a // [1]
b // [2]
对于函数参数也是同理
function foo(x) {
x.push(4)
x // [1, 2, 3, 4]
x = [4, 5, 6]
x.push(7)
x // [4, 5, 6, 7]
}
var a = [1, 2, 3]
foo(a)
a // [1, 2, 3, 4]
需要注意的是,因为基本类型值是不可更改的,所以对应对象子类型在引用时,如果遇到值发生变化的情景,会解除引用并将变化的值转化为基本类型(而非基本类型对应的对象子类型)
var a = new Number(1)
b = a
a // Number{1}
b // Number{1}
typeof a // 'object'
b = b + 1
b // 2
a // Number{1}
typeof b // 'number'
给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 'a','e','i','o','u' ,在子字符串中都恰好出现了偶数次。
示例 1:
输入:s = "eleetminicoworoep"
输出:13
解释:最长子字符串是 "leetminicowor" ,它包含 e,i,o 各 2 个,以及 0 个 a,u 。
示例 2:
输入:s = "leetcodeisgreat"
输出:5
解释:最长子字符串是 "leetc" ,其中包含 2 个 e 。
示例 3:
输入:s = "bcbcbc"
输出:6
解释:这个示例中,字符串 "bcbcbc" 本身就是最长的,因为所有的元音 a,e,i,o,u 都出现了 0 次。
提示:
1 <= s.length <= 5 x 10^5
s 只包含小写英文字母。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-the-longest-substring-containing-vowels-in-even-counts
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
首先考虑暴力算法,单是找出字符串 s
的所有子串,即 [i, j] ,其中 0 <= i < j < s.length
因为前后两个变量,必然需要双层循环,在s.length <= 5*10^5 前提下,效率肯定不行。
以在 str = 'baaca'
这个字符串判断 'a'
在子串中数量举例
通过一次遍历可得:
假设想求得 [2, 4]
这个子串中 'a'
的数量
用 pre[5] - pre[2](前5个字符中a的数量 - 前2个字符中a的数量)
即可很方便得到数量为2
因此,如果在遍历时维护一个前缀和对应的各元音出现次数,可以直接计算出某一子串中各元音出现次数。
但是每得到一个前缀和,都需要和之前的前缀和比对,来不断更新最大长度
奇数 - 奇数 = 偶数 - 偶数 = 偶数
同样以上述 str = 'baaca'
举例
假设想求得 [2, 4]
这个子串中 'a'
的奇偶性
用 pre[5] 对比 pre[2](前5个字符中a的奇偶性 对比 前2个字符中a的奇偶性)
相同为偶,不相同为奇
推而广之,
假设 pre[i] 中奇偶性为 { 'a': 奇, 'e': 偶, 'i': 奇, 'o': 偶, 'u': 奇 }
并且 pre[j] 中奇偶性为 { 'a': 奇, 'e': 偶, 'i': 奇, 'o': 偶, 'u': 奇 } (0 <= i < j <= str.length)
那么可以得到 [i, j - 1]
这个子串中各元音出现次数均为偶数
转化下思路,
当一种奇偶组合第一次出现时,记录当前的前缀和个数 i
当相同组合再次出现时,当前的前缀和个数 j
此时可得到对应的子串为 [i, j - 1]
,长度为 j - i
如果长度比当前维护的最大长度大,则更新
{ 'a': 奇, 'e': 偶, 'i': 奇, 'o': 偶, 'u': 奇 } 这种存储结构比对起来很不方便,也没法作为键值,如何优化?
对于n个key,且每个key只对应两种状态,很容易想到bitMap来存储
令 奇 = 1,偶 = 0
利用按位异或来不断切换对应的奇偶性,
且将第一次出现的奇偶组合、对应的当前前缀和个数作为 key: value
存储起来
当bitMap对应的值已经存在时,即当前奇偶组合已经出现过
var findTheLongestSubstring = function(s) {
let res = 0;
let bitMap = 0; // 当前前缀和的5个元音奇偶表
const view = { // 对照表
'a': 1 << 0,
'e': 1 << 1,
'i': 1 << 2,
'o': 1 << 3,
'u': 1 << 4,
}
const posMap = {}; // 记录所有奇偶可能性第一次出现的前缀和个数
posMap[0] = 0; // 00000这种奇偶组合在0个前缀和中出现
for(let i = 0; i < s.length; i++) {
if(view[s[i]]) {
bitMap ^= view[s[i]]; // 更新前缀和元音奇偶表
}
if(posMap[bitMap] >= 0) {
// 如果posMap中已经出现过这种奇偶组合
// 则当前前缀和个数 - posMap中记录的前缀和个数这一子串肯定符合条件,更新res
res = Math.max(res, i + 1 - posMap[bitMap]);
}
else {
// posMap中没出现过这种奇偶组合,则记录下当前前缀和个数
posMap[bitMap] = i + 1;
}
}
return res;
};
给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
示例 1:
输入: nums: [1, 1, 1, 1, 1], S: 3
输出: 5
解释:
-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3
一共有5种方法让最终目标和为3。
注意:
数组非空,且长度不会超过20。
初始的数组的和不会超过1000。
保证返回的最终结果能被32位整数存下。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/target-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
sum = 0
;i === nums.length
时,对比和 target
是否相等var findTargetSumWays = function(nums, S) {
let count = 0;
function calculateSum(nums, i, sum, target) {
if(i === nums.length) {
if(target === sum) {
count++;
}
}
else {
calculateSum(nums, i + 1, sum + nums[i], target);
calculateSum(nums, i + 1, sum - nums[i], target);
}
}
calculateSum(nums, 0, 0, S);
return count;
};
时间复杂度过高
dp[i][j]
表示数组中的前 i + 1
个元素,相加和为 j
的方案数;dp[i][j] = dp[i - 1][j - nums[i]] + dp[i - 1][j + nums[i]]
-1000 <= j <= 1000
,需要给 dp
第二维预先增加1000;dp[nums.length - 1][S + 1000]
即代表前 nums.length
个元素,相加和为 S
的方案数;var findTargetSumWays = function(nums, S) {
const dp = new Array(nums.length).fill(null).map(item => {
return new Array(2001).fill(0);
});
dp[0][nums[0] + 1000] = 1;
dp[0][-nums[0] + 1000] += 1;
for(let i = 1; i < nums.length; i++) {
for(let j = 0; j < 2001; j++) {
if(dp[i - 1][j + nums[i]] > 0) {
dp[i][j] += dp[i - 1][j + nums[i]];
}
if(dp[i - 1][j - nums[i]] > 0){
dp[i][j] += dp[i - 1][j - nums[i]]
}
}
}
return dp[nums.length - 1][S + 1000] ? dp[nums.length - 1][S + 1000] : 0;
};
轻点来点按
;将 跟踪速度
设置为最大三指拖移
桌面
左下角设置为 通知中心
大小
,并关闭 放大
动画大部分命令行工具安装需要使用外网环境,需要科学上网或者切换国内镜像
从 MacOS High Sierra,Sierra 开始,无需安装整个 Xcode 软件包,就可以单独安装 Command Line Tools
。
xcode-select --install
git version
macOS 自带 zsh
,但是老版本系统可能默认使用的是 bash
查看当前使用的 shell
:
echo $SHELL
# /bin/bash 默认使用的是bash
# /bin/zsh 默认使用的是zsh
修改默认 shell
为 zsh
:
chsh -s /bin/zsh
当然也可以修改回 bash
chsh -s /bin/bash
zsh
对应的配置文件路径:~/.zshrc
oh my zsh
是对 zsh
的一个增强,可以配置许多插件、主题,并且内置了许多有用的函数(没有用到)
通过 curl
安装:
sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
通过 wget
安装:
sh -c "$(wget https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh -O -)"
增加语法高亮和自动补全(ctrl + F 采纳)插件:
cd ~/.oh-my-zsh/custom/plugins
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git
git clone https://github.com/zsh-users/zsh-autosuggestions.git
编辑 .zshrc
,添加如下内容:
plugins=(
git
zsh-autosuggestions
zsh-syntax-highlighting
)
激活配置:
source ~/.zshrc
使用iTerm2的原因主要是可以配置一个比较舒服的命令行界面 iTerm2下载地址
安装PowerFonts:
cd ~/Documents && mkdir itermConfig && cd itermConfig # 创建一个iTerm2配置的目录
git clone https://github.com/powerline/fonts.git --depth=1 # 下载字体
cd fonts
./install.sh # 安装
设置字体:
iTerm2 → Preferences → Profiles → Text,在Font区域下拉菜单中选中 Meslo LG S for Powerline
安装配色方案(最新的iTerm2貌似自带):
cd ~/Documents/itermConfig && git clone https://github.com/altercation/solarized
cd solarized/iterm2-colors-solarized/
open . # 有明暗两种配色方案的 .itermcolors 文件,双击即可安装
设置配色:
iTerm2 → Preferences → Profiles → Colors → Color Presets,选中 Solarized Dark
安装主题:
cd ~/Documents/itermConfig && git clone https://github.com/fcamblor/oh-my-zsh-agnoster-fcamblor.git
cd oh-my-zsh-agnoster-fcamblor/
./install
设置主题:
编辑 .zshrc
,修改 ZSH_THEME
配置项为 agnoster
激活配置:
source ~/.zshrc
另外可以根据个人习惯设置背景透明度或背景图片:
iTerm2 → Preferences → Profiles → Window
最终效果图:
配置多 ssh
环境:
cd ~ && mkdir .ssh # 如果不存在的话创建.ssh目录
ssh-keygen -t rsa -C "[email protected]" # 根据工作邮箱创建工作仓库秘钥对(github同理)
Enter file in which to save the key (/Users/maoqunzhi/.ssh/id_rsa): /Users/maoqunzhi/.ssh/id_rsa_work
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
操作完成后 .ssh
目录下有两对秘钥(将公钥上传至对应的git仓库):
id_rsa_work
id_rsa_work.pub
id_rsa_github
id_rsa_github.pub
配置 ssh
:
cd ~/.ssh && touch config # 创建配置文件
添加如下内容:
个人建议保持host和host别名一致,因为目前无论是github还是gitlab都提供复制仓库地址的功能,使用别名反而增加了操作复杂度
Host github.com
HostName github.com
User qunzi0214
IdentityFile ~/.ssh/id_rsa_github
Host company.net
HostName company.net
User maoqunzhi
IdentityFile ~/.ssh/id_rsa_work
测试是否配置成功:
ssh -T [email protected]
Hi qunzi0214! You've successfully authenticated, but GitHub does not provide shell access.
编辑 .zshrc
,加入两条快捷配置项目 git 用户名邮箱的 alias
alias git-config-work="git config user.name \"maoqunzhi\" && git config user.email \"[email protected]\" && echo \"Change git config work success\""
alias git-config-github="git config user.name \"qunzi0214\" && git config user.email \"[email protected]\" && echo \"Change git config github success\""
激活配置:
source ~/.zshrc
新建当前用户全局 git 配置文件:
cd ~ && touch .gitconfig
增加如下配置:
[alias]
br = branch
co = checkout
ci = commit
st = status
df = diff --stat
lg = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
homebrew
是一个 macOS 或 Linux 下的命令行工具管理器:
将 homebrew
安装至当前用户根目录下:
cd ~ && mkdir homebrew && curl -L https://github.com/Homebrew/brew/tarball/master | tar xz --strip 1 -C homebrew
编辑 ~/.zshrc
,添加 homebrew
环境变量(修改为你自己的路径):
export PATH=/Users/maoqunzhi/homebrew/bin:$PATH
激活设置:
source ~/.zshrc
tmux
是一个终端增强工具,支持窗口与会话分离以及窗口水平垂直切割
通过 homebrew
安装:
brew install tmux
下载 tmux
插件管理器:
git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm # 插件管理器
创建配置文件 .tmux.conf
cd ~ && touch .tmux.conf
添加如下配置:
set -g prefix C-a
unbind C-b
set -g mouse on
# List of plugins
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-sensible'
set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @plugin 'tmux-plugins/tmux-yank'
# Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf)
run '~/.tmux/plugins/tpm/tpm'
激活配置:
tmux source-file ~/.tmux.conf
修改 iTerm2 配置:
iTerm2 → Preferences → General → Selection:勾选 Applications in terminal may access clipboard
进入 tmux
,使用快捷键 prefix(ctrl + A) + I
安装配置文件中的插件
tree
命令可以在撰写文档的时候快速生成目录树结构
通过 homebrew
安装:
brew install tree
配置 .zshrc
,添加如下 alias
:
alias show-dir="tree -L 3 -a -I (node_modules|dist)"
使用效果:
show-dir
.
├── package.json
├── src
│ ├── index.js
│ └── index.ts
└── yarn.lock
承包了我一上午笑点的毛子哥项目,遇事不决请 fuck
一下~
通过 homebrew
安装:
brew install thefuck
nvm
是一个 node 版本管理工具,可以切换不同版本的 nodejs,经手的项目时间跨度很大时,非常有用
通过 homebrew
下载:
brew install nvm
编辑 .zshrc
,添加如下内容(修改为你自己的路径):
# nvm config
export NVM_DIR="$HOME/.nvm"
. "/Users/maoqunzhi/homebrew/opt/nvm/nvm.sh"
#export NVM_NODEJS_ORG_MIRROR=https://npm.taobao.org/mirrors/node
墙裂推荐通过 nvm
下载 node
!!!
nvm install x.y.z
nrm
是一个 npm
镜像源管理器,不过如果项目是多人合作或要走流水线上生产环境,推荐使用 .npmrc
或 .yarnrc
文件来统一项目依赖的 npm
站点
通过 npm
安装:
npm install -g nrm
使用方式和 npm
大同小异,执行速度略快
通过 npm
安装:
npm install -g yarn
设置通过 code
从命令行打开 vscode
:
编辑 ~/.zshrc
,添加 vscode
环境变量(修改为你自己的安装路径):
export PATH=/Applications/Visual\ Studio\ Code.app/Contents/Resources/app/bin:$PATH
激活设置:
source ~/.zshrc
安装开发常用插件:
settings.json
添加如下配置:
{
"vetur.format.enable": false,
"editor.tabSize": 2,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"editor.formatOnSave": true,
"eslint.format.enable": true,
"eslint.validate": [
"javascript",
"javascriptreact"
],
"explorer.autoReveal": false,
"diffEditor.ignoreTrimWhitespace": false,
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": "active",
"editor.unicodeHighlight.ambiguousCharacters": false,
"editor.inlineSuggest.enabled": true,
"github.copilot.enable": {
"*": true,
"yaml": false,
"plaintext": true,
"markdown": true,
"rust": false
},
"window.zoomLevel": 1,
"git.mergeEditor": false
}
快速打开一个禁用同源策略、开启控制台的标签页:
编辑 ~/.zshrc
,添加 alias
(需要在相应位置创建 MyChromeDevUserData
目录):
alias chrome-disabled="open -n /Applications/Google\ Chrome.app/ --args --disable-web-security --auto-open-devtools-for-tabs --user-data-dir=/Users/maoqunzhi/MyChromeDevUserData/"
chrome
插件:
描述一个函数最简单的方式是用 function type expression
,语法上和箭头函数非常像:
function greeter(fn: (a: string) => void) {
fn("Hello, World");
}
function printToConsole(s: string) {
console.log(s);
}
greeter(printToConsole);
语法 (a: string) => void
表示一个“第一个入参为字符串类型,且没有返回值”的函数。如果参数没有指定类型,那么它会隐式推断为 any
注意,
(string) => void
是指一个“第一个入参为任意类型,且没有返回值”的函数
可以通过类型别名来给一个函数类型命名:
type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) {
// ...
}
在 Javascript 中,函数除了可以被调用,也可以拥有属性。但是函数类型表达式并不能声明函数的属性。如果想要处理这种情况,可以使用 call signature
type DescribableFunction = {
description: string;
(someArg: number): boolean;
};
function doSomething(fn: DescribableFunction) {
console.log(fn.description + " returned " + fn(6));
}
Javascript 中的函数,可以通过 new
操作符当做构造函数使用。Typescript中,可以通过在调用签名前增加 new
关键字,声明一个 construct signature
type SomeConstructor = {
new (s: string): SomeObject;
};
function fn(ctor: SomeConstructor) {
return new ctor("hello");
}
Javascript 某些对象的构造函数也可以支持不通过 new
关键字调用(例如 Date
对象)。Typescript 中,可以给一个函数类型结合调用签名和构造签名:
interface CallOrConstruct {
new (s: string): Date;
(n?: number): number;
}
很多场景下,需要关联函数的输入值输出值类型,或者以某种方式关联两个输入值的类型。考虑如下函数,返回输入数组(不确定数组元素类型)的第一个元素:
function firstElement(arr: any[]) {
return arr[0];
}
问题在于,返回值被推论为 any
类型。最好是能够让返回值类型和数组元素类型关联起来
在 Typescript 中,可以通过 generics
关联两个值的类型,仅需要定义一个 type parameter
:
function firstElement<Type>(arr: Type[]): Type {
return arr[0];
}
定义一个类型参数 Type
,并且在两个位置使用这个参数,会创建一个输入值和输出值的链接,此时在调用该函数时,可以获得一个更具体的类型:
// s is of type 'string'
const s = firstElement(["a", "b", "c"]);
// n is of type 'number'
const n = firstElement([1, 2, 3]);
注意这里,在调用时并不需要指定类型参数 Type
的具体值,Typescript 会自动类型推论。
当然也可以同时使用多个类型参数,例如,一个独立的 map
函数可能长这样:
function map<Input, Output>(arr: Input[], func: (arg: Input) => Output): Output[] {
return arr.map(func);
}
// Parameter 'n' is of type 'string'
// 'parsed' is of type 'number[]'
const parsed = map(["1", "2", "3"], (n) => parseInt(n));
泛型函数可以让我们操作任意类型的值。但是某些场景下,我们希望关联两个值,同时希望它们有确定的子集,这时,需要用到 constraint
来限制泛型可以接受的类型
思考如下函数,接受两个任意类型的值,返回 length
更大的那个值。想要做到这点,可以使用 extends
语法:
function longest<Type extends { length: number }>(a: Type, b: Type) {
if (a.length >= b.length) {
return a;
} else {
return b;
}
}
// longerArray is of type 'number[]'
const longerArray = longest([1, 2], [1, 2, 3]);
// longerString is of type 'string'
const longerString = longest("alice", "bob");
// Error! Numbers don't have a 'length' property
const notOK = longest(10, 100);
// Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
使用泛型函数时,很容易发生如下错误:
function minimumLength<Type extends { length: number }>(
obj: Type,
minimum: number
): Type {
if (obj.length >= minimum) {
return obj;
} else {
return { length: minimum };
// Type '{ length: number; }' is not assignable to type 'Type'.
// '{ length: number; }' is assignable to the constraint of type 'Type', but 'Type' could be instantiated with a different subtype of constraint '{ length: number; }'.
}
}
看起来好像是没什么问题的:Type
被约束为必须满足 { length: number }
,同时此函数只会返回 Type
类型或是某个匹配约束条件的值。
问题在于,函数承诺返回值类型和第一个参数值类型是相同的,而非仅仅是某个“能匹配约束条件”的值。哪怕上述代码是合法的,在运行时调用也会出现问题:
// 'arr' gets value { length: 6 }
const arr = minimumLength([1, 2, 3], 6);
// and crashes here because arrays have
// a 'slice' method, but not the returned object!
console.log(arr.slice(0));
Typescript 通常可以在泛型函数中推断出合适的参数类型,但有些情况下不行,思考如下代码:
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
return arr1.concat(arr2);
}
const arr = combine([1, 2, 3], ["hello"]);
// Type 'string' is not assignable to type 'number'.
此时只能显式的指定 Type
:
const arr = combine<string | number>([1, 2, 3], ["hello"]);
编写合理优雅的泛型函数,需要遵循以下最佳实践:
function firstElement1<Type>(arr: Type[]) {
return arr[0];
}
function firstElement2<Type extends any[]>(arr: Type) {
return arr[0];
}
// a: number (good)
const a = firstElement1([1, 2, 3]);
// b: any (bad)
const b = firstElement2([1, 2, 3]);
以上两种方式看似相同,但实际上 firstElement1
才是正确的,因为此时返回值类型被合理推断为 Type
。在 firstElement2
中,Typescript 会根据类型约束将返回值类型推断为 any
function filter1<Type>(
arr: Type[],
func: (arg: Type) => boolean
): Type[] {
return arr.filter(func);
}
function filter2<Type, Func extends (arg: Type) => boolean>(
arr: Type[],
func: Func
): Type[] {
return arr.filter(func);
}
在 filter2
中,类型参数 Func
并没有关联两个不同的值。同时,调用者在想要显式指定类型参数时,必须要多额外指定一个(尽管 Func
毫无作用),这导致代码难以阅读和理解
function greet<Str extends string>(s: Str) {
console.log("Hello, " + s);
}
greet("world");
为什么不直接使用更简单的方式:
function greet(s: string) {
console.log("Hello, " + s);
}
在 Javascript 中,函数可以接受不确定数量的参数。Typescript 中可以通过标记可选参数来模拟:
function f(x?: number) {
// ...
}
f(); // OK
f(10); // OK
虽然此例中 x
被指定为 number
类型,但实际上它是 number | undefined
类型。因为在 Javascript 中,没有传入的形参会接收到值 undefined
当然也可以指定默认参数:
function f(x: number = 10) {
// ...
}
现在在函数体内,x
的类型为 number
。因为任何 undefined
参数都会被替换为 10
当结合可选参数和函数类型表达式,很容易写出以下错误:
function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
for (let i = 0; i < arr.length; i++) {
callback(arr[i], i);
}
}
此例中回调函数接受一个 index
可选参数,通常这么写是因为希望以下两种调用方式都成立:
myForEach([1, 2, 3], (a) => console.log(a));
myForEach([1, 2, 3], (a, i) => console.log(a, i));
但实际上,这么声明的结果是,Typescript 会认为此回调函数只接收一个参数,导致调用时报错:
myForEach([1, 2, 3], (a, i) => {
console.log(i.toFixed());
// Object is possibly 'undefined'.
});
在 Javascript 中,调用一个函数时可以传递实参少于函数形参,多余的参数会被忽略。Typescript 同样如此,只要参数类型相同,可以为形参更少的函数声明形参更多的函数类型:(此处有待研究,应该是只有回调函数支持这种特性)
function myForEach(arr: any[], callback: (arg: any, index: number) => void) {
for (let i = 0; i < arr.length; i++) {
callback(arr[i], i);
}
}
// both ok
myForEach([1, 2, 3], (a) => {
console.log(a);
});
myForEach([1, 2, 3], (a, i) => {
console.log(a, i);
});
基于以上特点,当给一个回调函数编写函数类型时,永远不要使用可选参数。
许多 Javascript 函数可以被多种方式调用(动态的参数数量以及类型)。例如可以编写一个函数,此函数可以通过一个时间戳参数或是年/月/日(三个参数)来生成 Date
对象。在 Typescript 中,可以通过编写 overload signatures
让一个函数可以被多种方式调用:
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
if (d !== undefined && y !== undefined) {
return new Date(y, mOrTimestamp, d);
} else {
return new Date(mOrTimestamp);
}
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
const d3 = makeDate(1, 3);
// No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.
第0章 计算机概论
第1章 Linux是什么与如何学习
第2章 主机规划和磁盘分区
第3章 安装CentOS
第4章 首次登陆与在线求助
装机后实测:TODO
终端输入 gsettings set org.gnome.desktop.interface enable-animations false
Linux默认的情况下会提供六个操作接口环境(tty1 ~ tty6)供用户登录,切换:Ctrl + Alt + F1 ~ F6
如果在 tty3 上登录了系统,同时运行 startx
则图形界面会产生在 tty3 上
startx
生效条件:
$ command [-options] parameter1 parameter2 ...
命令组成:
-h
,完整选项是 --help
\
来转义紧接着的特殊字符,如回车$ date
2020年11月18日 星期三 10时46分07秒 CST
$ date +%Y/%m/%d
2020/11/18
某些选项前面是 -
,特殊情况下,也有些选项的前面是 +
$ cal [month] [year]
可以快速查看日历
$ bc
启动一个计算器环境,输入 scale=3
即保留小数点后3位,输入 quit
离开
tab
:在一串命令第一个字段后面是命令补全,非第一个字段后面是文件补全。安装了 Bash-completion
后,某些命令后面可以进行选项和参数补全Ctrl + c
:中断当前程序或输入Ctrl + d
:通常代表EOF(End Of File),也可以当做 exit
使用shift + PageUP/PageDown
:往前往后翻页--help
:命令的使用方法(zsh上并不支持)
man [command]
:打开该命令的 manual page
info [command]
:打开该命令的 info page
man/info 中命令代号 | 含义 |
---|---|
1 | 用户在shell环境可以操作的命令或可执行文件 |
2 | 系统内核可调用的函数与工具 |
3 | 常用的函数或函数库,大部分为C的函数库 |
4 | 设备文件的说明,通常在 /dev 下的文件 |
5 | 配置文件或者是某些文件的格式 |
6 | 游戏 |
7 | 惯例和协议,例如Linux文件系统,网络协议 |
8 | 系统管理员可以使用的命令 |
9 | 跟内核有关的文件 |
按键 | manual page | info page |
---|---|---|
空格 | 向下翻页 | 向下翻页 |
Page Down | 向下翻页 | 向下翻页 |
Page Up | 向上翻页 | 向上翻页 |
Home | 去第一页 | |
End | 去最后一页 | |
/string | 向下查找string这个字符串 | 查找字符串 |
?string | 向上查找string这个字符串 | |
n | 查找下一个字符串 | 前往下一个节点 |
N | 查找上一个字符串 | |
q | 退出 | 退出 |
Tab | 在节点之间移动 | |
Enter | 在节点上时,进入节点 | |
b | 移动光标到节点顶部 | |
e | 移动光标到节点底部 | |
p | 前往上一个节点 | |
u | 向上移动一级 | |
s | 查找字符串 | |
h,? | 显示帮助选项 |
总结:
--help
man
或 info
来查询/usr/share/doc
下面有一些服务或软件的说明文件与关机有关的常用操作:
who
:可以查看当前有谁在线netstat -a
:可以查看网络联机状态ps -aux
:可以查看目前主机使用状态sync
:将数据同步写入硬盘shutdown
:最常用关机命令reboot/halt/poweroff
:重新启动、关机等历史小梳理:Multics (1965, Bell MIT GE)
—> Unics (1969, Ken Thompson)
—> UNIX (1973, Dennis Ritchie)
—> Minix (1986, Andrew S.Tanenbaum)
—> Linux (1991, Linus Torvalds)
$ uname -r
查看内核版本,参考Linux官网RPM软件管理 | DPKG软件管理 | 其他 | |
---|---|---|---|
商业公司 | RHEL(Red Hat)、SUSE(Micro Focus) | Ubuntu(Canonical Ltd.) | |
网络社区 | Fedora、CentOS、openSUSE | Debian、B2D | Gentoo |
/usr/share/doc
/var/log/
查询 log file
在JavaScript中,需要区分语句和表达式,因为它们存在一些重要差别。语句相当于句子,表达式相当于短语,运算符则相当于标点符号和连接词。在JavaScript中,表达式会返回一个结果值
var a = 3 * 6
var b = a
// 3 * 6是一个表达式,第二行的a也是一个表达式
// 这两行都是包含表达式的声明语句
var a
var b = 18
a = b
// 这里第三行只是赋值表达式,结果值是b的值18
有如下两种情况:
var let const
语句的结果值是 undefined
代码块 {..}
的结果值是其最后一个表达式/语句的结果值
var b
if (true) {
b = 4 + 38
}
// chrome 控制台会输出 42
var a, b
a = if (true) {
b = 4 + 38
}
// 无法运行!
var a, b
a = eval('if (true) { b = 4 + 38 }')
a
// 新版浏览器会报错:Uncaught EvalError,与书中不符。不过一般也没人这么写
最常见有副作用的表达式是函数调用(函数调用也是一个表达式,结果值是该函数的返回值):
function foo () {
a = a + 1
}
var a = 1
foo() // 结果值undefined,副作用:a的值被改变
递增和递减运算符的副作用:
var a = 42
var b = a++
a // 43
b // 42
// 副作用将a自增1
++ or --
在前时,副作用产生在表达式返回结果前,在后时,副作用产生在表达式返回结果之后
可以使用语句系列逗号运算符,将多个独立的表达式语句串联成一个语句:
var a = 42, b
b = ( a++, a )
a // 43
b // 43
// 使用括号包起来是因为','运算符优先级比'='运算符优先级低
delete
运算符的副作用:
var obj = {
a: 42
}
obj.a // 42
delete obj.a // true
obj.a // undefined
// 表达式本身返回true或false来表示是否删除成功,副作用是删除了obj的a属性
=
运算符的副作用:
var a;
a = 42 // 42
a // 42
// 表达式本身返回42,副作用将42赋值给a
利用赋值表达式的副作用合并 if
条件
function vowels (str) {
var matches
if (str) {
matches = str.match(/[aeiou]/g)
if (matches) {
return matches
}
}
}
// 优化后
function vowels (str) {
var matches
if (str && (matches = str.match(/[aeiou]/g))) {
return matches
}
}
在不同的上下文中 {..}
的含义不尽相同
对象常量
var a = {
foo: bar()
}
代码块
{ // 这里并非对象常量,而是一个代码块
foo: bar() // 合法,标签语句(不建议使用)
}
标签语句最大的用处在于,双层循环的时候可以从内循环控制外循环
foo: for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
if (j === i) {
continue foo // 跳转到foo的下一个循环
}
console.log(i, j)
}
}
// 1 0
// 2 0
// 2 1
// 3 0
// 3 1
// 3 2
foo: for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
if (j * i >= 3) {
break foo // 终止外层循环
}
console.log(i, j)
}
}
// 0 0
// 0 1
// 0 2
// 0 3
// 1 0
// 1 1
// 1 2
一个常见的坑,原因如下:
{}
被算作出现在 +
运算符表达式中,[]
会被强制类型转换为 ''
,而 {}
被强制类型转换为 '[object object]'
{}
被当做一个空代码块,因此 +[]
被强制类型转换为 0
[] + {} // '[object object]'
{} + [] // 0
对象解构
function getData () {
return {
a: 42,
b: 'foo'
}
}
var { a, b } = getData()
console.log(a, b) // 42 'foo'
function foo ({ a, b, c }) {
console.log(a, b, c)
}
foo({
c: [1, 2, 3],
a: 42,
b: 'foo'
}) // 42 'foo' [1, 2, 3]
else if
和可选代码块实际上JavaScript中并没有 else if
语法,只不过 if
和 else
只包含单条语句的时候可以省略代码块的大括号
if (a) {
// ...
} else if (b) {
// ...
} else {
// ...
}
// 实际上是
if (a) {
// ...
} else {
if (b) {
// ...
} else {
// ...
}
}
需要注意:除了优先级,操作符的左关联 or 右关联也非常重要
JavaScript会自动为代码补上缺失的分号,(Automatic Semicolon Insertion, ASI)
ASI只在换行符处起作用,且这个行为只有在代码行末尾与换行符中间除了空格和注释没有其他内容,才会产生
var a = 42, b
c
// 这里c是一条单独的表达式语句
var a = 42, b,
c
// 这里ASI不会生效,c会被当做第一行声明语句的一部分
书中建议在所有需要的地方加上分号,对ASI依赖的程度降到最低,仅为了追求“代码的美观”不值得。然而这件事完全可以交给webpack在打包/编译时处理。时代变了啊大人: )
ES6中,参数可以设置默认值,但是需要注意暂时性死区(Temporal Dead Zone, TDZ)
function foo (a = 42, b = a + b + 5) {
// ...
}
// 报错!b的默认值在b声明前就调用了b!但是访问a没有问题
有参数默认值的情况下,参数被省略或被赋值为 undefined
参数取值是一样的,但是 arguments
会产生如下问题
function foo (a = 42, b = a + 1) {
console.log(
arguments.length,
a,
b,
arguments[0],
arguments[1]
)
}
foo() // 0 42 43 undefined undefined
foo(10) // 1 10 11 10 undefined
foo(10, undefined) // 2 10 11 10 undefined
foo(10, null) // 2 10 null 10 null
除此之外,向函数传递参数时,arguments
数组中的对应单元会和命名参数建立关联(严格模式下不关联)
function foo (a) {
a = 42
console.log(arguments[0])
}
foo(2) // 42 关联了
foo() // undefined 未关联
function foo (a) {
'use strict'
a = 42
console.log(arguments[0])
}
foo(2) // 2
foo() // undefined
因此,尽量不去使用 arguments
,本身它也是JavaScript语言底层实现的一个抽象泄露。ES6之后,完全可以用剩余参数语法来代替
try .. finally
关于执行顺序,这里 try
先执行,将函数的返回值设置为42,接着执行 finally
。最后函数执行完毕
function foo () {
try {
return 42
} finally {
console.log('hi')
}
console.log('never run')
}
console.log(foo())
// hi
// 42
如果包含 throw
也是如此
function foo () {
try {
throw 42
} finally {
console.log('hi')
}
}
console.log(foo())
// hi
// Uncaught Exception: 42
但是当 finally
抛出异常,则函数执行会终止,try
中的返回值会被丢弃
function foo () {
try {
return 42
} finally {
throw 'oops'
}
}
console.log(foo())
// Uncaught Exception: 'oops'
当 finally
存在返回值时,会覆盖 try
或 catch
中的返回值
function foo () {
try {
return 42
} finally {
return 43
}
}
console.log(foo())
// 43
甚至还可以将 finally
和带标签的 break
语句一起使用(千万不要)
function foo () {
bar: {
try {
return 42
} finally {
break bar
}
}
console.log('crazy')
return 'hi'
}
console.log(foo())
// 'crazy'
// 'hi'
switch
在 switch
语句中,变量会逐一与 case
表达式比较(严格相等)。需要注意的是,即使 case
表达式中使用了宽松相等,但是在 switch
内部仍然是严格相等的比较。
var a = 'hi'
var b = 10
switch (true) {
case (a || b == 10):
// 不会执行到这里,因为 a || b == 10 的结果为'hi',真值与true不严格相等
break;
default:
console.log('oops')
}
// 'oops'
指令精简,单个指令运行时间短,复杂指令需要多个指令来完成(Oracle的SPARC、IBM的Power Architecture、ARM的 ARM CPU)。PS3是Power Architecture,而手机一般是ARM CPU
指令数目多且复杂,可以处理的工作丰富(AMD,Intel,VIA等x86)。叫x86的原因是最早Intel研发的CPU代号8086,64位的个人电脑CPU又称为x86-64架构。位(bit)是指CPU一次可以读写的数据量
1TB = 1024GB = 1024 ^ 2 MB = 1024 ^ 3 KB = 1024 ^ 4 Byte = 8 * 1024 ^ 4 bit
CPU频率:1GHz = 1000 ^ 3 Hz
网络传输:20M/5M 其实是 20Mbit/s 5Mbit/s,理论上下载 2.5MB/s 上传 625KB/s
内存频率的限制主要来自CPU中的内存控制器,假设一个x86-64的CPU对内存的工作频率是 1600MHz,
那么理论上总线带宽 = 1600MHz * 64bit = 12.8GB/s
SDRAM/DDR | 型号 | 数据位宽(bit) | 内部频率(MHz) | 频率倍数 | 带宽 |
---|---|---|---|---|---|
SCRAM | PC133 | 64 | 133 | *1 | 1064MB/s |
DDR | DDR-266 | 64 | 133 | *2 | 2.1GB/s |
DDR | DDR-400 | 64 | 200 | *2 | 3.2GB/s |
DDR | DDR2-800 | 64 | 200 | *4 | 6.4GB/s |
DDR | DDR3-1600 | 64 | 200 | *8 | 12.8GB/s |
同理,目前已经很流行的DDR4频率为3200MHz,带宽可以达到 33.6GB/s(Double Data Rate, DDR)
使用SRAM来做成CPU内部的二级缓存(L2 Cache),把常用的程序或数据放入缓存中,可以大大提升性能
通常用来写入固件,曾经BIOS(Basic Input Output System)也是写入ROM中的。特点是没有通电时也能记录数据。
主要用来处理图像,显存容量会影响到屏幕分辨率和颜色深度。同时,有专门的图像处理芯片,用于加速3D运算需求。目前显卡的插槽基本都是PCIe(PCI-Express)
规格 | 1x带宽 | 16x带宽 |
---|---|---|
PCIe 1.0 | 250MB/s | 4GB/s |
PCIe 2.0 | 500MB/s | 8GB/s |
PCIe 3.0 | 1GB/s | 16GB/s |
PCIe 4.0 | 2GB/s | 32GB/s |
一般由主轴马达、机械手臂、磁片柱和磁头组成。数据存储在磁片中,读写数据需要转动磁片,因此存在读写速度存在物理限制
版本 | 带宽(Gbit/s) | 速度(MB/s) |
---|---|---|
SATA 1.0 | 1.5 | 150 |
SATA 2.0 | 3 | 300 |
SATA 3.0 | 6 | 600 |
因为 SATA 接口传输数据时,10bit 中仅 8bit 是数据,其余 2bit 作为校验用,因此换算(B转bit)是 1:10 而不是 1:8
和SATA接口一样,仅仅是理论值
版本 | 带宽(Mbit/s) | 速度(MB/s) |
---|---|---|
USB 1.0 | 12 | 1.5 |
USB 2.0 | 480 | 60 |
USB 3.0 | 5 * 1024 | 500 |
USB 3.1 | 10 * 1024 | 1000 |
最大的好处是,通过闪存直接读写的特性,不需要马达,无延时速度快且省电
早期想让电脑执行程序需要参考一堆硬件功能函数,学习机械语言,并且程序迭代困难。操作系统的出现解决了这一问题:操作系统是一组程序,重点在于管理电脑的所有活动以及驱动系统中的所有硬件。
对硬件的所有操作都由内核完成,内核程序通常在电脑启动后就常驻内存中,且在内存中的区块是受保护的
操作系统通常提供一套应用程序编程接口(Application Programming Interface, API),否则开发人员仅仅从参考硬件功能函数编程参考内核功能
综上所述,硬件规格 -> 系统内核 -> 操作系统 -> 系统调用 -> 应用程序,所以应用程序必须指定某一版本的操作系统
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.