一.Generator
Generator是一种异步编程解决方案。
next方法的参数
yield
表达式本身没有返回值,或者说总是返回undefined
。next
方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
由于Generator函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一个可以暂时执行的函数。yield表达式就是暂停标志。
function* foo(x) {
var y = 2 * (yield (x + 1))
var z = yield (y / 3)
return (x + y + z)
}
var a = foo(5)
a.next() // { value: 6, done: false }
a.next() // { value: NaN, done: false }
a.next() // { value: NaN, done: true}
var b = foo(5)
b.next() // { value: 6, done: false }
b.next(12) // {value: 8, done: false }
b.next(13) // {value: 42, done: false }
复制代码
yield 后面的值作为返回值
上面代码中,a在第二次运行next方法的时候不带参数,导致y的值等于 2 * undefined (即 NaN),除以3以后还是NaN,因此返回对象的value属性也等于NaN。第三次运行Next方法的时候不带参数,所以z等于undefined,返回对象的value属性等于5 + NaN + undefined,即NaN.
如果向next方法提供参数,返回结果就完全不一样了。b在第一次调用next方法时,返回x+1的值6;第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y/3的值8;第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42.
注意:由于next方法的参数表示上一个yield表达式的返回值,所以**在第一次使用next方法时,传递参数是无效的。**V8引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。
回调函数本身并没有问题,它的问题出现在多个回调函数嵌套。
Promise
的写法只是回调函数的改进,使用then
方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。
Promise
的最大问题是代码冗余,原来的任务被 Promise
包装了一下,不管什么操作,一眼看去都是一堆then
,原来的语义变得很不清楚。
co模块
var gen = function* () {
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
复制代码
co 模块可以让你不用编写 Generator 函数的执行器。
var co = require('co');
co(gen);
复制代码
上面代码中,Generator 函数只要传入co函数,就会自动执行。
co函数返回一个Promise对象,因此可以用then方法添加回调函数。
co(gen).then(function (){
console.log('Generator 函数执行完成');
});
复制代码
co 模块其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成一个模块。使用 co 的前提条件是,Generator 函数的yield命令后面,只能是 Thunk 函数或 Promise 对象。如果数组或对象的成员,全部都是 Promise 对象,也可以使用 co
co 模块的源码
co 就是上面那个自动执行器的扩展,它的源码只有几十行,非常简单。
首先,co 函数接受 Generator 函数作为参数,返回一个 Promise 对象。
function co(gen) {
var ctx = this;
return new Promise(function(resolve, reject) {
});
}
复制代码
第一行,检查当前是否为 Generator 函数的最后一步,如果是就返回。
第二行,确保每一步的返回值,是 Promise 对象。
第三行,使用then方法,为返回值加上回调函数,然后通过onFulfilled函数再次调用next函数。
第四行,在参数不符合要求的情况下(参数非 Thunk 函数和 Promise 对象),将 Promise 对象的状态改为rejected,从而终止执行。
处理并发的异步操作
二、对象赋值
let obj1 = {a:1,b:{c:2},d:3}
let obj2 = Object.assign({},obj1)
复制代码
说明:Object.assign()方法只能复制第一层的对象值。但是obj2里b对象的c的值得时候,obj1里b的对象里的c值也发生了变化,说明Object.assign()这个方法不是深层的复制对象,只是让对象里第一层的数据没有了关联,但是对象内的对象则跟被对象复制的对象有着关联性。
目前最常用的进行深拷贝的方法
let obj1 = {a:1,b:{c:2},d:3}
let obj2 = JSON.parse(JSON.stringify(obj1))
复制代码
我们先把obj1转成字符串类型,这样他就失去了对象的属性和特性,然后我们再把他转成一个对象类型,这样我们新生成的对象是通过字符串转换过来的,这样居生成了一个新对象。 注:此方法对对象的value为function的无效。
按值传递和按引用传递
function test(obj) {
obj.age= '28';
console.log('inner',obj) // inner {name: "shuiguoge", age: "28"}
}
let obj={
name: 'shuiguoge'
}
test(obj)
console.log('outer',obj) // outer {name: "shuiguoge", age: "28"}
复制代码
我们发现两个obj指向同一个内存的副本
js中常见的按引用传递: object array
按值传递:string number boolean
三、setTimeout
setTimeout(function () {
setTimeout(function () {
console.log(1)
}, 100);
console.log(2)
setTimeout(function () {
console.log(3)
}, 0);
}, 0);
setTimeout(function () {
console.log(4)
}, 100);
结果: 2 3 4 1
复制代码
浏览器是多线程的但是js是单线程的! 解析:事件循环event loop。主线程(js线程)只会做一件事,就是从消息队列里面取消息、执行消息,再取消息、再执行。消息队列为空时,就会等待直到消息队列变成非空。只有当前的消息执行结束,才会去取下一个消息。这种机制就叫做事件循环机制Event Loop,取一个消息并执行的过程叫做一次循环。
链式setTimeout:在前一个定时器执行完之前,不会向队列插入新的定时器。
四、indexOf
此方法只能用于String
和Array
在数组中的用法 indexOf()方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1. 参数 searchElement 要查找的元素 fromIndex 可选开始查找的位置。如果该索引值大于或等于数组长度,意味着不会在数组里查找,返回-1.如果参数中提供的索引值是一个负值,则将其作为数组末尾的一个抵消,即-1表示从最后一个元素开始查找,-2表示从倒数第二个元素开始查找,以此类推。注意:如果参数中提供的索引值是一个负值,并不改变其查找顺序,查找顺序仍然是从前向后查询数组。如果抵消后的索引值仍小于0,则整个数组都将会被查询。其默认值为0.
var array = [2, 5, 9]
array.indexOf(2) // 0
array.indexOf(7) // -1
array.indexOf(9) // 2
复制代码
找出指定元素出现的所有位置
var indices = []
var array = ['a','b','a','c','a','d']
var element = 'a'
var idx = array.indexOf(element)
while (idx != -1) {
indices.push(idx)
idx = array.indexOf(element, idx + 1)
}
console.log(indices)
// [0,2,4]
复制代码
在字符串中的用法
indexOf()方法返回调用String
对象中第一次出现的指定值的索引,开始在fromIndex进行搜索。如果未找到该值,则返回-1. indexOf
方法区分大小写。 参数 searchValue
一个字符串表示被查找的值。 fromIndex
可选 表示调用该方法的字符串中开始查找的位置。可以是任意整数。默认值为 0。如果 fromIndex < 0 则查找整个字符串(如同传进了 0)。如果 fromIndex >= str.length,则该方法返回 -1。 返回值 指定值得第一次出现的索引;如果没有找到返回-1
'Blue Whale'.indexOf('Blue') !== -1 // true
复制代码
使用 indexOf 统计一个字符串中某个字母出现的次数
var str = 'To be, or not to be. that is the question'
var count = 0
var pos = str.indexOf('e')
while (pos !== -1) {
count ++
pos = str.indexOf('e',pos+1)
}
console.log(count) // 4
复制代码
五.lastIndexOf()
lastIndexOf()
方法返回指定值在调用该方法的字符串中最后出现的位置,如果没找到则返回-1.从该字符串的后面向前查找,从fromIndex
处开始。 demo:
let url = 'https://www.baidu.com/search'
let index = url.lastIndexOf('/')
let subStr = url.substring(0,index)
console.log(subStr) // https://www.baidu.com
复制代码
-
substr()
方法返回一个字符串中从指定位置开始到指定字符数的字符。str.substr(start[, length]) 复制代码
参数:
start:开始提取字符串的位置。首字符的索引为0,最后一个字符的索引为 字符串的长度减去1。如果为
负值
,则被看作 strLength + start,其中 strLength 为字符串的长度(例如,如果 start 为 -3,则被看作 strLength + (-3))。length:可选。提取的字符数。
var str = 'idesefesefsggfsehnjk' var strSub = str.substr(3,3) console.log(strSub) // sef 复制代码
-
substring()
方法返回一个字符串在开始索引到结束索引之间的一个子集,或从开始索引直到字符串的末尾的一个子集。str.substring(indexStart[, indexEnd]) 复制代码
参数:
indexStart: 需要截取的第一个字符的索引,该字符作为返回的字符串的首字母。
indexEnd: 可选。一个0到字符串长度之间的整数,以该数字为索引的字符不包含在截取的字符串内。
返回值:包含给定字符串的指定部分的新字符串。
var str = 'idesefesefsggfsehnjk' var strSub = str.substring(0,3) console.log(strSub) // ide 复制代码
区别:
substr
方法返回的是从一个指定位置开始的指定长度的字符串。而substring
方法返回才从指定开始位置到指定结束位置的子字符串。
六. toString()
总结: toString()
的基础用法就是返回相应的字符串
toString()
方法返回指定Number
对象的字符串表示形式。
numObj.toString([radix])
复制代码
参数
redix指定要用于数字到字符串的转换的基数(从2到36)。如果未指定radix
参数,则默认为10.
异常信息
如果toString()
的radix
参数不再2到36之间,将会抛出一个RangeError
进行数字到字符串的转换时,建议用小括号将要转换的目标括起来,防止出错。
var a = 256
a.toString() // '256'
(178).toString() // '178' 要用小括号,防止出错
复制代码
toString()返回一个字符串,表示指定的数组及其元素。
var array1 = [1,2,'a','l']
console.log(array1.toString()) // 1,2,a,l
复制代码
Array
对象覆盖了Object的toString方法。对于数组对象,toString方法连接数组并返回一个字符串,其中包含用逗号分隔的每个数组元素。
当一个数组被作为文本值或者进行字符串连接操作时,将会自动调用其toString
方法。
toString() 方法返回一个表示该对象的字符串。
描述: 每个对象都有一个toString()
方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。默认情况下,toString()
方法被每个Object
对象继承。如果此方法在自定义对象中未被覆盖,toString()
返回“[object type]”,其中type
是对象的类型。以下代码说明了这一点:
var o = new Object()
o.toString() // [object object]
复制代码
使用toString()检测对象类型
可以通过toString()
来获取每个对象的类型。为了每个对象都能通过Object.prototype.toString()来检测,需要以function.prototype.call()
或者function.prototype.call()
的形式来调用,传递要检查的对象作为第一个参数,称为thisArg
。
let toString = Object.prototype.toString
toString.call(new Date) // [object Date]
toString.call(new String) // [object String]
toString.call(Math) // [object Math]
toString.call(undefined) // [object Undefined]
toString.call(null) // [object Null]
复制代码
4.Function.prototype.toString()
toString()
方法返回一个表示当前函数源代码的字符串
function sum(a,b) {
return a + b
}
console.log(sum.toString())
// "function sum(a,b) {
// return a + b
//}"
复制代码
Function
对象覆盖了从Object
继承来的toString
方法。对于用户定义的Function
对象,toString
方法返回一个字符串,其中包含用于定义函数的源文本段。
在Function
需要转换为字符串时,通常会自动调用函数的toString
方法。
toString()
方法返回一个字符串,表示该Date对象。
Date
对象覆盖了Object
对象的toString
方法;它不是继承自Object.prototype.toString()
。对于Date
对象,toString()
方法返回一个表示该对象的字符串。
该toString
方法总是返回一个美式英语日期格式的字符串。
当一个日期对象被用来作为文本值或用来进行字符串连接时,toString方法会自动调用。
var x = new Date()
myVar = x.toString()
// "Sun Mar 31 2018 17:15:41 GMT+0800 (中国标准时间)"
复制代码
toString()
方法返回指定对象的字符串形式
String
对象覆盖了Object
对象的toString
方法;没有继承Object.toString()
。对于String
对象,toString
方法返回该对象的字符串形式,和String.prototype.valueOf()
var x = new String('Hello World')
console.log(x) // String {"hello world"}
x.toString() // hello world
复制代码
七、valueOf()
隐式类型转换
const a = {
num: 0,
valueOf: function() {
return this.num += 1
}
};
const equality = (a==1 && a==2 && a==3);
console.log(equality)
复制代码
参考: JavaScript:(a==1 && a==2 && a==3)能输出ture么?
总结: valueOf()
方法返回指定对象的原始值。
valueOf()
方法返回一个String
对象的原始值。
var x = new String('Hello World')
x.valueOf() // hello world
复制代码
valueOf()
方法返回指定对象的原始值
JavaScript调用valueOf
方法将对象转换为原始值。你很少需要自己调用valueOf()
方法;当遇到要预期的原始值的对象时,JavaScript会自动调用它。
八.连等赋值
核心点在运算符的优先级
var a = {n: 1}
var b = a
a.x = a = {n: 2}
console.log(a.x) // undefined
console.log(b.x) // {n: 2}
复制代码
-
运算符优先级
.
高于=
-
a = b = c = d = 7 的运算过程 d被赋值为7,c被赋值为7,b被赋值为7,a被赋值为7,整个表达式的返回值为7
-
对于 a.x = a = {n: 2}
第一步执行
a.x
,为a对象创建x属性,此时x属性的值是undefined
,此时a对象和b对象的指针地址是相同的a.x === b.x // true
,所以b.x的值是{n:2}
,a.x的返回值为undefined,第二部执行 a={n:2},a对象被指向了新地址,且返回值为{n: 2}
-
最后的a.x是在被指向了新地址后的a的属性,不存在x属性,因未被赋值,为undefined
-
声明a对象的x属性,用于赋值,此时b指向a,同时拥有未赋值的属性x
-
对象a对象赋值,此时变量名a改变指向到对象 {n:2}
-
对步骤1中x属性赋值
拆分
a.x = a = {n: 2}
a.x = {n: 2}
b.x = {n: 2}
a = {n: 2} // a被再次重新赋值,a中不再含有a.x
// 将 a.x = a = {n: 2}换成 a = a.x = {n: 2} 效果相同
复制代码
解析器在接受到a = a.x = {n: 2}
后的操作如下:
- 解析器找到 a 和 a.x 指针。如果已有指针,则不改变它,如果没有指针,表示变量还未被声明,那就创建它,指向null,a是有指针的,指向 {n: 1} ;a.x是没有指针的,所以创建它,指向null
- 然后把上面找到的指针,【都指向最右侧赋的值】,即 {n: 2}
- 运算符优先级问题,先给 a.x 赋值,再给a 赋值。引用在表达式求值前确定。
连续赋值考虑的点: 1. 指针的变化。2.运算符的优先级.
高于=
。3.按值传递与按引用传递
九、单点登录
基本思路:
b.com
在发现未登录时跳转至a.com
进行登录a.com
在登录完成后将登录authcookie
与用户信息记录到服务器(session_id或者redis)- 同时
a.com
创建一个令牌token
关联上一步的authcookie
,并带回b.com
的backUrl中 b.com
拿到a.com
发回来的令牌去调用a.com
的接口查询是否令牌有效并且可以查到用于信息,有的话则拿回数据并做后续操作
注: 单点登录与前后端分离没有关系
十、typeof运算符
typeof undefined // undefined
typeof 'abc' // string
typeof 123 // number
typeof true // boolean
typeof {} // object
typeof [] // object
typeof console.log // function
typeof null // object
typeof总结
1. 只能区分 值类型 的4中详细类型string、number、boolean、undefined
2. 不能详细区分 引用类型,只能区分 function
3. typeof null 也是引用类型 object
复制代码
es6语法
var s = Symbol()
typeof s // "symbol"
var n = Symbol()
s === n // false
var m = Symbol('intel')
symbol作用
1.生成的值全部是不相等的
2.防止对象key相同时值被覆盖的情况
用法
var q = Symbol('intel')
m === q // false
var obj = {name: '张三'}
var sName = Symbol('name')
obj.sName = "李四"
console.log(obj) // {name: "张三", sName: "李四"} 理论上张三的李四的key值都是name,但因为symbol,避免了相同时值被覆盖的情况
复制代码
十一、变量计算 - 强制类型转换、变量提升
字符串拼接
var a = 100 + 10 // 110
var b = 100 + '10' // 10010
var c = 100 - '10' // 90
复制代码
变量提升
console.log(b) // b(){}
var b = 1
function b() {
}
console.log(b) // 1
复制代码
十二、== 运算符(慎用==)
100 == '100' // true 尝试让左右相等 '100' == '100'
0 == '' // true false == false
null == undefiend // true false == false
// 何时使用 == 何时使用 ===
只有下面这种情况适合用 ==,别的情况全用 === 判断一个对象的属性是不是存在(null或undefined)
if (obj.a == null) {
// 这里相当于 obj.a === null || obj.a === undefined 的简写形式,是jq源码推荐的写法
}
=== 不会进行强制类型转换
复制代码
十三、if语句
var a = true
if (a) {
// ...
}
var b = 100
if (b) { // b被转化为true
// ...
}
var c = ''
if (c) { // c被转化为 false
// ...
}
if中false的情况
1. 0
2. NaN
3. ''
4. null
5. false
6. undefined
复制代码
十四、逻辑运算符
console.log(10 && 0) // 0
console.log('' || 'abc') // 'abc' 相当于 fasle || 'abc'
console.log(!window.abc) // true 相当于 !undefined
// 判断一个变量会被转化为布尔类型的true还是false
var a = 200
console.log(!!a) // true
复制代码
十五、js中的内置函数
Object // ƒ Object() { [native code] }
Array // ƒ Array() { [native code] }
Boolean
Number
String
Function
Date
RegExp
Error
复制代码
十六、js按存储方式区分变量类型
// 值类型
var a = 10
var b = a
a = 16
console.log(b) // 10
// 引用类型
var obj1 = {x: 100}
var obj2 = obj1
obj1.x = 200
console.log(obj2.x) // 200
var arr = [1,2,3]
arr.age = 21 // 数组也可以通过这种方式复制
console.log(arr) // [1,2,3,arg:21]
值类型是值是被复制后放在各自的内存空间中,值的改变相互之间不会影响。
引用类型,则是指向了同一个内存对象,指针相互传递。目的:通过这种方式可以减少内存的开销.js 、c++ 等高级原因都是这么设计的。
复制代码
十七、如何理解json
1.JSON只不过是一个JS对象而已,内置在js的语法中。有两个常用API:stringfy、parse。和Math一样。
2.JSON也是一种数据格式
JSON.stringfy({a: 10,b: 20})
JSON.parse('{"a":10,"b":20}')
JSON、Math是内置对象
JSON {parse: ƒ, stringify: ƒ, Symbol(Symbol.toStringTag): "JSON"}
复制代码
十八、原型和原型链
构造函数
// 构造函数定义,大写开头
function Foo(name, age) {
this.name = name
this.age = age
this.class = '一年级1班'
// return this // 默认有这一行
}
var f = new Foo('张三', 20)
var n = new Foo('李四', 19)
复制代码
构造函数--扩展
1. var a = {} 其实是 var a = new Object()的语法糖
2. var a = [] 其实是 var a = new Array()的语法糖
3. function Foo(){ } 其实是 var Foo = new Function()
4. 使用instanceof判断一个函数是否是一个变量的构造函数(可用于判断一个变量是否为“数组”,使用:arr instanceof Array)
复制代码
原型规则和示例
5条原型规则
原型规则是原型链的基础
- 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展的属性(除了“null”)
var obj = {}; obj.a = 100;
var arr = []; arr.a = 100
function fn () { }
fn.a = 100
复制代码
- 所有的引用类型(数组、对象、函数),都有一个 proto(隐式原型) 属性,属性值是一个普通的对象。
console.log(obj,__proto__)
console.log(arr.__proto__)
console.log(fn.__proto__)
复制代码
- 所有的函数,都有一个 prototype(显式原型) 属性,属性值也是一个普通的对象
console.log(fn.__prototype)
复制代码
- 所有的引用类型(数组、对象、函数), proto 属性值指向他的构造函数的
prototype
属性值
console.log(obj.__proto__ === Object.prototype) // true
理解: var obj = {}实际上是 var obj = new Object()
因此 obj.__proto__ === Object.prototype
复制代码
- 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的 proto (即它的构造函数的prototype)中寻找
// 构造函数
function Foo(name) {
this.name = name
}
Foo.prototype.alertName = function () {
alert(this.name)
}
// 创建示例
var f = new Foo('张三')
f.printName = function () {
console.log(this.name)
}
f.printName()
f.alertName()
// 遍历
var item
for (item in f) {
// hasOwnPrototype 只遍历本身的属性,而不编译原型链上的属性
// 高级浏览器已经在for in中屏蔽了来自原型的属性
// 但是还是建议大家加上这个判断,保证程序的健壮性
if (f.hasOwnPrototype(item) ) {
console.log(item)
}
}
复制代码
原型链
f.toString() // 要去f.__proto__.__protoo__中查找
复制代码
instanceof 用于判断引用类型
属于哪个构造函数
的方法
f instanceof Foo 的判断逻辑是:f 的__proto__一层一层往上,能否对应到 Foo.prototype
判断 f instanceof Object
复制代码
如何准确判断一个变量是数组类型
框架源码中如何使用原型链
复制代码
1. 一个新对象被创建。它继承自foo.prototype原型对象(非常重要)
2. 构造函数 foo 被执行。执行的时候,相应的传参会被传入,同时上下文(this)会被指定为这个新实例。new foo等同于 new foo(),只能用在不传递任何参数的情况下
3. 如果构造函数返回了一个“对象”,那么这个对象会取代整个new出来的结果。如果构造函数没有返回对象,那么new出来的结果为步骤1创建的对象。
var new = function(func) {
var o = Object.create(func.prototype)
var k = func.call(o)
if (typeof k === 'object') {
return k
} else {
return o
}
}
复制代码
面向对象
一个原型链继承的例子(本质就是原型链)
function Car (color) {
this.color = color
}
Car.prototype.sail = functin() {
console.log(this.color)
}
var s = new Car('RED')
function BMW(color) {
Car.call(this.color)
}
var __proto = Object.create(Car.prototype)
__proto.constructor = BMW
BMW.prototype = __proto
BMW.prototype.price = function () {
}
复制代码
// 借助构造函数实现继承
function Parent1 () {
this.name = 'parent1'
}
Parent1.prototype.say = function () {} // 此方法未被Child1继承
// 下面的方法只是继承了父类构造函数中的属性
function Child1 () {
// call或apply 在此处执行,改变了this指向,那么父类在执行的时候,属性都会挂载到child的类的实例上去
Parent1.call(this)
this.type = 'child1'
}
console.log(new Child1)
// 借助原型链实现继承
function Parent2 () {
this.name = 'Parent2'
}
function Child2 () {
this.type = 'child2'
}
// 任何函数都有prototype属性,访问到它的原型对象
Child2.prototype = new Parent2()
console.log(new Child2())
公用了同一个了父类的实例
// 组合方式
function Parent3 () {
this.name = 'parents'
this.play = [1,2,3]
}
function Child3 () {
Parent3.call(this) // 第一次执行
this.type = 'child3'
}
Child3.prototype = new Parent3() // 第二次执行
var s3 = new Child3()
var s4 = new Child3()
// 组合继承的优化
function Parent3 () {
this.name = 'parents'
this.play = [1,2,3]
}
function Child3 () {
Parent3.call(this)
this.type = 'child3'
}
Child3.prototype = Parent3.prototype
var s5 = new Child3()
var s6 = new Child3()
cosole.log(s5,s6)
console.log(s5 instanceof Child3, s5 instanceof Parent3)
用constructor来判断对象是不是原型链的实例
// 组合继承优化2(完美写法)
function Parent5 () {
this.name = 'parents'
this.play = [1,2,3]
}
function Child5 () {
Parent5.call(this)
this.type = 'child3'
}
Child5.prototype = Object.create(Parent5.prototype) // __proto__
Child5.prototype.constructor = Child5
var s7 = new Child5()
var s6 = new Child3()
console.log(s7 instanceof Child5,s7 instanceof Parent5)
console.log(s7.constructor)
复制代码
十九、作用域和闭包-执行上下文
1.对变量提升的理解
2.说明this几种不同的使用场景
3.创建10个<a>
标签,点击的时候弹出来对应的序号
4.如何理解作用域
5.实际开发中闭包的应用
执行上下文 this 作用域 作用域链 闭包
范围:一段<script>
或者一个函数
全局: 变量定义、函数声明
函数: 变量定义、函数声明、this、arguments
注意: "函数声明"和"函数表达式"的区别
"函数声明"是通过function
定义的函数 "函数表达式"是 var fn = function () {}
<. undefined
是整个程序的输出,与代码无关
console.log(a) // undefined
var a = 100
fn('zhangsan') // 'zhangsan' 20
function fn(name) { // fn会拿到全局的最上边
age = 20
console.log(name.age)
var age
}
fu1() // 这种写法会报错,因为在执行到下面的时候function才会被赋值
var fn1 = function () {
age = 20
console.log(name.age)
var age
}
复制代码
this要在执行时才能确认值,定义时无法确认
var a = {
name: 'A',
fn: function () {
console.log(this.name)
}
}
a.fn() // this === a
a.fn.call({name: 'B'}) // this === {name: 'B'}
var fn1 = a.fn
fn1() // this === window
// 构造函数
function Foo(name) {
this.name = name
}
var f = new Foo('张三')
// 作为一个对象的属性
var obj = {
name: 'A',
printName: function () {
console.log(this.name)
}
}
obj.printName()
// call apply bing
function fn1(name, age) {
console.log('name', name)
console.log(this)
}
fn1.call({x: 100}, '张三', 20)
fn1.apply({x: 200}, ['张三', 20])
var fn2 = function (name, age) {
console.log('name', name)
console.log(this)
}.bind({y: 300})
fn2('张三', 20) // this====> {y: 300}
// this
var a = 'a'
var obj = {
a: 'b',
prop: {
a: 'c',
getA: function () {
return this.a
}
}
}
console.log(obj.prop.getA()) // 'c'
var getA = obj.prop.getA
console.log(getA()) // 'a'
复制代码
es5
// 无块级作用域
if (true) {
var name = '张三'
}
console.log(name)
// 函数和全局作用域
var a = 100
function fn() {
var a = 200
console.log('fn', a)
}
console.log('global', a)
fn()
// 作用域链
var a = 100
function F1() {
var b = 200
function F2() {
var c = 300
console.log(a) // a 是自由变量
console.log(b) // b 是自由变量
console.log(c)
}
F2()
}
F1()
复制代码
es6块级作用域
for(var i = 0; i < 10; i++) {
}
console.log(i) // 10
for (let j = 0; j < 10; j++) {
}
console.log(j) // 报错 无法取到j, j is not defined
if (true) {
let name = '李四'
}
console.log(name) // 报错 name is not defined 块级作用域
复制代码
闭包
function F1() {
var a = 100
return function () {
console.log(a)
}
}
// new 出来的是返回值
var f1 = new F1() // function () { console.log(a) }
var a = 200
f1() // 100
一个函数的作用域是它定义时的作用域,而不是执行时的作用域
复制代码
闭包的使用场景
1、函数作为返回值
2、函数作为参数传递
function F1() {
var a = 100
return function () {
console.log(a) // a是自由变量,向父作用域去寻找--- 函数**定义**时的父作用域
}
}
var f1 = F1()
function F2(fn) {
var a = 300
fn()
}
F2(f1) // 100
复制代码
在运行js代码时,它的运行环境是非常重要的,运行环境可能是如下几种中的一种:全局代码---首次执行代码的默认环境;函数代码---每当执行流程进入函数体时。我们将执行上下文定义当前代码的执行环境或作用域。
词法作用域
function createCounter () {
let counter = 0
const myFunction = function () {
counter = counter + 1
return counter
}
return myFunction
}
const increment = createCounter()
const c1 = increment()
const c2 = increment()
const c3 = increment()
console.log('example increment', c1, c2, c3 ) // 1 2 3
复制代码
当声明一个函数式,它包含了一个函数定义和一个闭包。闭包是函数创建时声明的变量的集合。
任何函数是否都有闭包,包括在全局范围内创建的函数?答案是肯等的。在全局范围中创建的函数也会创建一个闭包。但由于这些函数式在全局范围内创建的,因此他可以访问全局范围内的所有变量,就无所谓闭包不闭包了。
当一个函数返回另一个函数时,才会真正涉及闭包。返回的函数可以访问仅存在于其闭包中的变量。
不经意的闭包
let c = 4
const addX = x => n => n + x
const addTree = addX(3)
let d = addTree(c)
console.log(d) // 7
等效代码如下:
let c = 4
const addX = function (x) {
return function (n) {
return n + x
}
}
const addTree = addX(3)
let d = addTree(c)
console.log(d) // 7
复制代码
可以用背包类比的方式记住闭包。当创建和传递一个函数或将其从另一个函数返回时,这个函数就带有一个背包,背包中包了所有在创建时声明的变量。在高阶函数、react中都非常常用
二十、异步和单线程
1.同步和异步的区别是什么?分别举一个同步和异步的例子
2.一个关于setTimeout
的笔试题
3.前端使用异步的场景有哪些
知识点
1、什么是异步(对比同步)
2、前端使用异步的场景
3、异步和单线程
// 异步
console.log(100)
setTimeout(function() {
console.log(200)
},1000)
console.log(300)
// 同步
console.log(100)
alert(200)
console.log(300)
复制代码
同步会阻塞程序的执行,异步会无条件、无阻塞的继续往下执行
何时需要异步
1.在可能发生等待的情况
2.等待过程中不能像alert
一样阻塞程序运行
3.因此,所有的"等待的情况"都需要异步
前端使用异步的场景 1.定时任务:setTimeout , setInterval
2.网络请求:ajax请求,动态加载
3.事件绑定
console.log('start')
var img = document.createElement('img')
img.onload = function () {
console.log('loaded')
}
img.src = '/xxx.png'
console.log('end')
复制代码
什么是单线程?
同一时间只能执行一件事情
同步和异步的区别是什么?
1.同步会阻塞代码执行,而异步不会
2.alert是同步,setTimeout是异步
console.log(1)
setTimeout(function () {
console.log(2)
},0)
console.log(3)
setTimeout(function () {
console.log(4)
},1000)
console.log(5)
复制代码
在异步队列中,先进的会先出
console.log(1)
setTimeout(function () {
console.log(2)
},0)
console.log(3)
setTimeout(function () {
console.log(4)
},0)
console.log(5)
// 1 3 5 2 4
复制代码
console.log(1)
setTimeout(function () {
console.log(2)
// 2执行完之后,再把这个放到异步队列中
setTimeout(function() {
console.log(6)
},0)
},0)
console.log(3)
setTimeout(function () {
console.log(4)
},0)
console.log(5)
复制代码
阅读underscore.js源码。阅读zepto.js源码。
二十一、Date(日期)和Math
-
获取 2017-06-10 格式的日期
-
获取随机数,要求是长度一致的字符串格式
-
写一个能遍历对象和数组的通用 forEach 函数
知识点: 日期、Math、数组API、对象API
// Date是个构造函数
Date.now() // 获取当前时间的毫秒数
var dt = new Date()
dt.getTime() // 获取毫秒数
dt.getFullYear() // 年
dt.getMonth() // 月(0-11)
dt.getDate() // 日(0-31)
dt.getHours() // 小时(0-23)
dt.getMinutes() // 分钟(0-59)
dt.getSeconds() // 秒(0-59)
复制代码
Math
Math.random // 获取随机数 (0-1)可用于清除缓存
复制代码
数组API
forEach 遍历所有元素
every 判断所有元素是否都符合条件
some 判断是否至少一个元素符合条件
sort 排序
map 对元素重新组装,生成新数组
filter 过滤符合条件的元素
var arr = [1,2,3]
arr.forEach(function (item, index) {
// 遍历数组中的所有元素
console.log( item, index )
})
var result = arr.every(function (item, index) {
// 用来判断所有的数组元素,都满足一个条件
if (item < 4) {
return true
}
})
console.log(result)
var result = arr.some(function(item, index) {
if (item < 2) {
return true
}
})
console.log(result)
猜测some的内部实现机制,只要有一个是true,就是true
var arr = [1,4,2,3,5]
var arr2 = arr.sort(function(a, b) {
// 从小到大排序
console.log('a',a,'b',b) // 从打印可看出内部真实的执行逻辑
return a - b
// 从大到小排序
// return b - a
})
console.log(arr2)
var arr = [1,2,3,4]
var arr2 = arr.map(function(item, index) {
// 将元素重新组装,并返回
return `<b>${item}</b>`
})
console.log(arr2) // 返回新的数组
var arr = [1,2,3]
var arr2 = arr.filter(function(item, index) {
// 通过某一个条件过滤数组
if (item >= 2) {
return true
}
})
console.log(arr2) // [2,3]
复制代码
对象API
var obj = {
x: 100,
y: 200,
z: 300
}
var key
for (key in obj) {
// hasOwnProperty
if (obj.hasOwnProperty(key)) {
console.log(key, obj[key])
}
}
复制代码
二十二、JSWebAPI
变量类型和计算
原型和原型链
闭包和作用域
异步和单线程
其他(如日期、Math、各种常用API)
内置函数: Object Array Boolean String
内置对象: Math JSON
ECMA 262标准:使用来规定一些基础语法的规则
JS-Web-API: W3C 标准
W3C标准中关于JS的规定有:
DOM操作:将网页中图片删除、增加DOM、添加动态效果、修改网页结构
BOM操作:获取浏览器特性、获取当前屏幕的尺寸宽高、获取当前地址栏地址
事件绑定:click、keyon、keyup、mouseout、moustenter
ajax请求(包括http协议)
存储
操作DOM的各种方法
document.getElementById(id) // 获取元素
复制代码
W3C标准没有规定任何JS基础相关的东西:只管定义用于浏览器中JS操作页面的API和全局变量
ECMA管 变量类型、原型、作用域和异步
JS内置的全局函数和对象有哪些? Object、Array、Boolean、String、Math、JSON、window、document、navigator.userAgent
常说的JS(浏览器执行的JS)包含两部分:
JS基础知识(ECMA262标准):语法、变量类型、计算等
JS-Web-API(W3C标准):DOM BOM ajax 存储,nodejs没有W3C标准,因为不是跑在浏览器端的
DOM操作:Document、Object、Model 文档对象模型
DOM是哪种基本的数据结构? DOM操作的常用API有哪些? DOM节点的attr和property的区别?
DOM本质
html是xml特殊类型
树(类比真实的树,有很多节点)
浏览器拿到html之后需要把html代码结构化成浏览器和js都可以识别的DOM
DOM可以理解为:浏览器把拿到的html代码,结构化一个浏览器能识别并且js可操作的一个模型而已。
获取DOM节点
var div1 = document.getElementById('div1') // 元素
var divList = document.getElementsByTagName('div') // 集合
console.log(divList.length)
console.log(divList[0])
var containerList = document.getElementsByClassName('container') // 集合
var pList = document.querySelectorAll('p') // 集合
// property
var pList = document.querySelectorAll('p')
var p = pList[0]
console.log(p.style.width) // 获取样式
p.style.width = '100px' // 修改样式
console.log(p.className) // 获取 class
p.className = 'p1 main' // 修改 class
// 获取 nodeName 和 nodeType
console.log(p.nodeName) // nodeName 是p的property
console.log(p.ndoeType)
// Attribute 是获取html上的属性,可以扩展无限的属性
var pList = document.querySelectorAll('p')
var p = pList[0]
p.getAttribute('data-name')
p.setAttribute('data-name', 'fruit') // 可以扩展无限的属性
p.getAttribute('style')
p.setAttribute('style', 'font-size: 30px;')
复制代码
DOM的根节点是document,DOM(document object model)
prop是纯JS属性,符合JS语法。而attr是JS通过getAttribute获取的html上的属性,即它归根结底是html属性,DOM对象上没有这个属性。
parentElement和parentNode有什么区别?这两个基本是一个意思。parentNode是W3C标准的,尽量使用这个。前端还在标准化推广的过程中,遇到这种模棱两可的问题很常见。
DOM结构操作
1.新增节点 2.获取父元素 3.获取子元素 4.删除节点
新增节点
var div1 = document.getElementById('div1')
// 添加新节点
var p1 = document.createElement('p')
p1.innerHTML = 'this is p1'
// 添加新创建的元素
div1.appendChild(p1)
// 移动已有节点
var p2 = document.getElenemtById('p2')
div1.appendChild(p2) // 相当于把之前位置的p2节点移动到了div1下面
获取父元素和子元素
var div1 = document.getElementById('div1')
var parent = div1.parentElement
显示子节点
var child = div1.childNodes
div1.removeChild(child[0].nodeType) // text 3
div1.removeChild(child[1].nodeType) // p 1
div1.removeChild(child[0].nodeName) // text #text
div1.removeChild(child[1].nodeName) // p1 p
删除子节点
div1removeChild(child[1])
复制代码
nodeType有哪几种类型
www.w3school.com.cn/jsref/prop_…
BOM操作
Browser Object Model
复制代码
如何检测浏览器的类型
拆解url的各部分
知识点
- navigator
- screen
- location
- history
navigator & screen
// navigator 如何检测浏览器的类型
var ua = navigator.userAgent
// Chrome
// "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"
// iphone
// "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1"
var isChrome = ua.indexOf('Chrome')
console.log(isChrome)
// screen
console.log(screen.width)
console.log(screen.height)
// location 拆解url
console.log(location.href)
console.log(location.protocol) // 'http:' 'https:'
console.log(locatin.host) // 域名
console.log(location.pathname) // 'editor/posts'
console.log(location.search)
console.log(location.hash)
// history
history.back()
history.forward()
复制代码
二十三、linux基础命令
ssh work@10.340.23.450 登录
mkdir test 创建test文件夹
ls 看文件夹
ll 看文件夹详细信息 包括权限
pwd 看当前目录
cd ../ 返回上级目录
rm -rf test 删除test文件夹
vi a.js 编辑文件
cp a.js a1.js 将a.js拷贝为a1.js
mkdir src
mv a1.js src/a1.js 将a1.js移动到src下
rm a.js 删除文件(不会进入回收站)
vim a.js 进入vim编辑器
cat a.js 可直接查看文件内容
head a.js 看文件头部
tail a.js 看文件尾部
head -n 1 a.js 看文件的第一行
tail -n 2 a,js 看尾部两行
grep '2' a.js 从文件中搜索带2的内容
复制代码
二十四、页面渲染过程
从输入url到得到html的详细过程
加载资源的形式
加载一个资源的过程
浏览器渲染页面的过程
1. 输入url(或跳转页面)加载html
2. 浏览器根据DNS服务器得到域名的IP地址(可自己买个域名试一下,机器不识别域名,识别IP)
3. 向这个IP的机器发送http请求
4. 服务器手动、处理并返回http请求
5. 浏览器得到返回内容
6. 根据HTML结构生成DOM Tree
7. 根据 CSS 生成 CSSOM(css放到head中)
8. 将 DOM 和 CSSOM 整合形成 RenderTree
9. 根据 RenderTree 开始渲染和展示
10. 遇到 <script> 时,会执行并阻塞渲染(js有权利改变DOM结构)
复制代码
window.onload 和 DOMContentLoaded 的区别
window.addEventListener('load', function () {
// 页面的全部资源加载完才会执行,包括图片、视频等
})
window.addEventListener('DOMContentLoaded', function () {
// DOM 渲染完即可执行,此时图片、视频还可能没有加载完
})
复制代码
二十五、性能优化
这是一个综合性的问题
原则
多使用内存、缓存或者其他方法
减少CPU计算、减少网络清楚
加载页面和静态资源
页面渲染
静态资源和压缩合并
静态资源缓存
使用CDN让资源加载更快
使用SSR(server side render)后端渲染,数据直接输出到HTML中。现在Vue React中都提出了这样的概念,其实jsp php asp 都属于后端渲染
渲染优化
css放前面,js放后面
懒加载(图片懒加载、下拉加载更多)
减少DOM查询,对DOM查询做缓存 (DOM操作非常昂贵)
减少DOM操作,多个操作尽量合并在一起执行
事件节流
尽早执行操作(如 DOMContentLoaded)
复制代码
<img id="img1" src="preview.png" data-realsrc="abc.png" />
<script type="text/javascript">
var img1 = document.getElementById('img1')
img1.src = img1.getAttribute('data-realsrc')
</script>
复制代码
缓存DOM查询
// 未缓存DOM查询
var i
for (i = 0; i < document.getElementsByTagName('p').length; i++ ) {
// todo
}
// 缓存了 DOM 查询
var pList = document.getElementByTagName('p')
var i
for (i = 0; i < pList.length; i++) {
// todo
}
复制代码
合并DOM插入
var listNode = document.getElementById('list')
// 插入 10 个 li 标签
var frag = document.createDocumentFragment() // 临时片段,不会触发DOM操作
var x, li
for(x = 0; x < 10; x++) {
li = document.createElement("li")
li.innerHTML = "List item" + x
frag.appendChild(li)
}
listNode.appendChild(frag)
复制代码
事件节流
var textarea = document.getElementById('text')
var timeoutId
textarea.addEventListener('keyup', fucntion() {
if (timeoutId) { // timeoutId 存在的时候直接清除
clearTimeout(timeoutId)
}
timeoutId = setTimeout(function () {
// 停留100ms以上触发 change 事件
}, 100)
}
复制代码
二十六、安全性
XSS
XSS 跨站请求攻击
在博客写一篇文章,同时偷偷插入一段<script>(script标签支持跨域)
攻击代码中,获取cookie,发送到自己的服务器
发布博客,有人查看博客内容
会把查看者的 cookie 发送到攻击者的服务器
预防
前端替换关键字,例如替换 < 为 < > 为 >
后端替换
复制代码
CSRF 跨站请求伪造
1.你已登录一个购物网站,正在浏览商品
2.该网站付费接口是 xxx.com/pay?id=100 但是没有任何验证
3.然后你收到一封邮件,隐藏着 <img src=xxx.com/pay?id=100>
4.你查看邮件的时候,就已经悄悄的付费购买了
复制代码
解决方案
增加验证流程,如输入指纹、密码、短信验证码
复制代码
二十七、增量替换数据
let statusText = {'loading': 'COMING SOON'}
const defaultStatusText = {
'loading': 'LOADING',
'noFollow': 'FAILED',
'hasFollow': 'SUCCESS',
...(statusText || {})
}
复制代码
增量替换后的结果
const defaultStatusText = {
'loading': 'COMING SOON',
'noFollow': 'FAILED',
'hasFollow': 'SUCCESS'
}
复制代码
二十八、String对象知多少
substr、substring、split、chartAt、concat、indexOf、lastIndexOf、tolowerCase、toUpperCase、search、replace、match
复制代码
- substr与substring
var a ="He quoted the bible to her"
a.substr(3,4) // quot
a.substring(3,4) // q
复制代码
- split 把一个字符串分割为字符串数组
var b = "reference"
b.split('') // ["r", "e", "f", "e", "r", "e", "n", "c", "e"]
var c = hello,world
c.split(',') // ["hello", "world"]
复制代码
附: slice(数组、字符串)、splice(用于数组)、split(字符串)傻傻分不清 www.jianshu.com/p/c5e67f4b9…
- chartAt(index) 从一个字符串中返回指定的字符,参数index为一个介于0 和字符串长度减1之间的整数
var b = "reference"
b.charAt(4) // "r"
复制代码
二十九、HTTP协议类
- HTTP协议的主要特点
无连接:连接一次就会断掉
无状态:无法区分两次http请求是否为同一个身份
简单快速
灵活
复制代码
- HTTP报文的组成部分
请求报文
请求行( GET / HTTP/1.1)、请求头 (KEY VALUE)、空行、请求体 (数据部分)
响应报文
状态行、响应头、空行、响应体
复制代码
- HTTP方法
GET ---> 获取资源
POST ---> 传输资源
PUT ---> 更新资源
DELETE ---> 删除资源(业务类型一般不删除资源)
HEAD ---> 获得报文首部
复制代码
- POST和GET的区别
*GET在浏览器回退时是无害的,而POST会再次提交请求
GET产生的URL地址可以被收藏,而POST不可以
*GET请求会被浏览器主动缓存,而POST不会,除非手动设置
GET请求只能进行url编码,而POST支持多种编码方式
*GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留
*GET请求在URL中传送的参数是有长度限制的,而POST没有限制
对参数的数据类型,GET只接受ASCII字符,而POST没有限制
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息
*GET参数通过URL传递,POST放在Request body中
复制代码
- HTTP状态码
1xx : 指示信息 -- 表示请求已接收,继续处理
2xx : 成功-- 表示请求已被成功接收
3xx : 重定向 -- 要完成请求必须进行进一步的操作
4xx :客户端错误 -- 请求有语法错误或请求无法实现
5xx : 服务器错误 -- 服务器未能实现合法的请求
200 ok: 客户端请求成功
206 Partial Content: 客户发送了一个带有Range头的GET请求,服务器完成了他它(一般用于音频等)
301 Moved Permanently: 永久重定向,请求的页面已经转移至新的url
302: 临时重定向
304: 浏览器已经有缓存
400:客户端请求有语法错误,不能被服务器理解
401:请求未经授权
403: 禁止访问请求页面
404:请求资源不存在
500: 服务器发生不可预期的错误原来缓冲的文档还可以继续使用
503: 请求未完成,服务器零食过载或宕机,一段时间后可能恢复正常
复制代码
- 什么是持久连接
HTTP协议采用“请求-应答”模式,当使用普通模式,即非keep-Alive模式时,每个请求/应答客户和服务器都要新建一个连接,完成之后立即断开连接(HTTP协议为无连接的协议,无连接的协议上面说过)
当使用Keep-Alive模式(又称为持久连接、连接重用)时,Keep-Alive功能使客户端到服务器端的连接持续有效,当出现对服务器的后续请求时,Keep-Alive功能避免了建立或重新建立连接(HTTP1.1版本才支持,1.0版本不支持)
复制代码
- 什么是管线化
在持久连接的情况下,某个连接上消息的传递类似于
请求1 -> 响应1 -> 请求2 -> 响应2 -> 请求3 -> 响应3
某个连接上的消息变成了类似这样(多个请求一次打包发送,一次打包返回,实在持久连接下完成的)
请求1 -> 请求2 -> 请求3 -> 响应1 -> 响应2 -> 响应3
管线化
管线化机制通过持久连接完成,仅HTTP/1.1 支持此技术
只有GET和HEAD请求可以进行管线化,而POST则有所限制
初次创建连接时不应启动管线机制,因为对方(服务器)不一定支持HTTP/1.1版本的协议
未完待续...
复制代码
三十、通信类
-
什么是同源策略及限制
同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。包含协议、域名、端口。这是一个用于隔离潜在恶意文件的关键的安全机制。
- Cookie、LocalStroage和 IndexDB无法读取
- DOM 无法获得
- AJAX 请求不能发送 session
-
前后端如何通信
- AJAX
- WebSocket
- CORS
- 如何创建Ajax
- XMLHttpRequest对象的工作流程
- 兼容性处理
- 事件的触发条件
- 事件的触发顺序
1. 声明对象 兼容
2. get post 支持
3. open()确定发送方式
4. 发送
5. xhr.onload 响应
复制代码
三十一、跨域通信的几种方式
- JSONP
原理:script标签的异步加载,执行回调函数
<script src="http://www.xxx.com?callback=jsonp"></script>
<script>
jsonp({
data: {}
})
</script>
在出现postMessage和CORS之前一直用JSONP来做跨域通信
复制代码
- Hash
url中的#
Hash改变不会刷新页面
search改变会刷新页面
利用hash场景:当前页面A 通过iframe或frame嵌入了跨域的页面B
var data = window.location.hash
复制代码
- postMessage
H5 新增
窗口A(http:A.com)向跨域的窗口B(http://B.com)发送信息
window.postMessage('data','http://B.com') // 向跨域窗口B发出请求,'data'代表你要发送的数据
在窗口B中监听
window.addEventListener('message', function (event) {
console.log(event.origin) // http://A.com
console.log(event.source) // Awindow
console.log(event.data) // data
},false)
// message事件和keyup keydown类似
复制代码
- WebSocket
参考资料:http://www.ruanyifeng.com/blog/2017/05/websocket.html
不受同源策略的限制
var ws = new webSocket('wss://echo.websocket.org')
ws.onopen = function (evt) {
console.log('Connection open ...')
ws.send('Hello WebSockets')
}
ws.onmessage = function (evt) {
console.log('Received Message' + evt.data)
ws.close()
}
ws.onclose = function (evt) {
console.log('Connection closed')
}
复制代码
- CORS
参考资料:http:// www.ruanyifeng.com/blog/2016/04/cors.html
是ajax的一个变种
fetch('/some/url', {
method: 'get'
}).then(function (response) {
}).catch(function (err) {
// 出错了,等价于then的第二个参数,但这样更好用更直观
})
复制代码
三十二、async函数
ES2017 标准引入了 async 函数,使得异步操作变得更加方便。
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
async函数对 Generator 函数的改进,体现在以下四点。
(1)内置执行器。
Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
asyncReadFile();
复制代码
上面的代码调用了asyncReadFile
函数,然后它就会自动执行,输出最后结果。这完全不像 Generator
函数,需要调用next
方法,或者用co
模块,才能真正执行,得到最后结果。
(2)更好的语义。
async
和await
,比起星号和yield
,语义更清楚了。async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。
(3)更广的适用性。
co
模块约定,yield
命令后面只能是 Thunk
函数或 Promise
对象,而async
函数的await
命令后面,可以是 Promise
对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved
的 Promise
对象)。
(4)返回值是 Promise。
async
函数的返回值是 Promise
对象,这比Generator
函数的返回值是 Iterator
对象方便多了。你可以用then
方法指定下一步的操作。
进一步说,async
函数完全可以看作多个异步操作,包装成的一个 Promise
对象,而await
命令就是内部then
命令的语法糖。
async
函数的语法规则总体上比较简单,难点是错误处理机制。
async
函数内部return
语句返回的值,会成为then
方法回调函数的参数。
如果有多个await命令,可以统一放在try...catch
结构中。
async function main() {
try {
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1, val2);
console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
}
复制代码
async 函数的实现原理
async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
});
}
复制代码
所有的async
函数都可以写成上面的第二种形式,其中的spawn
函数就是自动执行器。
二十三:this知多少
单例的代码写法
var a = {
fa: () => {
console.log(this)
},
fb: function () {
console.log(this)
}
}
a.fa() // window
a.fb() // a
箭头函数里面根本没有自己的this,而是引用外层的this。
复制代码
const cat = {
lives: 9,
jumps: () => {
this.lives--;
}
}
复制代码
上面代码中,cat.jumps()方法是一个箭头函数,这是错误的。调用cat.jumps()时,如果是普通函数,该方法内部的this指向cat;如果写成上面那样的箭头函数,使得this指向全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致jumps箭头函数定义时的作用域就是全局作用域。
var button = document.getElementById('press');
button.addEventListener('click', () => {
this.classList.toggle('on');
});
复制代码
上面代码运行时,点击按钮会报错,因为button的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。
var a = 1
function fn(f) {
var a = 2
return f
}
function bar() {
console.log(a)
}
var f1 = fn(bar)
f1() // 1
复制代码
var a = 1
function fn(f) {
var a = 2
return function () {
console.log(a)
}
}
var f1 = fn()
f1() // 2
复制代码
二十四. Event Loop
Event Loop
即事件循环,是指浏览器或Node
的一种解决JavaScript
单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。
弄懂Event Loop的必要性
- 增加自己技术的深度,懂得
JavaScript
的运行机制。 - 掌握底层原理,可以让自己以不变,应万变。
堆 栈 队列
堆是一种数据结构,是利用完全二叉树维护的一组数据,堆分为两种,一种为最大堆,一种为最小堆,将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。 堆是线性数据结构,相当于一维数组,有唯一后继。
栈(Stack)
后进先出
栈在计算机科学中是限定仅在表尾进行插入或删除操作的线性表。 栈是一种数据结构,它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据。
栈是只能在某一端插入和删除的特殊线性表。
队列(Queue)
先进先出
特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。
进行插入操作的端称为队尾,进行删除操作的端称为队头。 队列中没有元素时,称为空队列。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从列中删除,故队列又称为先进先出(FIFO—first in first out
)
Event Loop
在JavaScript
,任务被分为两种,一种宏任务(MacroTask
)也叫Task
,一种叫微笑任务(MicroTask
)。
MacroTask(宏任务)
script
全部代码、setTimeout
、setInterval
、setImmediate
(浏览器暂时不支持,值有IE10支持)
MicroTask(微任务)
Process.nextTick(Node独有)
、Promise
、MutationObserver
浏览器中的Event Loop
JavaScript
有一个main thread
主线程和 call-stack
调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。
JS调用栈
JS调用栈采用的是后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行完成后,就会从栈顶移出,知道栈内被清空。
同步任务和异步任务
JavaScript