JavaScript高级程序设计第四版 目录
前言
本文章来源为:《JavaScript高级程序设计》(第四版),如有机会,请以书中内容为主。
注意
-
此文不涉及基础语法,如有需要可观看菜鸟教程
个人体会
第四版这本书啃起来有点难啃。迭代器,原型链,promise还是有一定了解才行,否则阅读起来有点生硬。
node毕竟是单线程非阻塞的语言,为了模拟多线程,会出现async和await这种东西,精髓是在回调函数上。
推荐
- 《JavaScript高级程序设计》(第四版)
- 《JavaScript学习指南》
1. 什么是JavaScript
DOm
文档对象模型(DOM):是一个应用编程接口(API)
<html>
<head>
<title>xxx</title>
</head>
<body>
<p>xxxx</p>
</body>
</html>
图例:
2. HTML中的JavaScript
<script>
元素
属性
- async:立刻下载脚本
- charset:指定的代码字符集
- crossorigin:跨资源共享设置
- defer:在文档解析和显示完成后再执行脚本
- integrity:允许比对接收到的资源和指定的加密签名,已验证子资源的完整性。用于CDN
- language
- src
- type:代码块中脚本语言的内容类型。如果是
module
,则被当作ES6模块
动态加载脚本
let script = document.createElement('script');
script.src = 'test.js'
document.head.appendChild(script);
XHTML中的变化
XHTML是将HTML作为XML重新包装的结果。
区别
- XHTML使用JavaScript必须指定
type=text/javascript
- 规则严格
- 已经废弃
//<!CDATA //]]>
块
3. 语言基础
内容概括
- 语法
- 数据类型
- 控制语句
- 函数
此文不涉及基础语法,可观看菜鸟教程
严格模式
- 处理不规范写法
use strict
Symbol类型
ECMAScript6
新增数据类型
- 符号是原始值
- 符号实例时唯一、不可变的
- 用于确保对象属性使用唯一标识符,不会出现冲突
符号的基本用法
Symbol()函数初始化
let sym = Symbol();
console.log(typeof sym); // symbol
传入字符串对符号描述
let genericSymbol = Symbol();
let otherGenericSymbol = Symbol();
let foolSymbol = Symbol('foo');
let otherFoolSymbol = Symbol('foo');
console.log(genericSymbol == otherGenericSymbol);
console.log(foolSymbol == otherFoolSymbol);
Symbol函数不能用作构造函数
- 不能和
new
一起使用
全局符号注册表 - symbol.for
如果运行时的不同部分需要共享和重用符号实例:
- 用一个字符串作为键
- 全局符号注册表中创建并重用符号
Symbol.for()
方法
// 创建新符号
let fooGlobalSymbol = Symbol.for('foo');
// 重用已有符号
let otherFooGlobalSymbol = Symbol.for('foo');
console.log(fooGlobalSymbol === otherFooGlobalSymbol); // true
symbol.for
- 对每个字符串键都执行幂等操作
- 第一次使用某个字符串调用时,会检查全局运行时注册表
- 如果不存在对应的符号,会生成一个新符号实例并添加到注册表中
- 后续使用相同的字符串调用时,会先检查注册表,发现对应的符号返回该符号实例
- 全局注册表中的符号必须为字符串,任何值都会被转换为字符串
-
类似于设计模式中的单例模式
- symbol与symbol.for定义的结果是不同的
let test1 = Symbol('foo');
let test2 = Symbol.for('foo');
console.log(test1 === test2); // false
查询全局注册表 - symbol.keyFor
- 接收符号,返回该全局符号对应的字符串键
- 如果查询的不是全局符号,则会返回
undefined
// create global symbol
let s = Symbol.for('foo');
console.log(Symbol.keyFor(s)); // foo
// create ordinary symbol
let s2 = Symbol('bar');
console.log(Symbol.keyFor(s2)); // undefined
// type error
console.log(Symbol.keyFor(123));
使用符号作为属性
可以用字符串或者数值作为属性的地方,都可以使用符号。
包括:
- 对象字面量属性,只能在计算属性语法中使用符号作为属性
Object.defineProperty()
Object.definedProperties()
let s1 = Symbol('foo'),
s2 = Symbol('bar'),
s3 = Symbol('baz'),
s4 = Symbol('qux');
let o = {
[s1]: 'foo val'
};
console.log(o);
o[s2] = 'foo val2';
console.log(o);
获取对象实例的四种用法
Object.getOwnPropertyNames()
:返回对象实例的常规属性数组Object.getOwnPropertySymbols()
:返回对象实例的符号属性数组Object.getOwnPropertyDescriptors()
:返回同时包含常规和符号属性描述符的对象Reflect.ownKeys()
:返回两种类型的键
console.log(Object.getOwnPropertyNames(o));
console.log(Object.getOwnPropertySymbols(o));
console.log(Object.getOwnPropertyDescriptor(o));
console.log(Reflect.ownKeys(o));
注意
- 符号属性是对内存中符号的一个引用
- 直接创建并用作属性的符号不会丢失
- 如果没有显式地保存这些属性的引用,必须遍历对象的所有符号属性才能找到响应的属性键
let o = {
[Symbol('foo')]: 'foo val',
[Symbol('bar')]: 'bar val'
};
console.log(o);
let barSymbol = Object.getOwnPropertySymbols(o)
.find((symbol) =>
symbol.toString().match(/bar/)
);
console.log(barSymbol);
常用内置符号
- 用于暴露语言内部行为,可以直接访问、重写或模拟这些行为
- 都以Symbol工厂函数字符串属性的形式存在
- 常用于重新定义,改变原生结构的行为
注意
- 引用符号在规范中的名称,前缀为
@@
,如@@iterator
指的就是Symbol.iterator
Symbol.asyncIterator
- 该方法返回对象默认的
AsyncIterator
,由for-await-of
使用 - 这个符号表示实现异步迭代器API的函数
for-await-of
循环时,执行异步迭代操作,调用以Symbol.asyncIterator
为键的函数,返回一个实现迭代器的API对象- 对象是实现API的
AsyncGenerator
class Foo {
async *[Symbol.asyncIterator]() {}
}
let f = new Foo();
console.log(f[Symbol.asyncIterator]()); // Object [AsyncGenerator] {}
- 这个由
Symbol.asyncIterator
生成的对象应该通过其next()
方法陆续返回Promise
实例 - 既可以通过显示调用
next()
方法返回,也可以通过隐式调用异步生成器函数返回
class Emitter {
constructor(max) {
this.max = max;
this.asyncIdx = 0;
}
// 异步迭代器的API
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();
注意
symbol.asyncIterator
只支持ES2018规范
Symbol.hasInstance
- 该方法决定一个构造器对象是否认可一个对象时它的实例,由
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
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
重新定义函数
- 这个属性定义在
function
原型上,默认所有函数和类上都可以调用 - 可以在继承的类上通过静态方法重新定义这个函数
class Bar{};
class Baz extends Bar {
static [Symbol.hasInstance]() {
return false;
}
}
let b = new Baz();
console.log(Bar[Symbol.hasInstance](b)); // true
console.log(b instanceof Bar); //true
console.log(Baz[Symbol.hasInstance](b)); //false
console.log(b instanceof Baz); //false
Symbol.isConcatSpreadable
- 布尔值,如果是
true
,意味着对象应该用Array.prototype.concat()
打平其数组元素 Array.prototype.concat()
会根据接收到的对象类型选择如何将一个类数组对象拼接成数组实例- false: 默认追加到数组末尾
- true: 被打平到数组实例
- 其他不是类数组对象的对象,在
Symbol.isConcatSpreadable
被设置为true的情况下忽略
let initial = ['foo'];
let array = ['bar'];
// false
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)]
// true
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']
// set
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
使用
class Foo {
*[Symbol.iterator]() {}
}
let f = new Foo();
console.log(f[Symbol.iterator]);
- 显示next()
- 隐式生成器函数
class Emitter{
constructor(max) {
this.max = max;
this.idx = 0;
}
*[Symbol.iterator]() {
while(this.idx < this.max){
yield this.idx++;
}
}
}
function count() {
let emitter = new Emitter(5);
for (const x of emitter){
console.log(x);
}
}
count();
symbol.match
- 正则表达式法,用正则表达式去匹配字符串
- 用
String.prototype.match()
String.prototype.match()
会使用Symbol.match
为键的函数来对正则表达式求值- 正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个
String
方法的有效参数
console.log(RegExp.prototype[Symbol.match]);
console.log('foobar'.match(/bar/));
- 如果传入非正则表达式值,会导致该值被转换为
regexp
对象 - 重新定义
symbol.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()
使用
console.log(RegExp.prototype[Symbol.replace]);
console.log('foobarbaz'.replace(/bar/, 'qux'));
class FooReplacer {
static [Symbol.replace] (target, replacement) {
return target.split('foo').join(replacement);
}
}
console.log('barfoobaz'.replace(FooReplacer, 'qux'));
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'));
Symbol.search
- 返回字符串中匹配正则表达式的索引
class FooSearch {
static [Symbol.search](target) {
return target.indexOf('foo');
}
}
console.log('foobar'.search(FooSearch)); // 0
console.log('barfoo'.search(FooSearch)); // 3
console.log('barbaz'.search(FooSearch)); // -1
class StringSearch {
constructor(str) {
this.str = str;
}
[Symbol.search](target){
return target.indexOf(this.str);
}
}
console.log('foobar'.search(new StringSearch('foo'))); // 0
console.log('barfoo'.search(new StringSearch('foo'))); // 3
console.log('barbaz'.search(new StringSearch('foo'))); // -1
symbol.species
- 创建派生对象的构造函数
- 用
symbol.species
定义静态的获取器(getter),覆盖新创建实例的原型定义
class Bar extends Array {}
class Baz extends Array {
static get [Symbol.species]() {
return Array;
}
}
let bar = new Bar();
console.log(bar instanceof Array); // true
console.log(bar instanceof Bar); // true
bar = bar.concat('bar');
console.log(bar instanceof Array); // true
console.log(bar instanceof Bar); // true
let baz = new Baz();
console.log(baz instanceof Array); // true
console.log(baz instanceof Baz); // true
baz = baz.concat('baz');
console.log(baz instanceof Array); // true
console.log(baz instanceof Baz); // false
symbol.split
- 该函数作为创建派生对象的构造函数
class FooSplitter {
static [Symbol.split](target){
return target.split('foo');
}
}
console.log('barfoobaz'.split(FooSplitter));
class StringSplitter {
constructor(str){
this.str = str;
}
[Symbol.split](target){
return target.split(this.str);
}
}
console.log('barfoobaz'.split(new StringSplitter('foo')));
symbol.toPrimitive
- 将对象转换为相应的原始值
class Foo {}
let foo = new Foo();
console.log(3 + foo);
console.log(3 - foo);
console.log(String(foo));
class Bar {
constructor() {
this[Symbol.toPrimitive] = function(hint) {
switch(hint) {
case 'number':
return 3;
case 'string':
return 'string bar';
case 'default':
default:
return 'default bar';
}
}
}
}
let bar = new Bar();
console.log(3 + bar);
console.log(3 - bar);
console.log(String(bar));
symbol.toStringTag
- 该字符串用于创建对象的默认字符串描述
Object.prototype.toString()
let s = new Set();
console.log(s); // Set[0] {}
console.log(s.toString()); // [Object Set]
console.log(s[Symbol.toStringTag]); //Tag
class Foo {}
let foo = new Foo();
console.log(foo); // Foo {}
console.log(foo.toString()); // [object Object]
console.log(foo[Symbol.toStringTag]); // undefined
class Bar {
constructor(){
this[Symbol.toStringTag] = 'Bar';
}
}
let bar = new Bar();
console.log(bar); // Bar {}
console.log(bar.toString()); // [object object]
console.log(bar[Symbol.toStringTag]); // bar
Object
- 对象其实就是一组数据和功能的集合
let o = new Object()
- Object也是派生其他类的基类,Object类所有属性和方法在派生的对象上同样存在
每个Object类的属性和方法
- constructor
- hasOwnProperty(propertyName):判断当前对象实例是否存在给定属性
- isPrototypeof(object):判断当前对象是否为另一个对象的原型
- propertyIsEnumerable(propertyName):判定给定的属性是否可以使用
- toLocaleString():返回对象的字符串表示
- toString()
- valueOf()
for-in 语句
- 严格的迭代语句,用于枚举对象中的非符号键属性
for (property in expression) statement
for-of 语句
- 迭代语句,遍历可迭代对象的元素
for (property of expression) statement
with语句
- 将代码作用域设置为特定的对象
with (expression) statement
- 主要是针对一个对象反复操作
let qs = location.search.substring(1);
let hostname = location.hostname;
let url = location.href;
with(location) {
let qs = search.substring(1);
let hostname = hostname;
let url = href;
}
- 严格模式不允许使用
with
严格模式的限制
- 函数不能以
eval
或者arguments
作为名称 - 函数的参数不能叫做
eval
或者arguments
- 两个函数的参数不能教同一个名称
**如果违反以上规则,将会导致语法错误,代码也不会运行**