0
点赞
收藏
分享

微信扫一扫

javascript高级程序设计(第四版)懒阅读:第三章

萨摩斯加士奇 2022-03-12 阅读 33

文章目录

(46-第三章20220218+a1043

语法

区分大小写

ECMAScript一切都区分大小写。

标识符

标识符就是变量、函数、属性或函数参数的名称。

标识符由一个或多个下列字符组成:

推荐:书写标识符的时候,一般建议使用驼峰大小写形式,即第一个单词首字母小写,后面每个单词的首字母大写,如

firstSecond
myCar
doSomethingImportant

注意

关键字、保留字、布尔值和null不能作为标识符。

注释

单行注释://

多行注释:/* */

严格模式

ES5增加了严格模式(strict mode)的概念。

开启严格模式后,一些不规范的代码写法会报错。

给整个脚本开启严格模式:

在脚本第一行加一句:

"use strict";

也可以给单独函数体开启严格模式:

在函数开头加一句:

function doSometing(){
  "use strict";
  //函数体
}

语句

语句后面的分号( ; )建议加上。

不加也没事,解析器会补上(稍微耗费性能)

(加分号减少报错的可能性)

还有尽量使用代码块,可以放内容更清晰减少报错的可能性。

if (test) {
 test = false;
 console.log(test);
} 
// 有效,但容易导致错误,应该避免
if (test)
 console.log(test);
// 推荐
if (test) {
 console.log(test);
} 

关键字与保留字

关键字不能作为标识符或属性名;

ES6以后关键字有:

ES6的保留字:

起标识符属性名避开就得了。

变量

var关键字

如果只定义变量,不赋值,则定义的变量为undefined。

变量定义后可以被重写:

var message="hi";
message=123;//可以,但是不建议。

var在函数内时的块级作用域

在函数内使用var声明变量,函数外无法访问

function test() {
	 var message = "hi"; // 局部变量
}
test();
console.log(message); // 出错!

但是有个不值得推荐的解决办法:

在函数内直接写变量名并赋值,这样写变量就变为全局变量,外部可以访问到

function test() {
 message = "hi"; // 全局变量
}
test();
console.log(message); // "hi" 

声明多个变量可以用逗号分隔:

var message = "hi",
 found = false,
 age = 29; 

注意变量名不要定义为eval和arguments。

var声明提升

使用var声明变量会自动提升到作用域顶部(仅仅只是变量名提升到顶部,但是并没有赋值)

function foo() {
 console.log(age);
 var age = 26;
}
foo(); // undefined 

上面代码等价于:

function foo() {
 var age;
 console.log(age);
 age = 26;
}
foo(); // undefined 

此外多次使用var声明同一变量也可以,相当于重写变量:

function foo() {
 var age = 16;
 var age = 26;
 var age = 36;
 console.log(age);
}
foo(); // 36 

let声明

let与var一样也是用于声明变量,但是let存在块级作用域,var没有块级作用域,但是var有函数作用域,比如:

if (true) {
 var name = 'Matt';
 console.log(name); // Matt
}
console.log(name); // Matt 

if (true) {
 let age = 26;
 console.log(age); // 26
}
console.log(age); 
// ReferenceError: age 没有定义

因为块级作用域是函数作用域子集,所以let也有函数作用域。

let不能重复声明:

var name;
var name;

let age;
let age; // SyntaxError;标识符 age 已经声明过了

js引擎会记录变量声明的标识符及其所在的作用域,所以可以嵌套使用相同标识符。

var name = 'Nicholas';
console.log(name); // 'Nicholas'
if (true) {
 var name = 'Matt';
 console.log(name); // 'Matt'
}

let age = 30;
console.log(age); // 30
if (true) {
 let age = 26;
 console.log(age); // 26
} 

对声明冗余报错不会因混用 let 和 var 而受影响。这两个关键字声明的并不是不同类型的变量,它们只是指出变量在相关作用域如何存在

var name;
let name; // SyntaxError
let age;
var age; // SyntaxError 

暂时性死区

let与var不一样,let声明变量之前引用变量会报错。(ReferenceError)

全局声明

let声明的变量不会称为window对象的属性(var声明的变量则会)

var name = 'Matt';
console.log(window.name); 
// 'Matt'

let age = 26;
console.log(window.age);
// undefined 

let的变量不是window对象的属性,但是let声明仍然存在于全局作用域中,对应的变量会在生命周期内存续。

条件声明

就是同一页面两段script标签。只要是在同一页面内,就不能用let声明第二次。会报错。但是var可以重复声明。

    <script>
        let name = 'Nicholas';
        let age = 36;
       </script>
       <script>
        // 假设脚本不确定页面中是否已经声明了同名变量
        // 那它可以假设还没有声明过
        if (typeof name === 'undefined') {
        let name;
        }
        // name 被限制在 if {} 块的作用域内
        // 因此这个赋值形同全局赋值
        name = 'Matt';
        try {
        console.log(age); // 如果 age 没有声明过,则会报错
        }
        catch(error) {
        let age; 
    }
 // age 被限制在 catch {}块的作用域内
 // 因此这个赋值形同全局赋值
 		age = 26;
</script> 

for循环中的let声明

如果for里面是个异步的逻辑,如定时器等,因为var没有块级作用域。所以,每次异步任务最终接收到的值都是for循环中计数器的最大值。

使用let不会,因为let有块级作用域,这样保持了每次接收的值都不一样。

for (let i = 0; i < 5; ++i) {
 setTimeout(() => console.log(i), 0)
}
// 会输出 0、1、2、3、4 

const声明

const用法与let一致。

不过const声明常量,声明过后不可修改。

const可以和for-of/for-in循环一起使用。

for (const key in {a: 1, b: 2}) {
 console.log(key);
}
// a, b

for (const value of [1,2,3,4,5]) {
 console.log(value);
}
// 1, 2, 3, 4, 5 

声明风格及最佳实践

不使用var;

let与const都能使用的时候,选const。

数据类型

ECMAScript的6种简单数据类型(原始数据类型):

  • Undefined
  • Null
  • Boolean
  • Number
  • String
  • Symbol(ES6新增)
  • 还有复杂数据类型Object

typeof 操作符

用来检测数据类型。

let message = "some string";
console.log(typeof message); // "string"
console.log(typeof(message)); // "string"
console.log(typeof 95); // "number" 

(补个:typeof 的返回值是以字符串来显示返回的数值类型。如:

console.log(typeof true)//这里以布尔值为例其他数值同理
//上面返回“Boolean”
console.log(typeof typeof true)//返回String

Undefined类型

光声明不赋值,最后变量为undefined;

变量值为undefined只是声明不赋值。如果直接不声明就输出,会报错;

测试代码:说明了声明了不赋值与不声明的区别。

let message; // 这个变量被声明了,只是值为 undefined
// age 没有声明
if (message) {
 // 这个块不会执行
}
if (!message) {
 // 这个块会执行
}
if (age) {
 // 这里会报错
} 

Null类型

null表示一个空对象指针。

只要变量要保存对象,但是还没有对象保存,就可以给变量加个null值表示该变量将来用来存储对象。

测试代码:说明了null与undefined的区别:

let message = null;
let age;
if (message) {
 // 这个块不会执行
}
if (!message) {
 // 这个块会执行
} 

if (age) {
 // 这个块不会执行
}
if (!age) {
 // 这个块会执行
} 

Boolean类型

有两个值:true和false;

注意区分大小写。

可以调用特定的Boolean()转型函数,将其他值转为布尔值;

其他数据类型转布尔值的转换规则:

数据类型转为true的值转换为false的值
Booleantruefalse
String非空字符串“”(空字符串)
Number非零数值(包括无穷值)0、NaN
Object任意对象null
UndefinedN/A(不存在)undefined

Number类型

十进制:

整数直接写出来

55

八进制:

八进制第一个数字必须是零(0),然后是响应的八进制数字(0~7)

070//有效八进制

079//无效

(八进制在严格模式下无效,会抛出错误)

十六进制:

前面必须写前缀 0x(区分大小写),然后是十六进制数字(09以及AF)。十六进制数字种的字母大小写均可。

0xA//十六进制10

0x1f//十六进制31

浮点值

存储浮点值使用的内存是存储整数值的两倍。所以ECMAScript会将数值尽可能转换为整数

注意:浮点值相加有bug,在js里0.1+0.2≠0.3

检测值在无限内

判断值是否是有限大的可以使用isFinite()函数

let result = Number.MAX_VALUE + Number.MAX_VALUE;
console.log(isFinite(result));
// false 

NaN

NaN:特殊值。意思为“不是数值”(Not a Number);

用于本来要返回的数值操作失败了(而不是抛出错误)

NaN不等于任何值,包括自己在内

判断一个值是否为NaN可以使用isNaN()函数来判断。

isNaN会将任何不能转为数值的值,返回true

console.log(isNaN(NaN)); // true
console.log(isNaN(10)); // false,10 是数值
console.log(isNaN("10")); // false,可以转换为数值 10
console.log(isNaN("blue")); // true,不可以转换为数值
console.log(isNaN(true)); // false,可以转换为数值 1 

数值转换规则

有3个函数可以将非数值转换为数值:

Number()、parseInt()和parseFloat();

Number()可以适用于任何数据类型;

parseInt()和parseFloat()主要用于将字符串转换为数值;

​ Number()函数转换规则:

  • 布尔值:true转换为1,false转换为0
  • 数值:直接返回
  • null:返回0
  • undefined:返回NaN
  • 字符串:应用如下规则:
    • 如果字符串包含数值字符,包括数值字符前面带加、减号的情况,则转换为一个十进制数值。
    • 如果字符串包含有效的浮点值如“1.1”则会转换为相应的浮点值
    • 如果字符串包含有效的十六进制格式如“0xf”则会将其转为相应的十进制整数
    • 如果是空字符串则返回0
    • 如果字符串包含除上述情况之外的其他字符,返回NaN.
  • 对象:调用ValueOf()方法,并按照上述规则转换返回的值。如果转换结果是NaN,则调用toString()方法,再按照转换字符串的规则转换。

parseInt()函数拿到数值之后,会在第一个非空字符串开始,如果碰到的第一个数值不为数值或加减号,则会立即返回NaN。(空字符串也会返回NaN);

如果parseInt碰到的第一个字符串为数值字符、加减号,则会继续一次检测每个字符直到字符串末尾,或碰到非数值字符后。直接返回检测到的数值。

parseInt()函数的第二个参数可以指定进制数(二进制十进制等……);

parseFloat()与parseInt()类似,它可以解析到第一个小数点后面第二个小数点之前;

String类型

字符串类型,可以使用双引号(“)、单引号(‘)或反引号(`)标识。

字符字面量

字面量含义
\n换行
\t制表
\b退格
\r回车
\f换页
\\反斜杠(\)
\’单引号(‘)
……

字符串可以通过length属性获取长度:

let text = "This is the letter sigma: \u03a3."; 
console.log(text.length); // 28 

字符串特点

字符串是不可变的,每次修改某变量字符串的值,都是先销毁原字符串然后将新字符串存入变量。

转换为字符串

几乎所有值都有toString()方法。用这个方法转换字符串。

null和undefined值没有toString()方法。

如果不确定一个值是否为null或undefined。可以使用String()转型函数:

  • 如果值有toString()方法,则调用该方法(不传参数)并返回结果。
  • 如果值是null,返回”null“
  • 如果值是undefined,返回”undefined“

模板字面量

ES6以后增加的就是(`)其中里面换行会进行保留。

字符串插值

模板字面量标签函数

标签函数就是可以自定义插值的函数。标签函数会接收被插值记号分隔后的模板和对每个表达式求职的结果。

let a = 6;
let b = 9;
function simpleTag(strings, aValExpression, bValExpression, sumExpression) {
 console.log(strings);
 console.log(aValExpression);
 console.log(bValExpression);
 console.log(sumExpression);
 return 'foobar';
}
let untaggedResult = `${ a } + ${ b } = ${ a + b }`;
let taggedResult = simpleTag`${ a } + ${ b } = ${ a + b }`;
// ["", " + ", " = ", ""]
// 6
// 9
// 15
console.log(untaggedResult); // "6 + 9 = 15"
console.log(taggedResult); // "foobar" 

因为表达式参数的数量是可变的,所以通常会使用剩余操作符(rest operator)将收集到的实参存入数组中去:

let a = 6;
let b = 9;
function simpleTag(strings, ...expressions) {
 console.log(strings);
 for(const expression of expressions) {
 console.log(expression);
 }
 return 'foobar';
}
let taggedResult = simpleTag`${ a } + ${ b } = ${ a + b }`;
// ["", " + ", " = ", ""]
// 6
// 9
// 15
console.log(taggedResult); // "foobar" 

如果想让字符串与表达式拼接:

let a = 6;
let b = 9;
function zipTag(strings, ...expressions) {
 return strings[0] +
 expressions.map((e, i) => `${e}${strings[i + 1]}`)
 .join('');
}
let untaggedResult = `${ a } + ${ b } = ${ a + b }`;
let taggedResult = zipTag`${ a } + ${ b } = ${ a + b }`;
console.log(untaggedResult); // "6 + 9 = 15"
console.log(taggedResult); // "6 + 9 = 15" 

原始字符串

// Unicode 示例
// \u00A9 是版权符号
console.log(`\u00A9`); // ©
console.log(String.raw`\u00A9`); // \u00A9
// 换行符示例
console.log(`first line\nsecond line`);
// first line
// second line
console.log(String.raw`first line\nsecond line`); // "first line\nsecond line"
// 对实际的换行符来说是不行的
// 它们不会被转换成转义序列的形式
console.log(`first line second line`);
// first line
// second line
console.log(String.raw`first line
second line`);
// first line
// second line 

另外,也可以通过标签函数的第一个参数,即字符串数组的.raw 属性取得每个字符串的原始内容

function printRaw(strings) {
 console.log('Actual characters:');
 for (const string of strings) {
 console.log(string);
 }
 console.log('Escaped characters;');
 for (const rawString of strings.raw) {
 console.log(rawString);
 }
}
printRaw`\u00A9${ 'and' }\n`;
// Actual characters:
// ©
//(换行符)
// Escaped characters:
// \u00A9
// \n 

Symbol类型

Symbol(符号)是ES6新增数据类型。

Symbol实例是唯一、不可变的。

Symbol的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。(确保对象属性是独一无二的)

Symbol就是用来创建唯一记号,进而用作非字符串形式的对象属性。

基本用法

符号需要使用Symbol()函数初始化。因为符号本身是原始数据类型。所以typeof 操作符对符号返回的是symbol:

let sym = Symbol();
console.log(typeof sym); // symbol 

Symbol()函数可以传入字符串参数作为对Symbol的描述(description),具体作用不清楚:

let genericSymbol = Symbol();
let otherGenericSymbol = Symbol();
let fooSymbol = Symbol('foo');
let otherFooSymbol = Symbol('foo');
console.log(genericSymbol == otherGenericSymbol); // false 
console.log(fooSymbol == otherFooSymbol); // false 

只要创建Symbol()实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性

let genericSymbol = Symbol();
console.log(genericSymbol); // Symbol()
let fooSymbol = Symbol('foo');
console.log(fooSymbol); // Symbol(foo); 

最重要的是,Symbol()函数不能与new关键字一起作为构造函数使用。这样做是为了避免创建符号包装对象:

let myBoolean = new Boolean();
console.log(typeof myBoolean); // "object"
let myString = new String();
console.log(typeof myString); // "object"
let myNumber = new Number();
console.log(typeof myNumber); // "object"
let mySymbol = new Symbol(); // TypeError: Symbol is not a constructor 

使用符号包装对象,可以借用Object()函数:

let mySymbol = Symbol();
let myWrappedSymbol = Object(mySymbol);
console.log(typeof myWrappedSymbol); 
// "object" 

使用全局符号注册表

Symbol.for()方法对每个字符串键都执行幂等操作。第一次使用某个字符串调用时,它会检查全局运行时注册表,发现不存在对应的符号,于是就会生成一个新符号实例并添加到注册表中。后续使用相同字符串的调用同样会检查注册表,发现存在于该字符串对应的符号,然后就会返回该符号实例。

let fooGlobalSymbol = Symbol.for('foo'); // 创建新符号
let otherFooGlobalSymbol = Symbol.for('foo'); // 重用已有符号
console.log(fooGlobalSymbol === otherFooGlobalSymbol); // true 

即使采用相同的符号描述,在全局注册表中定义符号跟使用Symbol()定义的符号也并不相同:

let localSymbol = Symbol('foo');
let globalSymbol = Symbol.for('foo');
console.log(localSymbol === globalSymbol); // false 

全局注册表中的符号必须使用字符串键来创建,因此作为参数传给 Symbol.for()的任何值都会被转换为字符串。此外,注册表中使用的键同时也会被用作符号描述。

let emptyGlobalSymbol = Symbol.for();
console.log(emptyGlobalSymbol); // Symbol(undefined) 

Symbol.keyFor()函数用来查询全局注册表,这个方法接收符号,返回全局符号对应的字符串键。如果查询的不是全局符号,则返回undefined。

串键。如果查询的不是全局符号,则返回 undefined。
// 创建全局符号
let s = Symbol.for('foo');
console.log(Symbol.keyFor(s)); // foo
// 创建普通符号
let s2 = Symbol('bar');
console.log(Symbol.keyFor(s2)); // undefined
如果传给 Symbol.keyFor()的不是符号,则该方法抛出 TypeError:
Symbol.keyFor(123); // TypeError: 123 is not a symbol 

使用Symbol符号作为属性

凡是可以使用字符串或数值作为属性的地方,都可以使用符号。这就包括了对象字面量属性和Object.defineProperty()/Object.defineProperties()定义的属性。对象字面量只能在计算属性语法中使用符号作为属性。

let s1 = Symbol('foo'),
 s2 = Symbol('bar'),
 s3 = Symbol('baz'),
 s4 = Symbol('qux');
let o = {
 [s1]: 'foo val'
};
// 这样也可以:o[s1] = 'foo val';
console.log(o);
// {Symbol(foo): foo val}
Object.defineProperty(o, s2, {value: 'bar val'});
console.log(o);
// {Symbol(foo): foo val, Symbol(bar): bar val}
Object.defineProperties(o, {
 [s3]: {value: 'baz val'},
 [s4]: {value: 'qux val'}
});
console.log(o);
// {Symbol(foo): foo val, Symbol(bar): bar val,
// Symbol(baz): baz val, Symbol(qux): qux val} 
let s1 = Symbol('foo'),
 s2 = Symbol('bar');
let o = {
 [s1]: 'foo val',
 [s2]: 'bar val',
 baz: 'baz val',
 qux: 'qux val'
};
console.log(Object.getOwnPropertySymbols(o));
// [Symbol(foo), Symbol(bar)]
console.log(Object.getOwnPropertyNames(o));
// ["baz", "qux"]
console.log(Object.getOwnPropertyDescriptors(o));
// {baz: {...}, qux: {...}, Symbol(foo): {...}, Symbol(bar): {...}}
console.log(Reflect.ownKeys(o));
// ["baz", "qux", Symbol(foo), Symbol(bar)] 
let o = {
 [Symbol('foo')]: 'foo val',
 [Symbol('bar')]: 'bar val'
};
console.log(o);
// {Symbol(foo): "foo val", Symbol(bar): "bar val"}
let barSymbol = Object.getOwnPropertySymbols(o)
 .find((symbol) => symbol.toString().match(/bar/));
console.log(barSymbol);
// Symbol(bar) 

常用内置符号

这些内置符号最重要的用途之一是重新定义它们,从而改变原生结构的行为。比如,我们知道for-of 循环会在相关对象上使用 Symbol.iterator 属性,那么就可以通过在自定义对象上重新定义Symbol.iterator 的值,来改变 for-of 在迭代该对象时的行为。这些内置符号也没有什么特别之处,它们就是全局函数 Symbol 的普通字符串属性,指向一个符号的实例。所有内置符号属性都是不可写、不可枚举、不可配置的。

Symbol.asyncIterator

这个符号作为一个属性表示“一个方法,该方法返回对象默认的 AsyncIterator。由 for-await-of 语句使用”。

for-await-of 循环会利用这个函数执行异步迭代操作。循环时,它们会调用以 Symbol.asyncIterator
为键的函数,并期望这个函数会返回一个实现迭代器 API 的对象。很多时候,返回的对象是实现该 API
的 AsyncGenerator:

class Foo {
 async *[Symbol.asyncIterator]() {}
}
let f = new Foo();
console.log(f[Symbol.asyncIterator]());
// AsyncGenerator {<suspended>}

技术上,这个由 Symbol.asyncIterator 函数生成的对象应该通过其 next()方法陆续返回Promise 实例。可以通过显式地调用 next()方法返回,也可以隐式地通过异步生成器函数返回:

class Emitter {
 constructor(max) {
 this.max = max;
 this.asyncIdx = 0;
 } 
 async *[Symbol.asyncIterator]() {
 while(this.asyncIdx < this.max) {
 yield new Promise((resolve) => resolve(this.asyncIdx++));
 }
 }
}
async function asyncCount() {
 let emitter = new Emitter(5);
 for await(const x of emitter) {
 console.log(x);
 }
}
asyncCount();
// 0
// 1
// 2
// 3
// 4 
Symbol.hasInstance

这个符号作为一个属性表示“一个方法,该方法决定一个构造器对象是否认可一个对象是它的实例。由 instanceof 操作符使用”。instanceof 操作符可以用来确定一个对象实例的原型链上是否有原型。instanceof 的典型使用场景如下:

function Foo() {}
let f = new Foo();
console.log(f instanceof Foo); // true
class Bar {}
let b = new Bar();
console.log(b instanceof Bar); // true 

在 ES6 中,instanceof 操作符会使用 Symbol.hasInstance 函数来确定关系。以 Symbol.hasInstance 为键的函数会执行同样的操作,只是操作数对调了一下:

function Foo() {}
let f = new Foo();
console.log(Foo[Symbol.hasInstance](f)); // true
class Bar {}
let b = new Bar();
console.log(Bar[Symbol.hasInstance](b)); // true 
Symbol.isConcatSpreadable

这个符号作为一个属性表示“一个布尔值,如果是 true,则意味着对象应该用 Array.prototype.concat()打平其数组元素”。ES6 中的 Array.prototype.concat()方法会根据接收到的对象类型选择如何将一个类数组对象拼接成数组实例。覆盖 Symbol.isConcatSpreadable
的值可以修改这个行为。

数组对象默认情况下会被打平到已有的数组,false 或假值会导致整个对象被追加到数组末尾。类
数组对象默认情况下会被追加到数组末尾,true 或真值会导致这个类数组对象被打平到数组实例。其
他不是类数组对象的对象在 Symbol.isConcatSpreadable 被设置为 true 的情况下将被忽略。

let initial = ['foo'];
let array = ['bar'];
console.log(array[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(array)); // ['foo', 'bar']
array[Symbol.isConcatSpreadable] = false;
console.log(initial.concat(array)); // ['foo', Array(1)]
let arrayLikeObject = { length: 1, 0: 'baz' };
console.log(arrayLikeObject[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(arrayLikeObject)); // ['foo', {...}]
arrayLikeObject[Symbol.isConcatSpreadable] = true;
console.log(initial.concat(arrayLikeObject)); // ['foo', 'baz']
let otherObject = new Set().add('qux');
console.log(otherObject[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(otherObject)); // ['foo', Set(1)]
otherObject[Symbol.isConcatSpreadable] = true;
console.log(initial.concat(otherObject)); // ['foo'] 
Symbol.iterator

这个符号作为一个属性表示“一个方法,该方法返回对象默认的迭代器。由 for-of 语句使用”。

Symbol.match

这个符号作为一个属性表示“一个正则表达式方法,该方法用正则表达式去匹配字符串。由 String.prototype.match()方法使用”。String.prototype.match()方法会使用以 Symbol.match 为键的函数来对正则表达式求值。正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个 String 方法的有效参数:

console.log(RegExp.prototype[Symbol.match]);
// ƒ [Symbol.match]() { [native code] }
console.log('foobar'.match(/bar/));
// ["bar", index: 3, input: "foobar", groups: undefined] 

给这个方法传入非正则表达式值会导致该值被转换为 RegExp 对象。如果想改变这种行为,让方法直接使用参数,则可以重新定义 Symbol.match 函数以取代默认对正则表达式求值的行为,从而让match()方法使用非正则表达式实例。Symbol.match 函数接收一个参数,就是调用 match()方法的字符串实例。返回的值没有限制:

class FooMatcher {
 static [Symbol.match](target) {
 return target.includes('foo');
 }
}
console.log('foobar'.match(FooMatcher)); // true
console.log('barbaz'.match(FooMatcher)); // false
class StringMatcher {
 constructor(str) {
 this.str = str;
 }
 [Symbol.match](target) {
 return target.includes(this.str);
 }
}
console.log('foobar'.match(new StringMatcher('foo'))); // true
console.log('barbaz'.match(new StringMatcher('qux'))); // false 
Symbol.replace

这个符号作为一个属性表示“一个正则表达式方法,该方法替换一个字符串中匹配的子串。由 String.prototype.replace()方法使用”。String.prototype.replace()方法会使用以 Symbol.replace 为键的函数来对正则表达式求值。正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个 String 方法的有效参数:

console.log(RegExp.prototype[Symbol.replace]);
// ƒ [Symbol.replace]() { [native code] }
console.log('foobarbaz'.replace(/bar/, 'qux'));
// 'fooquxbaz' 

给这个方法传入非正则表达式值会导致该值被转换为 RegExp 对象。如果想改变这种行为,让方法
直接使用参数,可以重新定义 Symbol.replace 函数以取代默认对正则表达式求值的行为,从而让
replace()方法使用非正则表达式实例。Symbol.replace 函数接收两个参数,即调用 replace()方
法的字符串实例和替换字符串。返回的值没有限制:

class FooReplacer {
 static [Symbol.replace](target, replacement) {
 return target.split('foo').join(replacement);
 }
}
console.log('barfoobaz'.replace(FooReplacer, 'qux'));
// "barquxbaz"
class StringReplacer {
 constructor(str) {
 this.str = str;
 }
 [Symbol.replace](target, replacement) {
 return target.split(this.str).join(replacement);
 }
}
console.log('barfoobaz'.replace(new StringReplacer('foo'), 'qux'));
// "barquxbaz
Symbol.search

“一个正则表达式方法,该方法返回字符串中匹配正则表达式的索引。由 String.prototype.search()方法使用”

Symbol.species

一个函数值,该函数作为创建派生对象的构造函数

Symbol.split

一个正则表达式方法,该方法在匹配正则表达式的索引位置拆分字符串。由 String.prototype.split()方法使用

Symbol.toPrimitive

“一个方法,该方法将对象转换为相应的原始值。由 ToPrimitive 抽象操作使用”

Symbol.toStringTag

“一个字符串,该字符串用于创建对象的默认字符串描述。由内置方法 Object.prototype.toString()使用

Symbol.unscopables

一个对象,该对象所有的以及继承的属性,都会从关联对象的 with 环境绑定中排除

(注意!因为Symbol方法众多,精细阅读的话,就违背了我懒的初衷,所以我删减细节。主要因为我技术菜,我也总结不了Symbol)

Object类型

对象就是一组数据和功能的集合。

使用new 操作符创建对象:

let o=new Object()

每个Object都有如下的属性和方法:

  • constructor:用于创建当前对象的函数。
  • hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查属性名必须是字符串(如o.hasOwnProperty(“name”))或符号。
  • isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型。
  • propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用for-in语句枚举。与hasOwnProperty()一样,属性名必须是字符串。
  • toLocaleString():返回对象的字符串表示,该字符串反应对象所在的本地化执行环境。
  • toString():返回对象的字符串表示。
  • valueOf():返回对象对应的字符串、数值或布尔值表示。通常与toString()的返回值相同。

因为ECMAScript中Object是所有对象的基类,所以任何对象都有这些属性和方法。

(81-操作符20220220+b0209

一元操作符

递增/递减操作符

前缀递增/递减会给数值加1/减1:

let	age=10;
++age;
let age=29;
--age;

前缀递增/减都会在语句被求值之前改变。

前缀递增/减在语句中优先级是相等的,所以会从左到右依次求值。

后缀版的递增和递减在语句被求值后才发生。

let age=10;
age++;
let age=29;
age--;

这 4 个操作符可以作用于任何值,意思是不限于整数——字符串、布尔值、浮点值,甚至对象都可以。递增和递减操作符遵循如下规则:

  • 对于字符串,如果是有效的数值形式,则转换为数值再应用改变。变量类型从字符串编程数值。
  • 对于字符串,如果不是有效的数值形式,则将变量的值设置为NaN。变量类型从字符串变成数值。
  • 对于布尔值,如果是false,则转换为0再应用改变。变量类型从布尔值变成数值。
  • 对于布尔值,如果是true,则转换为1再应用改变。变量类型从布尔值变成数值。
  • 对于浮点值,加1或减1.
  • 如果是对象,则调用其valueOf()方法取得可以操作的值。对得到的值应用上述规则。如果是NaN,则调用toString()并再次应用其他规则。变量类型从对象变成数值。
let s1 = "2";
let s2 = "z";
let b = false;
let f = 1.1;
let o = {
 valueOf() {
 return -1;
 }
};
s1++; // 值变成数值 3
s2++; // 值变成 NaN
b++; // 值变成数值 1
f--; // 值变成 0.10000000000000009(因为浮点数不精确)
o--; // 值变成-2 

一元加和减

将加号放在变量前面就相当于执行了Number()一样,会将变量转为Number类型:

let s1 = "01";
let s2 = "1.1"; 
let s3 = "z";
let b = false;
let f = 1.1;
let o = {
 valueOf() {
 return -1;
 }
};
s1 = +s1; // 值变成数值 1
s2 = +s2; // 值变成数值 1.1
s3 = +s3; // 值变成 NaN
b = +b; // 值变成数值 0
f = +f; // 不变,还是 1.1
o = +o; // 值变成数值-1 

将减号放在变量前面一样也会先执行数据转换,再将转换为Number的数据取反:

let s1 = "01";
let s2 = "1.1";
let s3 = "z";
let b = false;
let f = 1.1;
let o = {
 valueOf() {
 return -1;
 }
};
s1 = -s1; // 值变成数值-1
s2 = -s2; // 值变成数值-1.1
s3 = -s3; // 值变成 NaN
b = -b; // 值变成数值 0
f = -f; // 变成-1.1
o = -o; // 值变成数值 1 

位操作符

按位非

波浪符( ~ )

let num1 = 25; // 二进制 00000000000000000000000000011001
let num2 = ~num1; // 二进制 11111111111111111111111111100110
console.log(num2); // -26 
按位与

and符(&)

let num1 = 25; // 二进制 00000000000000000000000000011001
let num2 = ~num1; // 二进制 11111111111111111111111111100110
console.log(num2); // -26 
按位或

管道符( | )

let result = 25 | 3;
console.log(result); // 27 
按位异或

脱字符( ^ )

let result = 25 ^ 3;
console.log(result); // 26 
左移

两个小于号(<<)

let oldValue = 2; // 等于二进制 10
let newValue = oldValue << 5; // 等于二进制 1000000,即十进制 64 
有符号右移

两个大于号(>>)

let oldValue = 64; // 等于二进制 1000000
let newValue = oldValue >> 5; // 等于二进制 10,即十进制 2
无符号右移

3个大于号(>>>)

let oldValue = 64; // 等于二进制 1000000
let newValue = oldValue >>> 5; // 等于二进制 10,即十进制 2 

布尔操作符

布尔操作符一共三个:逻辑非、逻辑与、逻辑或。

逻辑非

逻辑非操作符是一叹号( ! )

它的作用就是将变量转换为布尔值后取反,有如下规则:

  • 如果操作数是对象,则返回false。

  • 如果操作数是空字符串,则返回true。

  • 如果操作数是非空字符串,则返回false。

  • 如果操作数是数值0,则返回true。

  • 如果操作数是非0数值(包括Infinity),则返回false。

  • 如果操作数是null,则返回true。

  • 如果操作数是NaN,则返回true。

  • 如果操作数是undefined,则返回true。

    console.log(!false); // true
    console.log(!"blue"); // false
    console.log(!0); // true
    console.log(!NaN); // true
    console.log(!""); // true
    console.log(!12345); // false 
    

逻辑与

逻辑与操作符由两个和符号组成(&&),逻辑与遵循如下规则:

  • 如果第一个操作数是对象,则返回第二个操作数
  • 如果人第二个操作数是对象,则只有一个操作数求值为true才会返回该对象。
  • 如果两个操作数都是对象,则返回第二个操作数。
  • 如果有一个操作数是null,则返回null。
  • 如果有一个操作数是NaN,则返回NaN。
  • 如果有一个操作数是undefined,则返回undefined。

逻辑与操作符是一种短路操作符:如果第一个是false,则不会读到后面了。如果第一个为true,则会看第二个,如果都是true,则返回第二个。

(两个条件都为真才为真,有一个为假,结果就是假)

逻辑或

逻辑或由两个管道符组成(||),逻辑或遵循如下规则:

  • 如果第一个操作数是对象,则返回第一个操作数。
  • 如果第一个操作数求值为false,则返回第二个操作数。
  • 如果两个数都是对象,则返回第一个操作数。
  • 如果两个操作数都是null,则返回null。
  • 如果两个操作数都是NaN,则返回NaN。
  • 如果两个操作数都是undefined,则返回undefined。

与逻辑的短路特性:当第一个为true,则不会读到第二个。

(有一个为真,则为真。全假才为假)

乘性操作符

乘法操作符

乘法操作符(*)

基本特性与初中学的乘法吻合:

  • 当JS无法表示乘积会返回Infinity或-Infinity。
  • 如果有任一操作数为NaN,则返回NaN。
  • 如果Infinity乘以0,则返回NaN.
  • 如果有不是数值的操作符,则会先用Number()将其转为数值,再计算。

除法操作符

除法操作符由一个斜杠表示(/),除法遵循规则:

  • 如果JS无法表示结果则返回Infinity或-Infinity。
  • 如果由任一操作符是NaN,则返回NaN.
  • 如果是Infinity除以Infinity,则返回NaN
  • 如果0除以0,返回NaN
  • 如果除以0,则会返回Infinity或-Infinity
  • Infinity除以任何数都是Infinity
  • 如果存在不是数值的,则先运行Number()再进行相除。

取模操作符

取模(余数)操作符由一个百分比符号(%)表示,取模操作符遵循规则:

  • 操作符都为数值,进行除法运算返回余数。
  • 被除数为无限值(Infinity),则返回NaN
  • 如果被除数是有限值,除数是无限值,则返回被除数。
  • 如果被除数是0,除数不是0返回0
  • 不是数值的先Number()转换再执行运算。

指数操作符

指数操作符=》Math.pow()与指数操作符一样(* *)

console.log(Math.pow(3, 2); // 9
console.log(3 ** 2); // 9
console.log(Math.pow(16, 0.5); // 4
console.log(16** 0.5); // 4 

也可以这样写:

let sqrt = 16;
sqrt **= 0.5;
console.log(sqrt); // 4 

加性操作符

加法操作符

加法操作符(+)用于求两个数的和

如果两个操作数都是数值,加法操作符执行加法运算并根据如下规则返回结果:

  • 如果有任一操作数是 NaN,则返回 NaN;
  • 如果是 Infinity 加 Infinity,则返回 Infinity;
  • 如果是-Infinity 加-Infinity,则返回-Infinity;
  • 如果是 Infinity 加-Infinity,则返回 NaN;
  • 如果是+0 加+0,则返回+0;
  • 如果是-0 加+0,则返回+0;
  • 如果是-0 加-0,则返回-0。

不过,如果有一个操作数是字符串,则要应用如下规则:

  • 如果两个操作数都是字符串,则将第二个字符串拼接到第一个字符串后面;
  • 如果只有一个操作数是字符串,则将另一个操作数转换为字符串,再将两个字符串拼接在一起。
let result1 = 5 + 5; // 两个数值
console.log(result1); // 10
let result2 = 5 + "5"; // 一个数值和一个字符串
console.log(result2); // "55" 

减法操作符

减法操作符(-)也是使用很频繁的一种操作符,与加法操作符一样,减法操作符也有一组规则用于处理 ECMAScript 中不同类型之间的转换:

  • 如果两个操作数都是数值,则执行数学减法运算并返回结果。
  • 如果有任一操作数是 NaN,则返回 NaN。
  • 如果是 Infinity 减 Infinity,则返回 NaN。
  • 如果是-Infinity 减-Infinity,则返回 NaN。
  • 如果是 Infinity 减-Infinity,则返回 Infinity。
  • 如果是-Infinity 减 Infinity,则返回-Infinity。
  • 如果是+0 减+0,则返回+0。
  • 如果是+0 减-0,则返回-0。
  • 如果是-0 减-0,则返回+0。
  • 如果有任一操作数是字符串、布尔值、null 或 undefined,则先在后台使用 Number()将其转换为数值,然后再根据前面的规则执行数学运算。如果转换结果是 NaN,则减法计算的结果是NaN。
  • 如果有任一操作数是对象,则调用其 valueOf()方法取得表示它的数值。如果该值是 NaN,则减法计算的结果是 NaN。如果对象没有 valueOf()方法,则调用其 toString()方法,然后再将得到的字符串转换为数值。
let result1 = 5 - true; // true 被转换为 1,所以结果是 4
let result2 = NaN - 1; // NaN
let result3 = 5 - 3; // 2
let result4 = 5 - ""; // ""被转换为 0,所以结果是 5
let result5 = 5 - "2"; // "2"被转换为 2,所以结果是 3
let result6 = 5 - null; // null 被转换为 0,所以结果是 5 

关系操作符

用法与数学课上的一样。结果返回布尔值 ,ECMAScript 中的其他操作符一样,在将它们应用到不同数据类型时也会发生类型转换和其他行为:

  • 如果操作数都是数值,则执行数值比较。
  • 如果操作数都是字符串,则逐个比较字符串中对应字符的编码。
  • 如果有任一操作数是数值,则将另一个操作数转换为数值,执行数值比较。
  • 如果有任一操作数是对象,则调用其 valueOf()方法,取得结果后再根据前面的规则执行比较。如果没有 valueOf()操作符,则调用 toString()方法,取得结果后再根据前面的规则执行比较。
  • 如果有任一操作数是布尔值,则将其转换为数值再执行比较

比较字符串的时候,会比较字符串中对应字符的编码;

比较两个字符串形式的数字的时候也是比较它们的字符编码去比较。

如果有一个为数值,则会按照数值比较。

(95-相等操作符20220221+b0112

等于和不等于

ECMAScript中等于(==)和不等于(!=)两个操作符在进行操作的时候会进行强制类型转换:

在转换操作数的类型时,相等和不相等操作符遵循如下规则。

  • 如果操作数有布尔值(Boolean)则会转换为数值(Number)进行比较。false 转换为 0,true 转换
    为 1。
  • 字符串与数字比较时,会尝试将字符串转为数字然后进行比较。
  • 如果一个操作数是对象,另一个操作数不是,则调用对象的 valueOf()方法取得其原始值,再
    根据前面的规则进行比较。

在进行比较时,这两个操作符会遵循如下规则。

  • null 和 undefined 相等。
  • null 和 undefined 不能转换为其他类型的值再进行比较。
  • 如果有任一操作数是 NaN,则相等操作符返回 false,不相等操作符返回 true。记住:即使两
    个操作数都是 NaN,相等操作符也返回 false,因为按照规则,NaN 不等于 NaN。
  • 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,
    则相等操作符返回 true。否则,两者不相等。

全等和不全等

全等(=)和不全等(!)false 转换为 0,true 转换为 1。

条件操作符

variable = boolean_expression ? true_value : false_value;

上面的代码执行了条件赋值操作,即根据条件表达式 boolean_expression 的值决定将哪个值赋给变 量 variable 。 如果 boolean_expression 是 true , 则 赋 值 true_value ; 如 果boolean_expression 是 false,则赋值 false_value。

(前面表达式如果为真返回第一个,如果为假,返回第二个)

赋值操作符

简单赋值用等于号(=)表示,将右手边的值赋给左手边的变量;

复合赋值使用乘性、加性或位操作符后跟等于号(=)表示。

  • 乘后赋值(*=)
  • 除后赋值(/=)
  • 取模后赋值(%=)
  • 加后赋值(+=)
  • 减后赋值(-=)
  • 左移后赋值(<<=)
  • 右移后赋值(>>=)
  • 无符号右移后赋值(>>>=)

逗号操作符

逗号操作符可以用来在一条语句中执行多个操作;

可以使用逗号操作符来辅助赋值。在赋值时使用逗号操作符分隔值,最终会返回表达式中最后一个值:

let num = (5, 1, 4, 8, 0); // num 的值为 0

语句

if语句

if(表达式1){
代码块1
}else if(表达式2){
代码块2
}else{
代码块3
}

(当读到if的时候:

  • 首先去读取"表达式1":
    • 如果为true则会进入到"代码块1",然后执行"代码块1"里的逻辑,随后忽略if的其他代码块。
    • 如果为false则会读取"表达式2":
      • 如果为true则会进入到"代码块2",然后忽略剩余的代码块。
      • 如果为false,则会进入到"代码块3"

do-while语句

do {
代码块
} while (表达式);

下面是一个例子:
let i = 0;
do {
i += 2;
} while (i < 10);

do-while语句会先执行一次前面的代码块,然后再进行表达式判断。

while语句

while(表达式) {
代码块}

while语句会先执行表达式,如果表达式成立,则会进入代码块执行。

for语句

for (initialization; expression; post-loop-expression){statement} 
for (let i = 0; i < count; i++) {
console.log(i);
}
for(表达式1:定义计数器;表达式2:为循环设置限制;表达式3:让计数器发生改变){

执行体}

for循环首先会执行表达式1然后再接着执行表达式2:

  • 如果表达式2满足条件,则会进入执行体,执行体执行完毕了会进入表达式3;
  • 执行完表达式3后则会继续执行表达式1,然后执行表达式2……
  • 如果不满足表达式2,则会直接跳出循环。

注意:for循环的表达式都不是必须的,所以可以省略。

for (;;) { // 无穷循环
doSomething();
}

for-in语句

for-in语句是一种严格的迭代语句,用于枚举对象中的非符号键属性

for (property in expression) statement
for (const propName in window) {
document.write(propName);
}

注意:

  • for-in可以循环遍历所有对象里面的属性
  • 如果for-in循环要迭代的变量是null或undefined,则不会执行循环体。

for-of语句

for-of 语句是一种严格的迭代语句,用于遍历可迭代对象的元素

(可迭代对象的元素:首先要求是可迭代,那么要遍历的对象是可迭代的:如数组、含有迭代器的对象等)

注意:如果尝试迭代的变量不支持迭代,则 for-of 语句会抛出错误。

标签语句

label: statement
例子:
start: for (let i = 0; i < count; i++) {
console.log(i);
}

标签语句的典型应用场景是嵌套循环

break和continue

使用break会立即退出循环体,然后执行循环下边的语句代码。

使用continue会立即退出当次循环,然后执行下一次循环,直到表达式不被满足退出循环体。

break和continue可以配合标签语句执行。

break:

let num = 0;
outermost:
for (let i = 0; i < 10; i++) {
    for (let j = 0; j < 10; j++) {
        if (i == 5 && j == 5) {
            break outermost;
        }
        num++;
        console.log(`j=${j}`)
    }
    console.log(`iiiiiiiiiiii=${i}`)
}
console.log(num); // 55

continue:

let num = 0;
outermost:
for (let i = 0; i < 10; i++) {
    for (let j = 0; j < 10; j++) {
        if (i == 5 && j == 5) {
            continue outermost;
        }
        num++;
        console.log(`j=${j}`)
    }
    console.log(`iiiiiiiiiiii=${i}`)
}
console.log(num); // 95

break案例表示当if判断满足时,break直接退出到标签带有的循环体。

continue案例表示当if判断满足时,continue直接退出当前循环(因为标签语句所以会回退到标签语句标记的循环处)开始下一次循环。

with语句

with 语句的用途是将代码作用域设置为特定的对象

with (expression) statement;
let qs = location.search.substring(1);
let hostName = location.hostname;
let url = location.href;
上面代码中的每一行都用到了 location 对象。如果使用 with 语句,就可以少写一些代码:
with(location) {
let qs = search.substring(1);
let hostName = hostname;
let url = href;
}

严格模式不允许使用 with 语句,否则会抛出错误。

switch语句

switch (expression) {
case value1:
statement
break;
case value2:
statement
break;
case value3:
statement
break;
case value4:
statement
break;
default:
statement
}
switch (i) {
case 25:
console.log("25");
break;
case 35:
console.log("35");
break;
case 45:
console.log("45");
break;
default:
console.log("Other");
}

在switch语句中,为了避免不必要的判断,最好在每条后面添加break,如果的确需要多次判断,建议加注释说明。

在js中switch表达式可以是常量也可以是变量可而且可以是任何数据类型!

函数

function functionName(arg0, arg1,...,argN) {
statements
}

调用函数使用函数名后加括号形式:

function sayHi(name, message) {
console.log("Hello " + name + ", " + message);
}
sayHi("Nicholas", "how are you today?");

return语句:如果函数内代码执行到return后,则会直接跳出函数体(return后面的代码不会被执行).

函数也可以不带返回值,不带return的话,最后函数会返回undefined。

小结

  • ECMAScript 中的基本数据类型包括 Undefined、Null、Boolean、Number、String 和 Symbol。
  • ECMAScript 不区分整数和浮点值,只有 Number 一种数值数据类型。
  • Object 是一种复杂数据类型,它是这门语言中所有对象的基类。(万物皆对象)
  • 严格模式为这门语言中某些容易出错的部分施加了限制(语言不规范就会报错)。
  • ECMAScript 提供了很多基本操作符,包括数学操作符、布尔操作符、关系操作符、相等操作符和赋值操作符等。
  • ECMAScript 中的函数与其他语言中的函数不一样,其余分支语句都是借鉴其他语言。
  • 不需要指定函数的返回值,因为任何函数可以在任何时候返回任何值。
  • 不指定返回值的函数实际上会返回特殊值 undefined。
举报

相关推荐

0 条评论