本文主要是介绍前端开发人必读红宝书重要章节知识点提炼+笔记+面试回答点,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
第6章-集合引用类型
一)数组
数组的特殊性:数组的每个槽位可以存储任意类型的数据。
Ⅰ)数组的创建
-
两个方法创建方式:(es6新增)
-
from()
-
用于将类数组结构转换为数组实例,会将传入的实例进行浅复值;
-
参数:
- 第一个参数是一个可迭代对象,任何可迭代的结构;
- 第二个参数,是个可选的映射函数参数;
- 第三个参数,用于指定映射函数中的this的值;
const a1 = [1,2,3,4]; const a2 = Array.from(a1,x=>x**2); console.log(a2) // [1,4,9,16]
-
-
of()
- 用于将一组参数转换为数组实例;
console.log(Array.of(1,2,3,4)) // [1,2,3,4]
-
二)数组的方法:
Ⅰ)迭代器方法
数组的迭代器方法;
① keys()
返回数组索引的迭代器;
let arr = [1, 2, 3, 4, 5, 6, 7];
for (const a of arr.keys()) {console.log(a)
}
② values()
返回数组元素的迭代器
③ entries()
返回索引/值对的迭代器;
const a = ["foo","bar","baz","qux"];
// 因为这些方法都返回迭代器,所以可以讲他们的内容通过Array.from()装换为数组
// ...
Ⅱ)复制和填充方法
es6新增;
① fill()
在一个已有数组中插入全部后者部分相同的值,开始索引用于指定开始填充的位置,它是可选的。如果不提供结束索引,则一直填充到数组末尾;
const = zeroes = [0,0,0,0,0];
zeroes.fill(6,3) // 用6填充索引大于等于3index的元素
// [0,0,0,6,6]
② copyWithin()
按照指定范围浅赋值数组中的部分内容,然后将他们插入到指定索引开始的位置。注意参数不够默认按顺序指定参数,在原索引或者目标索引达到边界时停止。
copyWithin(插入到指定的位置,从何处开始,从何处结束)
let ints;
reset = ()=>ints = [0,1,2,3,4,5,6,7,8,9];
ints.copyWithin(5);
reset(); // [0,1,2,3,4,0,1,2,3,4]
// 注意点函数是在原数组上进行修改的;
Ⅲ) 转换方法
- 所有对象都具有转换方法:
toLoacaleString():结果和toString()函数一样,区别是每个字符串调用自己的toLoacaleString()方法;
toString():返回有数组中每个字符串拼接而成的一个逗号分隔的字符串;也就是每个字符串都会调用toString()方法
valueOf():返回数组本身;
Ⅳ) 栈和队列方法
栈实现
- push和pop()方法
- 队列方法
队列
- shift():出队;
- push():入队;
Ⅴ) 排序方法
两个方法对数组中的元素实现重排操作;
数组元素反向排列:
- reverse()
数组元素排序:
-
sort()
-
① 默认情况下:按照升序排列;具体方法中,排序开始,sort会对每个元素先进行调用String()函数处理,然后比较字符串来进行处理顺序;
-
**② **接受比较函数:
- 参数:比较函数接受两个参数,也就是比较的参数的排序元素;具体视比较函数的返回值而定
- 返回负值:第一个参数排在第二个参数前面;
- 返回0:两个参数相等;
- 返回正值:第一个参数应该排在第二个参数的后面;
let values = [0,1,2,3,4,5]; values.sort((a,b)=>a<b?1:a>b?-1:0) // 降序 // 最常用的一种方式: values.sort((a,b)=>a-b) // 升序 values.sort((a,b)=>b-a) // 降序
- 参数:比较函数接受两个参数,也就是比较的参数的排序元素;具体视比较函数的返回值而定
-
注意点:上述两个方法都是在原数组上进行修改的,且返回值都是调用数组的引用;
Ⅵ) 数组的操作方法(重要)
① concat() 操作方法
函数的作用:
- 在现有数组的基础上创建一个原数组的副本。
- 如果函数中有传递参数(一个或者多个都可以):将传入的数组的每一下项都添加到结果数组中;(可以设置数组是否进行强制打平,数组自己的特性,而非这个方法的专利)。
let arr = [1, 2, 3, 4, 5, 6, 7];
let arr1 = [11, 22, 33, 44];
let arr2 = [222, 333, 444];
let a = arr.concat(arr1, arr2);
console.log(a);
② slice()函数
函数的作用:用于创建一个包含原数组中一个或者多个元素的新数组;
函数参数:
- 一个参数:返回该索引到数组末尾的所有元素;
- 两个参数:第一个参数,开始位置索引,第二个参数,数组结束索引位置。
注意点:生成新的数组,类似于切片;如果结束位置小于开始位置函数返回空值;
③ splice()函数
函数作用:
注意点:这个函数是在原数组上进行操作的;
- 删除:函数中传入两个参数:第一个:要删除的元素的开始位置,第二个,要删除删除元素的数量;
- 插入:函数需要传入三个参数:第一个,开始位置,第二个:0也即要删除的元素数量,第三个:要插入的元素,后可以多个,也即插入的元素都可以写上去;
- 替换:也就是第二个参数不知定位0,而是删除的元素数量,后续用要代替的元素填充上去。
④ 搜索和位置方法
搜索的两种类型:按严格相等搜索,按断言搜索;
-
严格相等搜索方法:
- indexOf():从头开始搜索,找到返回下标,否则返回-1;
- lastIndexOf():从未尾开始搜索,找到返回下标,否则返回-1;
- includes():返回布尔值;
上述三种方法都可接收两个参数:第一个要搜索的元素和指定一个搜索开始位置;
-
断言函数的搜索方法:
-
参数:断言函数接受三个参数,元素、索引和数组本身;
-
函数find()和findIndex()使用了断言函数;
- find():find返回第一个匹配的数组元素;
- findIndex():返回第一个匹配的元素的下标值;
这两个方法只找第一个,后续自动停止搜索;
const people = [{name:'Matt',age:27},{name:"Nicholas",age:29} ] console.log(people.find((element,index,array)=>element.age<28)); // {name:"Matt",age:27} console.log(people.find((element,index,array)=>element.age<28)); // 0
-
⑤ 迭代方法
数组的5个迭代方法;
1)every():对数组每一项都执行传入的函数,如果每一项都返回true,则这个方法返回true;
2)filter():对数组的每一项都运行传入的函数,函数返回true的项会组成一个数组进行返回;
3)forEach():对数组的每一项都运行传入的函数,没有返回值;
4)map():对数组的每一项都运行传入的函数,将每一项的运行结果构成数组然后返回;
5)some():对数组每一项都运行传入的函数,如果数组中有一项函数返回true,则这个方法返回true;
let arr = [1, 2, 3, 4, 5, 6, 7];
console.log(arr.every((e, index, arr) => e > 6)); // false
console.log(arr.filter((e, index, arr) => e <= 6)); // [1, 2, 3, 4, 5, 6]
console.log(arr.forEach((e, index, arr) => e += 2)); // undefined
console.log(arr.map((e, index, arr) => e *= 2)); //[2, 4, 6, 8, 10, 12, 14]
console.log(arr.some((e, index, arr) => e > 0)); // true
Ⅶ) 归并方法
注意点:在使用箭头函数后续使用{}号是,一定要有返回值,不带{}默认自动有返回值,多句要记得自己写;
① reduce()② reduceRight(),数组的两个归并方法,这两个方法都会迭代数组中的每一项数据,并在此基础上构建一个最终返回值;区别,遍历的方向;
函数的参数:这两个方法接受两个参数;
-
第一个:对每一项都会运行的归并函数;
- 其中传入的函数接受四个参数:
function(prev,cur,index,array):- prev:上一个归并值;
- cur:当前项;
- index:当前项的索引
- array:数组本身。
- 其中传入的函数接受四个参数:
-
第二个:可选的以其为归并起点的归并初始值;
let values = [1,2,3,4,5];
let sum = values.reduce((prev,cur,index,array)=>prev+cur);
// 15let sum = arr.reduce((prev, cur, index, arr) => {cur = cur * 2;return prev + cur;})console.log(sum);
三)Map和Set
Ⅰ) Map
① Map的操作方法
1)添加:set()方法添加键/值对。
2)查询:
- get()方法和has()方法进行查询;
- get()方法返回要查询键的值;
- has()方法返回布尔值;
- size()获取映射中键值对的数量;
3)删除:
- delete():删除指定键对应的键/值对;
- clear():清除映射中所有的键值对;
② 顺序与迭代
Map实例维护键值对的插入顺序,因此可以根据插入顺序进行迭代;
迭代方法:
1)entries()方法
使用entries()方法,它是默认迭代器,可以直接对映射实例进行扩展,把映射转化为数组;
const m = new Map([["key1","vale1"],["key2","vale2"],["key3","vale3"]
])
for(let pair of m.entries()){console.log(pair)
}// [key1,vale1],// [key1,vale1],// [key1,vale1],
2)回调方法forEach()
接受两个参数:第一个,传入的回调函数,第二个,用于指定this值;
m.forEach((val,key)=>console(`${key}->${val}`));
- keys()和values()可以分别返回按顺序生成的键和值的迭代器;
for(let key in m.keys()){console.log(key)
}
// 按顺序返回键值对中有对象的键;
Ⅱ)Set
创建:直接new可以在其中添加一个可迭代对象;
① 操作方法
add():增加值;
has():查询,返回布尔值;
size():获取集合的元素数量;
delete():删除;
clear():整个删除;
② 顺序与迭代
集合的迭代器的获取方法,set.keys()和set.values()都可以取得,他们是等价的;
其中values是默认的迭代器;
使用方式和Map一样迭代可以使用回调函数的方式进行一系列操作;
注意点:Array、所用定型数组、Map、Set都兼容扩展操作符,这种操对可迭代对象实行浅赋值操作特别有用;
let arr1 = [1,2,3];
let arr2 = [...arr1]
console.log(arr1 === arr2) // false;
let arr3 = [0,...arr,4,5]
console.log(arr3) // [0,1,2,3,4,5]
第7章-迭代器与生成器
第8章-对象、类与面向对象编程
对象:对象就是一组没有特定顺序的值。对于每个属性和方法都由一个名称来标识,这个名称映射到一个值。
1.1)对象的理解
Ⅰ 创建对象
① 方式一:
- 创建Object的一个新实例,然后给他添加属性和方法。
② 方式二:
- 字面量创建对象
let person = {name:'xiaohong',age:23,sayName(){console.log('hello'+this.name)}
}
Ⅱ 属性的类型
- 对象的属性分为两类:数据属性和访问器属性。
① 数据属性
注意:要修改属性的默认值必须使用函数Object.defineProperty()
数据属性包含一个保存数据值的位置,值会从这个位置读取,也会写入到这个位置。数据属性有四个特性来描述他们的行为:(特性使用双[]表示他们在js对象外部是访问不到的,但是定义时不用,且是小写)
- [[Configurable]]:表示数据可否通过delete删除并重新定义,可否修改他的特性,以及是否可以将他改成访问器属性。默认true
- [[Enumberable]]:表示属性可否通过使用for-in循环返回。默认true
- [[Writable]]:表示对象值可否被修改。默认true
- [[Value]]:包含属性的实际值,默认undefined。
let book = {age: 10,editor: 1}Object.defineProperty(book, "old", {configurable: true,enumerable: false,writable: false,value: 2017,})console.log(book); // {age: 10, editor: 1, old: 2017}
② 访问器属性
访问器属性不包含数据值,相反他们包含一个获取函数getter和一个设置函数setter。(这两个都是非必须的)
- getter:在读取访问器的时候,会调用获取函数,这个函数就是返回一个有效的值。
- setter:在写入访问器时候会调用设置函数,这个函数决定对数据做怎样的修改。
访问器属性的四个特性描述:
- [[Configurable]]:表示熟悉可否通过delete删除并重新定义,可否修改他的特性,以及是否可以将他改成访问器属性。默认true
- [[Enumberable]]:表示属性可否通过使用for-in循环返回。默认true
- [[Get]]:获取函数在读取时调用。默认undefined
- [[Set]]:设置函数,在写入属性时调用。默认undefined
Ⅲ 属性的特性修改与定义
①Object.defineProperty()
**使用Object.defineProperty(‘属性的对象’,‘属性的名称’,‘描述符对象’)**实现。
!注意点:数据属性使用上述函数直接可修改已经存在的这些特性,(数据属性在对象直接定义后就存在了),但是对于访问器属性这个函数时用来定义的,访问器属性是不能直接定义的,也就是要定义属性的特性最好在创建的过程中就直接定义好。
let book = {year_:2017,edition:1
};// !!!!例子说明,这里防止重复调用访问函数,需要将属性名字设置不一样,
Object.defineProperty(book,'year',{get(){return this.year_;},set(newValue){if(newValue>2017){this.year_ = newValue;this.edition +=newValue-2017;}}
})
book.year = 2018;
print(book.edition) // 2// 例二
let book = {age: 10,editor_: "matt"}Object.defineProperty(book, "editor", {get() {// console.log("hello get");return this.editor_ + "good!";},set(newValue) {console.log("hello set");this.editor_ = newValue;}})console.log(book.editor);console.log(book.editor = "lisi");
② 定义多个属性
使用defineProperties()可以一次定义多个属性;
let book = {};
Object.defineProperties(book,{year_:{value:2017},edition:{value:1},year:{get(){return this.year_;},set(newValue){if(newValue>2017){this.year_ = newValue;this.edition +=newValue-2017;}}}
})
上述demo一次性定义了数据属性:year_和edition反访问器属性year。这里很重要
1.2)对象的一些操作方法(重要)
Ⅰ 对象属性的定义
上述对对象属性定义;
Ⅱ 读取属性的特性
Object.getOwnPropertyDescriptor()方法可以取得指定属性的描述符。
- 参数:接受两个参数,属性所在的对象和要去的其属性的属性描述符。
- 返回值:一个对象,针对访问器属性和数据属性有所不同
- 访问器属性:configurable/enumberable/get/set
- 数据属性:configurable/enumberable/writable/value
// 上例中
let b = Object.getOwnPropertyDescriptor(book, "editor")
console.log(b);
/* enumerable: false, configurable: false, get: ƒ, set: ƒ}
configurable: false
enumerable: false
get: ƒ get()
set: ƒ set(newValue)
*/
Ⅲ 合并对象(混入)
Object.assign()方法;
- 参数:接受一个目标对象和一个或多个源对象作为参数。
- 返回值:将这些传入的源对象的可枚举的的属性和自有属性整理添加到一个目标对象。
let dest,src,result;
dest = {};
src = {id:'src'};
result = Object.assign(dest,src);
// Objecte.assgin会修改目标对象
// 也会返回修改后的目标对象
dest === result // true;
dest !== src; // true;
result // {id:src};
dest // {id:src}
- 实现方式:就是对每个源对象进行浅复制;
- 混入会覆盖重复的属性。
- 注意点:混入失败不会发生回滚操作 ,它是一个尽力而为,可能只完成部分复制的方法。
Ⅳ 对象标识及相等判定
-
js中三等号(===)可以用来判断两个对象是否相等。
-
这个操作有一个对应的方法:Object.is()
-
方法接受两个参数。
-
注意点:
Object.is({},{}) // false; Object.is(NaN,NaN) // true; 三等中他是不相等的。
-
Ⅴ 增强的对象语法
① 属性值简写
let name = "Mate";
let person = {name:name;
}
//简写
let person = {name
}
② 可计算属性
有了可计算属性,就可以在对象字面量中完成动态属性的赋值。中括号包围的对象属性键告诉运行时将其当作js表达式。
const nameKey = 'name';
const ageKey = 'age';
const jobKey = 'job';let person = {[nameKey]:'Matt',[ageKey]:'27',[jobKey]:'software engineer'
}
// 创建成为动态的键
let uniqueToken = 0;
function getUniqueKey(key){return '${key}_${uniqueToken++}';
}
let person = {[getUniqueKey(nameKey)]:'Matt',[getUniqueKey(ageKey)]:'27',[getUniqueKey(jobKey)]:'software engineer'
}
print(person) // {name_0:'Matt',age_1:'27',job_2:'software engineer'}
注意点:上述创建操作失败后是不会发生回滚操作的。也就是动态创建对象失败就导致创建对象部分成功。
③ 简写方法名
// 常规:方法名:function(){}
// 简写: 方法名(){}
Ⅵ 对象的解构
什么是解构:对象解构就是使用与对象匹配的结构来实现对象属性赋值。
- 如果引用的变量值不存在,则这个值为undefined;
- null和undefined不能被解构,会报错
- 解构相当于浅拷贝。复制一个对象的引用给变量。
1.3 创建对象
Ⅰ 工厂模式
- 工厂模式:用于抽象创建特定对象的过程。
- 工厂模式可以创建多个类似的对象。
function createPerson(name,age,job){let o = new Object();o.name = name;o.age= age;o.job = job;o.sayName = function(){print(this.name)};return o;
}
// 工厂模式创建相同的对象;
let person1 = createPerson('xiaoli',20,'software engineer');
let person2 = createPerson('xiaohong',19,'MBA stundent')
Ⅱ 构造函数模式
ECMAscript中的构造函数是用来创建特定类型对象的。
上述例子构造函数实现
// 构造函数本身
function Person(name,age,job){this.name = name;this.age =age;this.job = job;this.sayName = function(){print(this.name)}
}
// 创建对象
let person1 = new Person('xiaoli',20,'software engineer');
/*
区别上述:
这里没有返回值;
属性和方法直接赋值给this
*/
-
注意点:构造函数的首字母需要大写,非构造函数不用;(编程规定)
-
构造函数创建实例使用new关键字;
- new的作用和内部过程
- 在内存中创建一个新的对象、
- 这个新对象内部的[[prototype]]特性被赋值为构造函数的prototype属性。
- 构造函数内部的this值被赋值为这个对象(this指向新的对象)
- 执行内部代码
- 返回创建的这个新对象。
- new的作用和内部过程
-
确定对象类型的方法:instanceof,自定义对象相较于工厂模式可以确定创建的对象的类型。
person1 instanceof Object // true; person1 instanceof Person // true;
① 构造函数也是函数
区别:构造和函数和普通函数的区别就是调用方式不同。
构造函数:任何使用new关键字定义的函数都是构造函数,否则就是普通函数。
注意点:只有函数作为对象的方法时,this指向创建的实例,否则所有的函数,在没有明确设置其this值时(apply、call),this始终指向全局。
② 构造函数的重复创建问题
构造函数的问题:其定义的方法都会在每个实力上都创建一遍,这就造成了相同的方法被重复创建。
- 上文中创建的person1和person2中的sayName方法相同但是时在实例1,2中都创建了一次。
- 解决方案:将方法提出来,改成全局方法。
function Person(name,age,job){this.name = name;this.age = age;this.job = job;'this.sayName = sayName; // 使用共同的全局方法
}
function sayName(){console.log(this.name)
}
let person1 = new Person(...)
let person2 = new Person(...)
Ⅲ 原型模式
原型:prototype,每个函数在创建时就会创建一个prototype属性(指向原型对象),这个属性是一个对象。在它上面定义的属性和方法可以被所有实例共享。
① 原型
- 主要创建一个函数就会给这个函数船舰一个prototype属性(指向原型对象),默认情况下所有的所有的原型对象都会获得一个constructor属性,指回与之关联的构造函数的原型。注意,实例与构造函数原型直接有直接联系但是与构造函数之间没有直接联系。
-
确定两个对象之间的关系:
- 方法一:isPrototypeOf()
- 函数作用:谁是不是谁的原型。
console.log(Person.prototype.isPrototypeOf(person1)) //true,Person为实例person1的构造函数
-
方法二:Object.getPrototypeOf()
- 获取对象的实例。
Object.getPrototypeOf(person1) // Person.prototype;
- 方法一:isPrototypeOf()
-
设置对象的原型
-
setPrototypeOf()
- 参数:第一个参数,要设置的对象实例,第二个参数,要设置成为原型的对象;
let biped = {numLen = 12; } let person = {name:'Matt } Object.setPrototypeOf(person,biped) // 设置person的实例为biped
-
Object.create()函数
-
参数:传入要作为原型的对象;
-
函数作用:创建一个新对象并为指定原型。
-
相对与上述方法性能好些。
-
-
② 原型层级:
原型链上的属性和方法是可以层级找到的。
-
方法:hasOwnPrototype
- 确定某个属性是在实例上还是原型对象上;(本身或者要通过原型链层级找.
person1.hasOwnproperty('name') // 返回值为布尔值
-
方法:Object.getOwnPropertyDescription
- 只针对实例属性有效,如果要查询原型上的某个属性就必须在原型上使用这个方法。
-
方法:in操作符
- 确定实例是否有某个属性(在实例本身或者原型链上都可以,只要能找到)
console.log('name' in person1) // true;
-
组合使用:
- 确定一个属性是否是原型属性
!obeject.hasOwnProperty(name) && (name in object) // 结果为真则这个属性就是在原型上的。
③ 属性的枚举方法
方法 | 不可枚举可访问否 | 枚举顺序 | 特点 |
---|---|---|---|
for-in | 不确定 | ||
Object.keys(Person.prototype) | 不确定 | ||
Object.getOwnPropertyNames() | 可以 | 确定 | |
Object.getOwnPropertySymbols() | 确定 | 只返回符号属性 | |
Object.assign() | 确定 |
- 顺序:先以升序,再依据插入顺序。(排序主和辅);
对象迭代
- Object.values()和Object.entries()
- Object.values()接受一个对象返回对象值的数组
- Object.entries()接受一个对象返回键/值对的数组。其中非字符串属性会被转换为字符串输出。
- 上述两个方法都是执行的浅拷贝。
④ 原型的相关操作
原型的赋值(重写原型)
- 可以使用给一个对象的原型直接指定prototype,这个prototype类是自己写的一个对象。
function Person(){};
Person.prototype = {constructor:Person, // 再次指回来name:'Matt',age:21,job:"software engineer",sayName(){console.log(this.name)}
}
- 注意点:
- 这样赋值后,这个对象原型的constructor属性丢失,因为此次赋值的对象中未定义constructor,所有此处要重新赋值一次指向。
- 以这种方式添加的constructor属性是可以枚举的。但是原生的这个属性是不可枚举的。此处可以使用Object.defineProperty(Person.prototype,‘constructor’)再次配置。、
原型的动态性:
就算实例实现创建出来的,只要原型上的方法和属性发生改变,这个实例还是会访问到修改后的属性和方法。而重新赋值会使得实例和原型之间的联系被切断。
注意点:但是:重写构造函数上的原型后在创建实例,这些实例会引用新的原型。但是再次之前的实例任然使用最初的原型。
原生对象原型:
如果要对写好的原生的原型对象进一步更改,推荐做法:创建一个对象,让他继承原来的原型对象,然后再这个对象上进行一些更改。防止对原生的函数发生修改出现混乱的情况。
原型的问题:
所有实例都会取得原型上的属性值,使得构造函数向实例传值能力弱化。并且实例取到数据可以进行操作,发生数据污染的情况。
// 实例1向原型中一个数组push值,所有的实例拿到的这个数组都会发生修改。
1.4 继承(重要)
js中继承主要是通过原型链实现的。
Ⅰ 原型链
原型链继承就是将实例的原型链赋值为指定要继承的类。
function SuperType() {this.color = ['red', 'blue', 'yellow']console.log("Super");}// 原型链继承function SubType() {console.log("sub");}SubType.prototype = new SuperType();let ins = new SubType();console.log(ins.color);
① 确定原型与实例之间的关系
-
方法一:instanceof
解释:某个实例属性对象谁否
console.log(instance instanceof Object) // true;
- 方法二:isPrototypeOf()
② 原型链的问题
- (1):所有实例共享原型链上的属性,实例修改其中的属性会发生数据污染。
- (2):子类型在实例化时不能给父类型传递参数。
Ⅱ 盗用构造函数继承
方法:在子类构造函数中调用父类构造函数。具体:使用**apply()函数和call()**函数以新创建的对象为上下文作为构造函数。
function SuperType(){this.colors = ['red','blue','green']
}
function SubType(){SuperType.call(this); // 使用call函数改变SuperType函数的执行上下文,此处this就是函数subType的上下文。
}
let instance1 = new SubType();
console.log(instance1.colors) // ['red','blue','green']
**参数:**相比于原型链继承,盗用构造函数继承可以实现参数的传递。
function SuperType(name){this.name = name;
}
function SubType(){SuperType.call(this,'Matt'); // 此处可以传递参数;this.age = 12;
}
let instance1 = new SubType();
console.log(instance1.name) // Matt
缺点:子类无法访问父类原型上定义的方法。只能在构造函数中定义方法。
Ⅲ 组合继承(重要)
实现:综合原型链继承和构造函数继承。
思路:利用继承原型链上的方法,使得方法可以被每个实例重用。利用call函数传递参数,每个实例都可以传递自己的参数。
function SuperType(name){this.name = name;this.colors = ['red','blue','green']
}
SuperType.prototype.sayName = function(){ // 原型上添加方法console.log(this.name);
}
function SubType(name,age){this.age = age;// 继承属性;SuperType.call(this,name);}
// 继承方法
SubType.prototype = new SuperType();
// 添加自己的方法
SubType.prototype.sayAge = function(){console.log(this.age);
}
let instance1 = new SubType('Matt',20);
Ⅳ 原型式继承
方法:object.create()
本质:你有一个对象,想在他的基础上再创建一个新对象。它对源对象进行了一次浅复制。他是将传入的第一个对象作为新对象的原型,进行了一次对象的浅复制。
使用情况:原型式继承非常适合不需要单独创建构造函数的情况。但任需要再对象间共享信息的场合。
let person = {name:'Matt',friends:['xiaohong','xiaoliu','xiaozhang']
}
let anotherPerson = Object.create(person)
anotherPerson.name = "Grey";
anotherPerson.friends.push('Rob')
console.log(person.friends) // 'xiaohong','xiaoliu','xiaozhang','Rob'
参数:一:作为新对象原型的对象,二:给新对象定义的额外属性
Ⅴ 寄生式继承
与原型式继承比较相似的一种继承方式就是寄生式继承
思想:创建一个实现继承的函数,以某种方式增强对象,然后返回这个的对象。
function createAnother(original){// 通过调用函数创建已一个对象let clone = object(original) //objece功能类似于object.create()clone.sayHi = function(){ //以某种方式增强这个对象。console.log('Hi');}return clone; // 返回这个对象
}// 例二
// 寄生式继承
let Person = {name: "Matt",friends: ["xiaohong", "xiali", "xiabingbao"],
}function createAn(O) {let clone = Object(O);clone.sayHi = function () {console.log("hello");}return clone;}
let c = createAn(Person)
c.sayHi()
console.log(c);
上述函数createAnother函数接受一个参数,这个参数就是新对象的基准对象,这个对象被传递给object函数创建一个新对象,然后将返回的新对象复制给clone。后续添加一些方法给clone,最后返回这个对象。
let person = {name : "Matt",friends:['xiaohong','xiaoli','xiaoliu']
}
let anotherPerson = createAnother(person);
anotherPerson.sayHi(); // 'Hi'
缺点:寄生式继承给函数添加方法导致函数难以重用。每个实例都添加自己的函数,重复造轮子。
Ⅵ 寄生式组合继承(重要)
组合继承的问题(重要):效率问题:组合继承的父类构造函数会被调用两次,一次在创建子类的原型的时候,一次是在子类构造函数中。
function SuperType(name){this.name = name;this.colors = ['red','blue','green']
}
SuperType.prototype.sayName = function(){ // 原型上添加方法console.log(this.name);
}
function SubType(name,age){this.age = age;// 继承属性;SuperType.call(this,name); // 第二次调用SuperType函数
}
// 继承方法
SubType.prototype = new SuperType(); // 第一次调用SuperType
SubType.prototype.constructor = SubType // 指向自己!!!
// 添加自己的方法
SubType.prototype.sayAge = function(){console.log(this.age);
}
let instance1 = new SubType('Matt',20);
寄生式组合继承思路:不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本。
function inheritPrototype(subType,superType){let prototype = object(superType.prototype); // 创建对象,注意此处为prototypeprototype.constructor = subType; // 增强对象,指回自己,防止constructor属性的丢失subType.prototype = prototype; // 赋值给新的对象
}
// 寄生式组合继承function inherit(sub, Sup) {let prototype = Object(Sup.prototype);prototype.constructor = sub;sub.prototype = prototype;}// 1.0 组合继承function Person(name) {this.name = name;}Person.prototype.say = function () {console.log(this.name + 'Hello');}function Son(name, age) {Person.call(this, name);this.age = age;}inherit(Son, Person);// 添加自己的方法Son.prototype.sayGood = function () {console.log(this.name + 'good');}// 2.0寄生let son = new Son("matt", 20)console.log(son);console.log(son.age);console.log(son.name);son.sayGood()son.say()
2 )类
上述各种对象的继承方式都存在这一些问题。es6定义了class来实现类。他背后还是使用原型链和构造函数的概念。
2.1)类的定义
Ⅰ类声明
class Person{}
Ⅱ 类表达式
const Animal = class{};
注意点:
- 与函数表达式一样,类表达式在他们被求值前也不能引用。与函数定义不同的是,函数声明可以提升,但是类声明是无法提升的。
- 函数受函数作用域的限制,类受到块作用域的限制。
2.2)类的构造函数
constructor关键字:用于在类定义块内部创建类的构造函数。方法名constructor会在使用new操作符创建类的新实例时,应该调用这个函数。
- 实例化:使用new关键字会执行下述操作:
- 在内存中创建出一个新对象
- 新对象内部的[[prototype]]指针被复制为构造函数的prototype属性。
- 构造函数内部的this被赋值为这个新对象(即this指向这个新的对象)
- 执行构造函数中的代码(给新对象添加属性)
- 构造函数非空则返回这个对象,否则返回刚创建的新对象
注意点:类实例化时传入的参数会用作构造函数的参数。类必须使用new定义。
typeof 检测类返回函数,类就是一个函数。
class Person{}
console.log(typeof Person) // function;
注意点:类本身在使用new关键字时就被当成构造函数使用,类定义的constructor不会被当成构造函数,对他使用instanceof会返回false;
p1 instanceof Person.constructor //false;
p1 instanceof Person //true;
2.3)实例原型和类成员
使用类实例化后的成员,都是唯一的,就算他们属性值一样,但是在内存中他们不一样。
① 原型方法和构造器
为了在实例间共享方法,类定义语法把在类块中定义方法作为原型方法。
class Person{constructor(){// 添加到this的所用内容都会存在于不同的实例上this.local = ()=>{console.log('instance')}}// 在类块中定义的所有内容都会定义在类的原型上local(){console.log('prototype')}
}// 类的原型上的访问方式:
Person.prototype.local() // prototype
类也支持获取和设置访问器,和函数的行为一样。
② 类的静态方法
类的静态方法定义在类本身上,使用static关键字作为前缀
class Person{constructor(){// 添加到this的所用内容都会存在于不同的实例上this.local = ()=>{console.log('instance')}}// 在类块中定义的所有内容都会定义在类的原型上local(){console.log('prototype')}// 定义在类上的静态方法static local(){console.log('class',this)}
}
let p = new Person();
p.local(); // instance;
Person.prototype.local(); // prototype;
Person.local() // class, class Person{}// 注意点:方法会一步步向上找,这里p.local()可以调用到原型上的方法,前提是他本自身没有这个方法,然后回向上找。
③ 生成器和迭代器
生成器用法:
- 语句中使用yield关键字;
- 使用时调用next()方法会一步步执行;
class Person{*createName(){yield 'xiaohong';yield 'xiaoliu';yield 'xiaoli'}
}
let p = new Person();
let N_name = p.createName();
N_name.next().value // xiaohong
N_name.next().value // xiaoliu
N_name.next().value // xiaoli// 例二 注意点:函数中的*号不能缺失,注意执行方式class Person {*crt() {yield "hello 1";yield "hello 2";yield "hello 3";}}let p = new Person();let d = p.crt();console.log(d.next().value);console.log(d.next().value);console.log(d.next().value);
迭代器
因为支持生成器方法,所以就可以实现迭代器方法。
class Person{constructor(){this.namec = ['xiaohong','xiaoliu','xiaoli']}[Symbol.iterator](){yield *this.name.entires() //迭代器实现;}
}
let p = new Person();
for(let [idx,name] of p){ // for...of 主要用在可迭代对象上。console.log(name)
}
// 'xiaohong',
// 'xiaoliu',
// 'xiaoli'
-
内置迭代器
可迭代的对象,都内置以下3种迭代器
entries(): 返回一个迭代器,值为键值对
values(): 返回一个迭代器, 值为集合的值
keys(): 返回一个迭代器,值为集合中的所有键
2.4)类(es6)的继承
es6新增:原生支持了继承,本质还是使用的原型链。
① 继承基础
extends关键字继承:可以继承所有拥有[[Constructor]]和原型的对象。
class Vehicle{}
class Bus extends Vehicle {} // 创建一个新类继承自Vehicle;// 继承举例class Person {constructor(name, age) {this.name = name;this.age = age;}sayHi() {console.log(`hello i am ${this.name}`);}}// let p = new Person("p", 18)// p.sayHi()// console.log(p.name);class Son extends Person {sayChinese() {console.log(`我是${this.name}`);}}let s = new Son("zh", 10)s.sayHi() s.sayChinese()console.log(s);
/*
hello i am zh
class.html:66 我是zh
class.html:72 Son {name: 'zh', age: 10} */
② 构造函数、HomeObject和super关键字
派生类:利用继承机制,新类可以从已有的类中派生。
class Animal{constructor(){this.type = '哺乳类';}static get flag(){return '动物'}eat(){console.log('eat')}
}
// es6中的继承都干了些啥事
// Tiger.__proto__ = Animal
// Animal.call(this)
// Tiger.prototype = Object.create(Animal.prototype)class Tiger extends Animal{// 实例在传递参数的过程中必须使用到constructor,这里继承就必须使用super否则this值不对constructor(){// 在使用this前必须调用supersuper(); // Animal.call(this) super指向父类,父类自己的this在它本身上console.log(this.type)}// 此处定义一个静态方法,然后他实际上是调用父类的方法static getFlag(){return super.flag // 静态中的方法指向的是父类,父类静态方法定义在父类本身上}// 子类调用父类中的方法eat(){ // 类的重写super.eat(); // 这里的super指向父类的原型。因为父类的方法定义在父类的原型上}
}
let tiger = new Tiger('白老虎');
console.log(tiger.eat()) // eat// 自写例二// 继承class Person {constructor(name, age) {this.name = name;this.age = age;this.sex = "man"}sayHi() {console.log(`hello i am ${this.name}`);}}// let p = new Person("p", 18)// p.sayHi()// console.log(p.name);class Son extends Person {constructor(name, age, height) {super();this.height = height; this.name = name; // !!!这里为何不能重用,需要再次写出来,否则未定义this.age = age;}sayChinese() {console.log(`我是${this.name}`);}}let s = new Son("zh", 10, 180)s.sayHi()s.sayChinese()console.log(s.name);console.log(s.height);console.log(s.sex);
super关键字:
这个关键字只能在派生类中使用,而且仅限于构造函数、实例方法和静态方法内部
- 在构造函数中使用:super调用父类构造函数。
- 父类的构造函数就是constructor
- 在实例方法中使用:
- ???
- 在静态方法中使用
- super相当于父类
使用super要注意的几个问题:
- super 只能使用在派生类中(extends构建的类)
- 不能单独使用super关键字
- 调用super会调用父类构造函数,并将返回的 实例赋值给this
- 不能在调用super之前使用this值
- 。。。还有几个不能理解
③ 基类
定义:它只供其他类继承,但是自己本身不被实例化。
实现:此类不可使用new关键字,也即new.target如果非空则报错
class Vehicle{constructor(){if(new.target === Vehicle){throw new Error('Vehicle cannot be directly instantiated')}}
}
④ 类混入
类混入:实现多类继承:将不同类的行为集中到一起。
注意点:Object.assign()用来实现对象的混入方式,此处是类的混入方式
混入模式:混入模式可以通过在一个表达式中连缀多个混入元素来实现,这个表达式最终会解析为一个可以被继承的类。
实现:使用嵌套继承或者reduce函数,写辅助函数实现继承。
第9章-代理与反射
定义:代理是目标对象的抽象,代理作为目标对象的替身,但又完全独立于目标对象。
1.1)代理的创建
Ⅰ)代理
代理是通过Proxy构造函数创建的。构造函数接受两个参数:目标对象和处理程序对象。
const target = {id:'tartget'
};
const handler = {};
const proxy = new Proxy(target,handler) // 目标函数,处理程序(就是相关操作)
- 区分代理和目标:可以使用严格相等来区分;(===);
- hasOwnproperty()方法:在代理和目标中都会返回相同的结果。
Ⅱ 代理的作用
① 创建捕获器:
捕获器就是在处理程序对象中定义的“基本操作的拦截器“。例如get和set的定义。
执行方式:通过触发目标函数中的相关操作,j就会执行处理程序中的代码。
- 关于get的触发:js代码中以下方式都会触发get()捕获器的执行:
- proxy[property]、proxy.property、Object.create(proxy)[property];(也就是对对象的访问一般会触发捕获器get()的执行)
const target = {foo:'bar';
}
const handler = {get(){ // 捕获器在处理程序中以方法名为键return 'handler override';}
}
const proxy = new Proxy(target,handler)
// 当通用代理对象执行get()操作时就会触发定义的get()捕获器。
注意点:只有在代理对象上执行这些操作才会触发捕获器,在目标上执行这些操作任然会产生正常的行为(也就是不会执行定义的handler中的相关代码);
② 捕获器参数和反射API
-
捕获器参数:捕获器会接收到三个参数:目标对象、要查询的属性和代理对象。
const target = {foo:'bar'; } const handler = {get(trapTarget,property,receiver){console.log(trapTraget === target);console.log(property);console.log(receiver === proxy);} } const proxy = new Proxy(target,handler); proxy.foo; // true // foo // true
-
反射API:所有捕获器都可以基于自己的参数重建原始操作,但并非所有的捕获器都和get那样简单(get的原始方法),开发时不想重建就使用反射方法,它封装了原始的方法。(此处类似于函数的继承,在原函数基础上添加新的方法)
const target = {foo:'bar',baz:'qux' }; const handler = {get(trapTarget,property,receiver){let decoration = '';if(property === 'foo'){decoration = '!!!';}return Reflect.get(...arguments)+decoration; // 原始方法+添加新的一部分demo} } const proxy = new Proxy(target,handler); console.log(proxy.foo) // bar!!! console.log(target.foo) // bar console.log(proxy.baz) // qux console.log(target.baz) // qux
-
捕获器不变式:如果一个属性不可变就无法定义相关捕获器。
const target = {}; Object.defineProperty(target,'foo',{configrable:false,writerable:false,value:'bar' }) // 上述target就无法定义get的捕获器。
③ 可撤销代理
中断代理与目标对象之间的联系
Proxy的方法revocable(target,handler)可以切断他们之间的联系。
const target = {foo: 'hello'};const handle = {get(target, foo, proxy) {console.log(target);console.log(foo);console.log(proxy);}}let p = new Proxy(target, handle);p.foop = Proxy.revocable(target, handle); // 切断代理p.foo
④ 一等函数替代操作符
Reflect.get() // 可替代对象属性访问操作符
Reflect.set() // 可替代=赋值操作符
Reflect.has() // 可替代in或者with()操作
Reflect.deleteProperty() // 可替代delete操作
Reflect.construct() // 可替代new操作符
- 补充点:代理可以代理另一个代理,这样就可以实现对一个目标对象上构建多层拦截网。
Ⅲ 代理的不足之处
① 代理中的this值问题,代理中的this值设置和代理自身一样,这样用到目标对象上是this中的一些方法都是访问不到的。?
② 代理由于槽位问题,在Date对象中是无法很好的使用的。
1.2)代理捕获其与反射方法
代理可以捕获13种不同的基本操作。
①get()
get() // get()捕获器在获取属性值的操作中会被触发
// get() 里面的三个参数,target目标对象,property属性,receiver代理对象本身
② set()
set() // 捕获器在设置属性值的操作中被调用
// // set() 里面的四个参数,target目标对象,property属性,value要赋值给属性的值,receiver代理对象本身
③ has()
has() // 捕获器会在in操作中被调用
// 两个参数,target目标对象,property属性
④ defineProperty()
// defineProperty捕获器在Object.defineProperty中被调用
// 三个参数,target目标对象,property属性description属性值的描述
const myTarget = {};
const proxy = new Proxy(target,{defineProperty(target,property,description){console.log('defineProperty()')return Reflect.defineProperty(...arguments)}
})
// 调用
Object.defineProperty(proxy,'foo',{value:'bar'})
// 注意description是一个对象,里面包含了这个属性的一些可配置方式。具体有
/*
enumerable:可遍历不;
configurable:可配置不
writable:可修改不
value:值
get和set
*/
⑤ getOwnPropertyDescriptor()
getOwnPropertyDescriptor()//捕获器在Object.getOwnPropertyDescriptor()中被调用
// 函数的返回值为一个对象,也就是上一个函数中定于的description对象。
// 参数,函数接受两个参数 target,proxy
⑥ ownKeys()
// 触发方式Object.own()方法等
// 返回值是一个可迭代的对象或者字符串符号,也就是对象的所有property属性名字
⑦ setPrototypeOf()
setPrototypeOf()函数的作用,可以指定一个对象的原型
// 参数:target prototype:target的替代原型。
⑧ apply()
// apply()捕获器会在函数被调用的时候被触发!
// 参数:target,thisArg:调用函数时的this对象,argumentsList:调用函数时的参数列表
…
补充:
以上函数巧妙应用可以实现:跟踪属性访问、隐藏属性、属性验证、函数与构造函数参数验证、数据绑定与可观察对象。
第10章-函数
1.1) 函数定义的方式
函数声明。函数表达式。箭头函数。构造函数。
// 函数声明式定义
function sum(num1,num2){return num1+num2
}
// 函数表达式
let sum = function(num1,num2){return num1+num2
}
// 箭头函数
let sum = (num1,num2)=>{return num1+num2
}
// 构造函数
let sum = new Function('num1','num2','return num1+num2') // 不推荐
-
区别:函数声明于函数表达式
- 函数声明相当于变量的声明,会发生提升。
- 函数表达式就相当于普通的表达式,后续函数在表达式执行后才会存在可以调用。
-
箭头函数
- 箭头函数的返回值使用大括号就生成了函数体,可以在内部写多句语句。
- 注意点:箭头函数不能使用argument、super、new.target,也不可以用作构造函数。此外箭头函数还没有prototype属性。
1.2) 函数的对象
Ⅰ 函数的对象
**函数的对象有3个:argument、this和new.target。**
-
② argument:
-
类数组对象,保存传入函数中的所有参数,可以使用中括号语法访问其中的值,可以使用length方法获取其长度。(箭头函数没有此属性,箭头函数中只能使用具名参数进行参数值的调用。可以使用值传递传给箭头函数使用argument。相当于传参了)
-
函数的参数:js函数定义时的参数个数和传入的参数个数可以不一致的原因:函数内部的参数被保存在一个数组中,函数调用时总会接受一个数组。
-
argument对象可以和命名参数一起使用,他们的使用效果是一样的,但是argument和命名参数在内存中是不同的位置进行保存的。严格模式下,修改argument不会使得命名参数的值发生改变,重写argument会报错。
-
argument的属性:
-
callee属性:可以实现让函数逻辑与函数名解耦的操作。(此后这个函数的名字后续可以直接使用就像是函数表达式的函数名一样使用)
-
callee在递归中使用:
argument.callee本质:就是一个指向正在执行的函数的指针,解耦后后续改变函数名函数执行不受影响。
// 递归阶乘的实现 function factorial(num){if(num<=1){return 1;}else{return num*argument.callee(num-1)} }let trueFacorial = factorial; factorial = function(){return 0 } trueFacorial(5) // 120 factorial(5) // 0 //递归实现斐波那契数列 function factorial(num){if(num<=1){return 1;}else{return num*arguments.callee(num-1) // 在编写递归时这种写法是首选,严格模式下无法访问到callee属性,所以无法使用。} }
-
-
-
③ this对象:
- 标准函数中,this引用的是将函数当成方法使用调用的上下文对象。this值到底指向那里是有调用函数确定的。
- 箭头函数中,this指向定义箭头函数的上下文。
- 通常在事件回调中引用某个函数的this时this指向可能不是想要的对象,此时就可以使用箭头函数解决this的值。
-
⑤ new.target属性对象:
-
函数可以作为构造函数实例化一个新对象 nb nbm。
-
new.target检测函数是否使用new关键字调用。
- 不是:返回undefined;
- 是:newtarget引用被调用的构造函数。
function a() {console.log(new.target); } let aa = a() let aaa = new a()
-
1.3)函数的补充:
-
函数的name属性:es6之后函数都会自动创建一个属性name,保存这这个函数的标识,构造函数创建的则返回空字符串。
-
如果函数是一个获取函数、设置函数或者使用bind()实例化的则返回的name之前会添加一个前缀。
function foo(){console.log(foo.bind(null).name) // bound foo }
-
-
函数的参数:
-
暂时性死区:前面定义的参数不能应用后面定义的参数,否则引起暂时性死区。
-
参数扩展与参数收集:
-
使用参数扩展可以将数组作为函数的参数传入函数中,并且后续含可以传递其他值,他俩互不影响
-
参数收集:
-
function getSum(...values){// 顺序类加values中的所有值return values.reduce((x,y)=>x+y,0)// reduce('结果',初始值) }
-
-
私有变量:定义在任何函数内部或者块中的变量,可以认为是私有的。
- 特权方法(访问方法):类似于函数中定义的get方法和set方法。
- 注意点:使用闭包和私有变量会使得作用域变长,变量查找时间增加。
1.4)函数的属性和方法:
Ⅰ 函数的属性:
函数的属性主要有两个:length、prototype。
- ①length属性:这个参数保存函数定义的命名参数的个数。
- ②prototype属性:这个属性保存引用类型所有实例方法的地方,意味着toString()、valueOf方法 都保存在这里,使得所有实例共享。
function a() {console.log(new.target);console.log(a.name);console.log(a.length);}let aa = a()let aaa = new a()
Ⅱ 函数的方法:
apply、call函数都会以指定的this值来调用函数。即会设置调用函数时函数体内this对象的值。(狸猫换太子)
① apply函数
- 函数接受两个参数,this和参数数组,apply以数组传入,也可以是arugments对象。
②call函数
- 上面两个函数接受两个参数,this和一个一个传入的参数。
上述两个函数的主要作用是控制函数调用上下文即函数体内this值的能力,这两个函数的好处就是可以将任意对象设置为任意函数的作用域,这样对象就可以不用关系方法。
window.color = 'red';
let o = {color:'blue';
};
function sayColor(){console.log(this.color);
}
sayColor(); // red;
sayColor.call(this); //red;
sayColor.call(window); //red;
sayColor.call(o); //blue;
③ bind函数
bind会创建一个新的函数实例其this值会被绑定到传给bind的对象。
window.color = 'red';
var o = {color:'blue'
}
function sayColor(){console.log(this.color)
}
let objectSayColor = sayColor.bind(o);
object.sayColor(); // blue
??? toLocaleString()和toString()和valueOf()方法。
④ caller函数
函数的caller:
- 这个属性引用的是调用当前函数的函数,在全局作用域中为null;属性在严格模式下有所不同;
// callee:通过值调用原函数;
// caller:引用调用当前函数的函数;
function outer(){inner();
}
function inner(){console.log(arguments.callee.caller)
}
outer();
// outer()
Ⅲ 函数的尾调用优化
- 尾调用优化可以重用栈
尾调用条件:
- 确定外部栈真的没有必要存在了。
- 确切条件:
- 严格模式下执行
- 外部函数的返回值时对尾调用函数的调用。(函数返回值为一个函数的调用)
- 尾调用函数返回后不需要执行额外的逻辑
- 外调用函数不是引用外部函数作用域中自由变量的闭包。
1.5)闭包
定义:闭包是指那些引用了另一个函数作用域中变量的函数,通常发生在嵌套函数中。
- 函数执行上下文的作用域连中有两个变量对象:局部变量对象和全局变量对象。函数执行完成后会将局部活动对象销毁掉,内存中就只剩下全局作用域。闭包不一样,子一个函数内部定义的函数的活动对象会将把它包含的函数的作用域添加到自己的作用域连中。
- 如果闭包有引用,函数的作用域链就不能销毁,他执行完毕后才会销毁。这样下来闭包比其他函数占用内存空间大。
this对象:
-
全局对象:非严格模式:window,严格模式:undefined。
-
作为某个对象的方法调用,this指向这个对象,但是匿名函数不会绑定到某个对象上,他也不会向上寻找上去,而是直接指向window(非严格)。
- 针对上述问题的解决方案:定义一个变量保存这个this然后进行使用。
window.identity = 'The Window'; let object = {identity:'My object'getIdentityFunc(){let that = this;return function(){ // 匿名函数return that.identity;}} } object.getIndentityFunc()() // 'My object'
-
注意:this和arguments和上述一样使用。
-
注意赋值过程中的this:赋值表达式的值是函数本身,this值不绑定任何对象。也就是this原来的绑定对象会消失。
1.6)立即调用函数:
- 立即执行函数可以锁定参数的值,针对于之前没有块级作用域的情况实现的。
第11章-期约与异步
1)异步编程
Ⅰ)同步与异步
同步行为:同步对应内存中顺序执行的处理器指令。
异步:异步行为类似于中断。程序不会去等待一个长时间未结束的指令。
回调地狱:以前的JavaScript中,是通过回调函数来实现异步操作的,要串联多个异步操作时,这样的深度嵌套的回调函数,就被称为回调地狱。
2)期约对象Promise
es6新增了引用类型Promise,可以通过new操作来实例化。创建新期约时需要传入执行器函数作为参数。
Ⅰ)期约的三种状态
① 待定(pending)
待定状态可以落定为解决或者拒绝,但是他只能处于其中一种状态,且是不可逆的。
期约的状态是私有的,不能直接通过js监测到。
② 解决(兑现)(resolve/fulfilled)
③ 拒绝(rejected)
Ⅱ)期约的用途
① 抽象的表示一个异步,期约状态代表期约是否完成,期约状态最常见在发送网络请求的过程中,依据请求的成功与失败返回的状态码,改变期约的状态。
② 期约的异步操作会生成某个值,而程序执行转台发生改变就可以访问到这个值,
期约解决:就会有一个内部的值,期约失败:就会有一个内部的理由。
Ⅲ)通过指向函数控制期约的状态
执行器函数的主要作用:初始化期约的异步行为和控制状态的最终转换。
① 控制期约的状态转换:
- 通过调用函数:resolve(解决)和reject(拒绝)实现状态的转换,调用reject还会抛出错误。
- 执行器函数是同步执行的。
② 防止期约卡在待定状态:
- 通过添加一个计时器,当到一定时间自动调用拒绝状态,改变期约状态,因为期约只能处于一个状态中,所以拒绝后,在调用解决是不会执行任何操作的。
③ 实例化一个解决的期约
**方法:**Promise.resolve()
let p = new Promise.resolve();
// 或者
let p = new Promise((resolve,reject)=>resolve())
- 上述方法可以将所有的实例转换为一个解决的期约,包括一个错误对象
- Promise.resolve()函数是幂等的;
④ Promise.reject()函数
方法Promise.reject():实例化一个拒绝的期约,并且抛出错误;
- 这个错误不能通过try/catch捕获到,只能通过拒绝期约处理程序捕获到。
let p = new Promise.reject();
// 或者
let p = new Promise((resolve,reject)=>reject())
- 这个拒接的理由是传给Promise.reject的第一个参数,这个参数后续继续传递给拒绝的处理程序;
- 不幂等:这个函数中如果包装一个期约,他会将他作为一个期约拒接的理由。
重点:
拒绝期约抛出的错误不能被try/catch函数捕获到的原因:
- try/catch捕获的是同步的错误,他没有使用异步方式进行捕获,期约一旦执行异步任务只能使用异步的方式执行相关操作。
Ⅳ)期约的实例方法
期约的方法是连接外部同步代码与内部异步代码的桥梁。
① Promise.prototype.then()
- 注意点:Promise为一个对象他的方法直接Promise.then()和Promise.prototype都可以访问到。
Promise.prototype.then()方法作用:
-
为期约实例添加处理程序的主要方法。
-
参数:接受最多两个参数:(以下这两个参数都是可选的):顺序是:第一个参数为成功,第二个为失败
- onResolved处理程序
- onReject处理程序
function onResolved(id){setTimeout(console.log,0,id,'resolved') } function onRejected(id){setTimeout(console.log,0,id,'rejected') } let p1 = new Promise((resolve,reject)=>setTimeout(resolve,3000)); let p2 = new Promise((resolve,reject)=>setTimeout(reject,3000)); p1.then(()=>onResolved('p1')) p2.then(()=>onRejected('p2')) // 3秒后 // p1 resolved // p2 rejected
注意点:期约实例只能装换一次状态,所以这两个操作一定是互斥的。
-
结果:这个方法最后会返回一个新的期约实例。
-
这个新的期约实例基于onResolved处理程序返回值构建,也就是这个返回值会依据Promise.resolve包装上一个期约解决之后的值。没有返回语句默认包装undefined;
(1) 不传参:如果调用then后不给他传值,他会原样返回这个值;
(2)抛出异常:在其中抛出异常,返回拒绝的期约。注意,返回错误值不会触发上述行为,而是把这个错误对象包装在一个解决期约中;
② Promise.prototype.catch()
**作用:**Promise.prototype.catch()方法可以用于给期约对象添加拒绝处理程序;
参数:接受一个参数:onRejected处理程序;事实上这个方法就是一个语法糖,它相当于调用Promise.prototy.then(null,onRejected);
③ Promise.prototype.finally()
作用:Promise.prototype.finally()给实例添加onFinally处理程序,这个处理程序在期约解决或者拒绝都会调用这个处理程序;
**注意点:**但是这个处理程序没办法知道期约的状态的;即拒绝或者解决;
④ 重入与非重入期约方法?????
定义:当期约进入落定状态时,于该状态相关的处理程序仅仅会被排期,而非立即执行。跟添加处理程序 代码之后的同步代码一定会在处理程序之前先执行。称为非重入;
⑤ 抛出错误的区别
同步中抛出错误:同步中使用throw对象抛出错误,js运行时的错误处理机制会停止执行抛出错误之后的任何指令;
异步中:异步中抛出的错误实际上是从消息队列中一部抛出的**,所以不会阻止运行时继续执行同步指令**;
异步的错误只能通过异步的方法捕获到;
Ⅴ)期约连锁和期约合成
多个期约组合在一起可以构成强大的代码逻辑
① 期约连锁
解释:一个期约接一个期约的拼接;
由于期约的实例方法(then()、catch()、finally())都会返回一个新的期约对象,而这些新的期约又会有自己的期约方法,所以期约可以实现组合;这样的连缀方法调用就可以构成所谓的“期约连锁”;(串行化异步任务)。每个后续的处理程序都会等待前一个期约的解决,然后实例化一个新的期约便返回它;
- 期约是层序遍历
② 期约合成
解释:将多个期约组和为一个期约;
期约合成方法:
promise函数提供两个将多个期约实例组合成一个期约的静态方法;
(1):Promise.all()
- Promise.all()静态方法创建的期约会在一组期约全部解决之后再解决。这个方法接受一个可迭代对象,然后返回一个新期约;
(2):promise.race()
- promise.race()返回一个包装期约,是一组集合中最先解决或拒绝期约的镜像,这个方法接受一个可迭代对象,返回一个新期约;
- promise.race()不会针对期约返回的结果(拒绝或者接受)而区别对待,只要是第一个落定的期约,就会包装其解决值或拒绝理由返回新期约;只要落定就不受其他的影响;
3)异步函数
Ⅰ)async
async/await(语法关键字,es8增加)
async:这个关键字用于声明异步函数。这个关键字可以用在函数声明、函数表达式、箭头函数和方法上;
async function foo(){}
let bar = async function(){}
let baz = async ()=>{}
class Qux {async qux(){}
}
- (重要)使用async关键字可以让函数具有异步特征,但总体上代码是同步求值的;不过,当异步函数如果使用return关键字返回了值(如果没返回则会返回undefined),这个值会被Promise.resolve()包装成一个期约对象。异步函数始终返回期约对象;
async function foo(){console.log(1)return 3;
}
// 给返回的期约添加一个解决处理程序
foo().then(console.log)
console.log(2);
// 1
// 2
// 3
-
注意点:对于异步函数async:
- 在异步函数中抛出错误会返回拒绝的期约;
- 拒绝期约的错误不会被异步函数所捕获;
// 情况一 async function foo(){console.log(1);throw 3; } foo().catch(console.log); console.log(2) // 1 // 2 // 3// 情况二 async function foo(){console.log(1)Promise.reject(3); } foo().catch(console.log); console.log(2); // 1 // 2 // Uncaught (in promise):3
Ⅱ)await
使用await关键字可以暂停异步函数代码的执行,等待期约解决。await关键字会暂停执行异步函数后面的代码(和yield关键字是一样的;)
- await函数的限制:await关键字必须在异步函数中使。
- await关键字:它并非只是等待一个值那么简单,他会记录在哪里暂停执行。等到await右边是值可用了,js运行时会在消息队列中推送一个任务,这个函数会恢复异步函数的执行。(红宝书p354例子)
综合:async/await中真正起作用的是await,async关键字只作为异步的标识,如果异步函数中不包含await关键字,其执行和普通函数没啥区别;(但他任然是一个异步函数,具体例子见上述)。
第23章-JSON
JSON:JavaScript对象简谱(JavaScript Object Notation),是一种数据结构
1)语法
Ⅰ JSON支持的3种类型值
① 简单值:
字符串、数值、布尔和null,特殊值undefined除外。JSON字符串必须使用双引号,单引号会报错。
② 对象
JSON中的对象必须使用双引号将属性名包围起来。
对比js对象区别
- JSON没有变量声明,他就没有变量
- JSON最后没有分号(不需要因为他不是js语句)
// JSON数据结构举例
{"name":"Matt","age":29,"school":{"name":"SWJT","location":'chendu'}
}
注意点:JSON中不能出现属性名相同的对象。
③ 数组
数据和对象的结合可以表述复杂的对象结构。
2)解析与序列化
JSON可以直接被解析为可用的JavaScript对象。
Ⅰ)JSON对象
JSON对象的两个方法:
-
stringify():将JavaScript对象序列化为JSON字符串。
-
parse():将JSON解析为原生JavaScript值。
注意点:默认序列化为JSON的字符串不会自动换行。
① 转换规则
- 在序列化JavaScript对象时,所有的原型成员和函数都会被有意的在结果中省略。
- 值为undefined的任何属性也会被跳过,最终得到的实例属性均为有效的数据类型表示。
Ⅱ)stringify函数/序列化选项
①参数
第一个:要被序列化的对象。
第二个:过滤器,可以是数组或者函数
- 数组的情况:JSON.stringify()返回的结果中仅包含该数组中列出的对象属性。
- 函数的情况:函数接受两个参数:属性名和属性值,可以依据这个key决定相对性的属性执行什么操作。
let book = {title:"javascript",authors:['Nicholas','Matt'],edition:4,year:2017
};
let jsonText = JSON.stingify(book,(key,value)=>{switch(key){case 'authors':return value.join(',');case 'year':return 5000;case 'edition':return undefined;default:return value;}
})
- 注意:过滤器会使用到要序列化的对象的所有属性上。
第三个:字符串缩进
设置后会依据这个值进行换行空格,最大值10,超过自动默认10;
可以设置其他字符串形式,会以这个字符串作为缩进符添加到缩进的位置上。
② toJSON()方法
可以在JSON.stringify()上自定义JSON序列化,方式就是在要序列化的对象上添加toJSON方法,序列化时会依据这个方法返回需要的JSON表示。
let book = {title:"javascript",authors:['Nicholas','Matt'],edition:4,year:2017// 添加toJSONtoJSON: function(){return this.title;}
};
let jsonText = JSON.stingify(book);
-
上述序列化将book序列化为字符串,toJSON会返回相应序列化后的值。
-
注意点:箭头函数不能用来定义toJSON函数,因为其this为全局的。
Ⅲ)parse()函数/解析选项
① 参数
**第一个:**要解析的JSON数据结构。
第二个:和上述类似,属性名和属性值的函数,叫做还原函数。这个函数会针对每个键/值对调用一次,其中undefined默认跳过,不识别。
第24章-网络请求与远程资源
一)Ajax
- 优点:这个对象发送请求额外数据不会使得页面整体发生刷新事件;
Ⅰ)使用XHR
使用方法和步骤
① open()方法
参数:
- 第一个参数:请求类型,get,post等;
- 第二个参数:请求的URL;
- 第三个参数:请求是否是异步的布尔值,true表示为异步的;
let xhr = new XMLHttpRequest();
xhr.open('get','example.php',false);
注意点:这里的open只是为发送请求准备好,而不是正真的发送请求;并且这个请求的URL只能时同源的地址;
② send()方法
参数:接受一个参数,作为请求体发送的数据,不与要请求体,传入null,兼容性;同步执行,js后续代码会等待服务器响应后在执行,受到相应后,XHR对象的属性会被数据填充上去;
xhr被填充的属性:
- responseText:最为响应体返回的文本;
- responseXML:视响应体返回是数据类型而定;
- status:相应的HTTP状态;
- statusText:相应的HTTP说明;
xhr.send(null);
if((xhr.status>=200&&xhr.status<300)||xhr.status == 304){alert(xhr.responseText)
}else{alert(xhr.status);
}
③ XHR的5个阶段
xhr对象有一个readyState属性,可以查看当前处在的请求/响应的那一步。
- 0:未初始化,尚未调用open()方法;
- 1:已打开(open),以调用open方法;
- 2:已发送(Sent),已调用send方法,尚未收到响应;
- 3:接受中,已收到部分响应;
- 4:完成(Complete),已收到所有响应,可以使用了;
机制:每次readyState从一个值发生改变时,都会触发readystatechange事件,可以借此机会检查readyState的值;一般只关心值为4时的情况,表示数据以准备就绪;
④ 异步请求的取消
xhr.abort()方法可以取消异步请求;调用后xhr对象会停止触发事件;
Ⅱ)HTTP头部
① 头部组成
参考其他资料
② 发送额外头部信息
方法:使用函数setRequestHeader()函数接受两个参数,头部字段的名称和值;
阶段:在open函数后,在send函数调用之前;
自定义头部一定要区别与浏览器正常发送的头部
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){...
}
xhr.open('get','URL',true);
xhr.setRequestHeader('MyHeader','MyValue');
xhr.send(null);
③ 获取头部方法
getResponseHeader():获取响应头部信息,传入要获取头部的名称就行;
getAllResponseHeader():获取所用的响应头部信息;
Ⅲ)请求方式
① GET请求
作用:用于向服务器查询某些信息;
必要时,可以在URL后面添加上查询字符串,这些字符串要正确的编码;然后传递给open函数;
编码规则:所有的名/值对之间使用&分隔,其中URL与参数之间使用?好隔开;
xhr.open('get','example.php?name1=value1&name2=value2',true)
addURLParam方法:使用函数让他自己编码正确的附加参数的URL,他能保证编码的正确性
!这个函数是自定义的
let url = 'example.php';
url = addURLParam(url,'name1','Matt')
② POST请求
作用:用于向服务器发送应该保存的数据;每个post请求都要在请求体中添加需要提交的数据;post请求体可以携带是数据很多(send中添加需要提交的数据)。
注意点:post请求和提交表单还是不一样的,不过可以使用post 请求模拟提交表单。
注意点2:post请求比get请求占用空间更大,发送同样的数量的请求按理说post占用空间是get请求的两倍。
Ⅳ)XHR后续新增功能
① 超时
xhr添加了一个timeout属性,表示发送请求后等待多少秒,如果响应不成功就中断请求;超时后会自动执行函数ontimeout事件处理程序;
xhr.open('get','example.php',true);
xhr.timeout = 1000;
xhr.ontimeout = function(){alert('Request did not return in a second.')
}
Ⅴ)跨域与资源共享
使用XHR进行Ajax通信的一个主要限制就是跨域安全策略。默认情况下,XHR只能访问与发起请求的页面在同一个域内的资源。
① 关于资源请求CORS
1)定义了浏览器与服务器如何进行跨源通信;
实现方式:使用自定义的HTTP头部允许浏览器和服务器相互了解;对于简单的get、post请求没有自定义头部,但是他会自己附带一个额外的头部叫做Origin。
- Origin它包含发送请求的页面的源,以便服务器确定是否为其提供响应。
// 举例
Origin:http://www.nczonline.net
- 服务器:如果服务器确定对其响应,那么应该发送Accent-Control-Allow-Origin头部,包含相同的源;或者资源是公开的就是"*"。
Accent-Control-Allow-Origin:http://www.nczonline.net
Accent-Control-Allow-Origin:http:"*"
- 如果有这个头部,但是头部不匹配,则表明不会响应浏览器的头部。注意:无论是否响应这个信息都不包含cookie信息;
2)跨域的额外限制
跨域XHR对象被限制:
- 不能使用setRequestHeader()设置自定义头部;
- 不能发送和接受cookie
- getResponseHeader方法始终返回空字符串;
Ⅵ) 跨域的方法
① JSONP
具体:JSONP格式包含两部分:回调和数据。回调是页面接收到响应之后应该调用的函数,通常函数名动态指定;数据就是作为参数传给回调函数的JSON数据。
http://freegeoip.net/json/?callback=handleResponse
上例中,将回调函数名字动态指定为handleResponse();
实现过程:JSONP是通过动态创建script标签并为src属性指定跨域URL实现的。和img标签类似可以实现其他域资源加载;
function handleResponse(response){console.log(response.ip)
}
let script = doucument.createElement("script");
script.src = "http://freegeoip.net/json/?callback=handleResponse";
doucument.body.insertBefore(script,doucument.body.firstChild);
缺点:
- 跨域,未知域的安全性;
- 不好确定JSONP的请求是否失败;
二)Fetch API
fetch 能够实现xhr对象的功能,并且能偶在web工作线程中等现带web工具中使用。XMLHttpRequest对象可以选择异步,但是fetch api必须是异步。
Ⅰ)基本用法
fetch方法是暴露在全局作用域中的;
① 分派请求
fetch()有一个必须分派的参数input,一般是要获取资源的URL,这个方法返回一个起源对象;
let r = fetch('/bar');
console.log(r); // Promise<pending>
当请求资源完成时,期约对象会解决为一个Response对象。这个对象中包含了要获取的资源对象,利用Response对象的属性和方法可以获得需要使用的资源的一些内容。
fetch('bar.text').then((response)=>{console.log(response);
})
// Response{ type:"basic",url:...}
② 读取响应
纯文本读取内容,这里要用到text()方法;
fetch('bar.text').then(response)=>{response.text().then((data)=>{console.log(data);})
}
// bar.text 的内容
内容的结构通常是打平的
③ 处理状态码和请求失败
Fetch支持通过Response的status和statusText属性检查响应状态。
fetch('bar').then((response)=>{console.log(response.status)
})
// 200
注意点:只要服务器返回了响应,fetch期约都会解决。
fetch失败的情况
- 由于服务器没有响应导致浏览器超时,这样真正的fetch请求会失败。
- 违反CORS、无网络连接、HTTP错配等。
④ 自定义选项
fetch只是用URL时,会发送get请求,进一步配置可以确定其他方式,这就需要配置参数init对象。键值对形式。
page 725
Ⅱ)常见的fetch请求模式
① 发送JSON数据
let payload = JSON.stringiby({foo:'bar'
});
let jsonHeaders = new Headers({'Content-Type':"application/json"
});
// fetch('URL',init对象(以键值对))
fetch('/send-me-json',{method:"POST", // 发送请求体式必须使用一种HTTP方法body:payload,headers:jsonHeaders
})
② 跨域资源请求
从不同源请求资源,影响要包含CORS才能够保证浏览器收到响应。
fetch('//cross-origin.com');
// 报错,跨域
如果不要访问,可以发送no-cors请求,此时影响的type属性值为opaque,无法读取响应内容,适用于探求以备后续使用。
Ⅲ)Headers对象
Header对象是所有外发请求和入站影响头部的容器。分别使用方法Resquest.prototype.headers和Response。prototype.header访问,这两个属性都是可修改的属性。使用new Headers()方法可以创建一个新的实例。
- Headers和Map特别相似,区别:Headers可以传递键值对形式的对象初始化处理,但是Map不可以,他使用键值对的数组初始化。
Ⅳ)Request对象
作用:获取资源请求的接口
① 创建Request对象
使用Requst对象创建,对象中需要传递一个值input参数,一般是URL
let r = new Request('https://foo.com');
console.log(r);
// Request{...}
参数:
- 第一个,URL;
- 第二个:一个init对象,这个对象和前面介绍的fetch的init对象一样,在没有init对象中涉及的值则会使用默认值。
② 克隆对象
将Request实例作为input参数传递给Request构造函数,则会得到给请求的一个副本
let r1 = new Request('https://foo.com');
let r2 = new Request(r1)
console.log(r2.url); // https://foo.com
如果再次传入init对象,则init对象中的值会覆盖掉对象中同名的值;
不能创建副本的情况:Request的内容被使用,常见的是使用text()方法后,bodyUsed属性值true;
③ 使用fetch
在调用fetch时,可以传入已经创建好的request请求实例而不是URL,相同的会被覆盖;
let r = new Request('http://foo.com');
fetch(r); // 相网站发送get请求
fetch(r,{method:"POST"}) // 向网站发送POST请求
如果想要克隆请求则必须在发送POST请求前克隆,发送后,会被标记为已使用,不可克隆;
Ⅴ)Response对象
作用:Response对象是获取资源响应的接口;
① 创建Response对象
可以使用构造函数初始化Response对象且不需要参数,此时相应实例的参数都为默认值,因为她并不代表实际的HTTP响应。
构造:
- 参数:接受一个可选的body参数,还可以接受一个init对象,键值对形式传递;
克隆同Requests对象
这篇关于前端开发人必读红宝书重要章节知识点提炼+笔记+面试回答点的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!