[TOC] # 一、变量类型和计算 ## 1\. JS变量类型 分为原始类型和应用类型。 * JS中有 6 种原始类型,分别是: 1. **boolean** 2. number 3. **string** 4. **undefined** 5. [symbol](https://zhuanlan.zhihu.com/p/22652486) 6. null 7. bigint > Symbol: > > * Symbol: let x = Symbol(12); > > * 输出x为symbol(12)而不是12且不等于12 > > * 主要用来表示独一的量 > > > bigint: > > * let x = 123456789n; 在后面加n表示 > > * 可以表示任意大数 > > * 1n == 1, 1n !== 1 > * 引用类型: 1. 对象 2. 数组 3. 函数 ### <mark>1-1. JS变量按存储方式分类</mark> * 值类型:在内存中,每个值都存储在变量对应的位置 ~~~ var a = 100;    var b = a;    console.log(a); //100    console.log(b); //100 ​    a = 200;    console.log(b); //100 ​    var c = b;    console.log(c); //100    b = 300;    console.log(c); //100    //以上体现值类型的特点,不会因为赋值而相互干预 ~~~ 但是呢,如果某个对象`var a = {age:20}`需要无限地扩大属性,比如加上`a.name; a.sex`, 这时候空间肯定不够用,所以出现了**引用类型** * 引用类型:**对象、数组、函数** * 引用类型:在内存中,**变量**实际上是真实对象/数组/函数的**指针**。任何一个变量都可以作为指针,修改原始的对象/数组/函数 ~~~    var a = {age:20};    var b = a;    console.log(b);  //Object age:20    b.age = 21;    console.log(a);  //Object age:21    var c = b;    c.age = 22;    console.log(a);  //Object age:22    //以上体现引用类型的特点,内存中通过指针指向真实对象,不会拷贝复制具体的值 ​    a.name = 'Bob';    a.bb = true;    a.cc = 10000;    console.log(a);  //{age: 22, name: "Bob", bb: true, cc: 10000}    //Object对象作为引用类型,可以扩大属性 ​    var arr = [1,2,3];    arr.age = 21;    console.log(arr); //(3) [1, 2, 3, age: 21]    var arr2 = arr; //只改变了指针    arr2.age = 22;    console.log(arr2); //(3) [1, 2, 3, age: 22]    console.log(arr); //(3) [1, 2, 3, age: 22]    //数组作为引用类型 ​    function fn(){};    console.log(fn); //ƒ fn(){}    fn.age = 21;    var fn1 = fn; //只改变了指针    console.log(fn1.age); //21    fn1.age = 22;    console.log(fn.age); //22    console.log(fn1.age); //22    //函数作为引用类型 ~~~ ### <mark>1-2. 类型判断/数组/空对象</mark> #### 1-2-1. typeof可以得到的JS类型 * 8个:**number、string、boolean、undefined、object、function**、symbol、[bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) ~~~    console.log(typeof 10); //number    console.log(typeof '20'); //string    console.log(typeof true); //boolean    console.log(typeof unds); //undefined    console.log(typeof {a:1}); //object    console.log(typeof fn);  //function    console.log(typeof null);  //object    //typeof可以区分值类型,不可区分引用类型(函数除外),null也是object    //因为设计的时候`null`是全0,而对象是`000`开头,所以有这个误判。 ~~~ * typeof**可以区分值类型**,但是对于**null会误判成Object** * typeof**不可以区分引用类型**数组和对象,但是可以辨别出**function**,因为函数非常特殊 #### 1-2-2. 手写类型判断函数 1. 判断最特殊的**null**(typeof1判错) 2. 用typeof判断**基础的值类型和函数** 3. 使用`Object.prototype.toString.call(target)`来判断**引用类型** 注意: 一定是使用`call`来调用,不然是判断的Object.prototype的类型 之所以要先判断是否为基本类型是因为:虽然`Object.prototype.toString.call()`能判断出某值是:**number/string/boolean**,但是其实在包装的时候是把他们先转成了对象然后再判断类型的。 但是JS中包装类型和原始类型还是有差别的,因为对一个**包装类型**来说,typeof的值是object ~~~ /** * 类型判断 */ function getType(target) {  //先处理最特殊的Null  if(target === null) {    return 'null'; }  //判断是不是基础类型  const typeOfT = typeof target  if(typeOfT !== 'object') {    return typeOfT; }  //肯定是引用类型了  const template =    "[object Object]": "object",    "[object Array]" : "array",    // 一些包装类型    "[object String]": "object - string",    "[object Number]": "object - number",    "[object Boolean]": "object - boolean" };  const typeStr = Object.prototype.toString.call(target);  return template[typeStr]; } var a = new String('string'); getType(a); //"object - string" ~~~ #### 1-2-3. 判断数组类型 ~~~ //方法1 function isArray(t){    return Array.isArray(t) } //方法2 function isArray(t){    let type = Object.prototype.toString.call(t);    return (type === "[object Array]"); } ​ //方法3 function isArray(t){    return (t instanceof Array); } ​ //方法4 function isArray(t){    return (t.__proto__.constructor === Array); } //测试 console.log(isArray([1,2,3])) //true console.log(isArray(1,2,3))  //false ~~~ * ~~~ Array.isArray(obj) ~~~ * ECMAScript 5种的函数,当使用ie8的时候就会出现问题。 * ~~~ Object.prototype.toString.call(obj) == '[object Array]' ~~~ * 这个方法比较靠谱 * ~~~ obj instanceof Array ~~~ * 当用来检测在不同的window或iframe里构造的数组时会失败。这是因为每一个iframe都有它自己的执行环境,彼此之间并不共享原型链,所以此时的判断一个对象是否为数组就会失败。此时我们有一个更好的方式去判断一个对象是否为数组。 * ~~~ obj.__proto__.constructor.name === Array ~~~ * constructor属性返回对创建此对象的函数的引用 #### 1-2-4. 判断空对象 ~~~ //方法1:for...in...遍历属性,为真则非空 var obj = {}; function fn(obj){ for (var i in obj) { return true } return false // 如果为空,返回false } fn(obj) //方法2:利用JSON.stringify function fn(obj){ if(JSON.stringify(obj) === '{}'){ return false // 如果为空,返回false } return true } //注意不能用toString obj.toString() // "[object Object]" //方法3:ES6的Object.keys() function fn(obj){ if(Object.keys(obj).length === 0){ return false // 如果为空,返回false } return true //一行搞定法:return Object.keys(obj).length === 0 } var a = {} Object.keys(a) // [] ~~~ ### 1-3. 类型转换 #### 1-3-1. 强制类型转换的方式 * **字符串拼接** * **\==运算符** * **if语句** * **逻辑运算** ~~~ console.log(100+10); //110 console.log(100+'10'); //10010 //字符串拼接导致强制类型转换 console.log(100 == '100'); //true console.log(100 === '100'); //false console.log('' == 0); //true console.log('' === 0); //false console.log(null == undefined); //true console.log(null === undefined); //false //==运算符导致强制类型转换 if (100) {console.log(101)}; //100->true if ('') {console.log(102)}; //''->false //if语句的强制类型转换 console.log(10 && 0); //0 console.log('' || 'abc'); //'abc' //逻辑运算符的强制类型转换 /*----------------------------------------------*/ var a = 100; console.log(!!a); //true var b = ''; console.log(!!b); //false //双非符号!!判断强制转换的布尔类型 /*----------------------------------------------*/ console.log(!!0); //false console.log(!!-0); //false console.log(!!NaN); //false console.log(!!''); //false console.log(!!null); //false console.log(!!undefined); //false console.log(!!false); //false //false的需要记住 ~~~ #### 1-3-2. JS变量类型转换的规则 * “+”,运算中**只要有一方是有字符串**,所有值类型都会自动转字符串,所有引用类型也会调用自身的toString方法。 ![img](https://img.kancloud.cn/d8/9c/d89c43e05288c9942cfebe1087fafbdd_145x170.png)![img](https://img.kancloud.cn/c3/e3/c3e32d8fecf5bfaa37ec0890657c6a1d_184x131.png) * “+”, **若没有字符串,有一方是数字**,那么会将另一方的值类型转换为数字,对象类型会调用toString方法 ~~~ 1 + '1' // '11' 1 + undefined //NaN 1 + true //2 1 + false //1 1 + null //1 true + true // 2 4 + [1,2,3] // "41,2,3" 4 + [1] //"41" ~~~ * “\*”,运算中,把所有**值类型都转换为数字**,引用类型不支持 ![img](https://img.kancloud.cn/b1/5a/b15acee271b1d1bb01dadcc6a8b7b7f5_181x261.png)![img](https://img.kancloud.cn/54/64/54643eb0af223fa7ca62a3d956848139_206x127.png) * 还需要注意这个表达式`'a' + + 'b'` ~~~ 'a' + + '1' // "a1" 'a' + + 'b' // -> "aNaN" ~~~ 因为 + 'b' 等于 NaN,所以结果为 "aNaN",你可能也会在一些代码中看到过 + '1' 的形式来快速获取 number 类型。 [JS类型转换规则总结](https://blog.csdn.net/qq_37746973/article/details/82491282) [JS隐式类型转换](https://blog.csdn.net/qq_37746973/article/details/81010057) #### 1-3-3. 对象(引用类型)怎么转基本类型的? 对象在转换基本类型时,会调用`valueOf`, 需要转成字符类型时调用`toString`。 ~~~ var a = { valueOf() { return 0; }, toString() { return '1'; } } 1 + a // 1 '1'.concat(a) //"11" ~~~ 也可以重写 `Symbol.toPrimitive` ,该方法在转基本类型时调用**优先级最高**。 [Symbol.toPrimitive](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toPrimitive) 指将被调用的指定函数值的属性转换为相对应的原始值。 ~~~ // 一个没有提供 Symbol.toPrimitive 属性的对象,参与运算时的输出结果 var obj1 = {}; console.log(+obj1); // NaN console.log(`${obj1}`); // "[object Object]" console.log(obj1 + ""); // "[object Object]" // 接下面声明一个对象,手动赋予了 Symbol.toPrimitive 属性,再来查看输出结果 var obj2 = { [Symbol.toPrimitive](hint) { if (hint == "number") { return 10; } if (hint == "string") { return "hello"; } return true; } }; console.log(+obj2); // 10 -- hint 参数值是 "number" console.log(`${obj2}`); // "hello" -- hint 参数值是 "string" console.log(obj2 + ""); // "true" -- hint 参数值是 "default" ~~~ #### 1-3-4. 应用 ##### 1\. 输出以下代码运行结果 ~~~ 1 + "1" 2 * "2" [1, 2] + [2, 1] "a" + + "b" ~~~ * 1 + "1" * 加性操作符:如果只**有一个**操作数是**字符串**,则将**另一个操作数转换为字符串**,然后再将两个字符串拼接起来; * 所以值为:“11” * 如果没有字符串,就都转换为数字类型相加(undefinde输出NaN) * 2 \* "2" * 乘性操作符:如果有一个操作数不是数值,则在后台调用 Number()将其转换为数值4 * \[1, 2\] + \[2, 1\] * Javascript中所有对象基本都是**先调用valueOf方法,如果不是数值,再调用toString方法**。 * 所以两个数组对象的toString方法相加,值为:"1,22,1" * "a" + + "b" * 后边的“+”将作为一元操作符,如果操作数是字符串,将调用Number方法将该操作数转为数值,如果操作数无法转为数值,则为NaN。 * 所以值为:"aNaN" ##### 2\. 100+ 问题 ~~~ '100' + 100 // "100100" 100 + '100' // "100100" 100 + true // 101 100 + false // 100 100 + undefined //NaN 100 + null // 100 ~~~ ### 1-4. 应用问题 #### 1-4-1. obj.toString() 和Object.prototype.toString.call(obj) * 同样是检测对象obj调用toString方法,obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是为什么? * 这是因为**toString为Object的原型方法**,而**Array ,function等类型作为Object的实例,都重写了toString方法**。 * 不同的对象类型调用toString方法时,根据原型链的知识,**调用的是对应的重写之后的toString方法**(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串.....),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型。 * 因此,在想要得到对象的具体类型时,应该调用**Object上原型toString方法**。 * **Object.prototype.toString** * 如果是原始类型,他会将原始类型包装为引用类型,然后调用对应方法 * ~~~ function dd(){} var toString = Object.prototype.toString; toString.call(dd); //[object Function] toString.call(new Object); //[object Object] toString.call(new Array); //[object Array] 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] toString.call(123) //[object Number] toString.call('abc') //[object String] ~~~ #### 1-4-2. "a common string"为什么会有length属性 * 通过字面量的方式创建:var a = 'string';,这时它就是**基本类型值**;通过构造函数的方式创建:var a = new String('string');这时它是**对象类型**。 ![img](https://img.kancloud.cn/4a/de/4adee747ec24a2f3e622a252beb34d6f_261x171.png) * **基本类型是没有属性和方法的**,但仍然可以使用对象才有的属性方法。这时因为在对基本类型使用属性方法的时候,后台会隐式的创建这个基本类型的对象,之后再销毁这个对象 #### 1-4-3. console.log(!!(new Boolean(false))输出什么 \[易混淆\] **true!!** 布尔的包装对象 Boolean 的对象实例,**对象只有在 null 与 undefined 时,才会认定为布尔的 false 值**,布尔包装对象本身是个对象,**对象->布尔 都是 true**,所以 new Boolean(false)其实是布尔的 true,看下面这段代码: ~~~ if(new Boolean(false)){ alert('true!!'); } ~~~ 只有使用了 valueOf 后才是真正的转换布尔值,与上面包装对象与原始资料转换说明的相同: ~~~ !!(new Boolean(false)) //true (new Boolean(false)).valueOf() //false ~~~ #### 1-4-4. 1 与 Number(1)有什么区别 ~~~ var a = Number(1) // 1 var b = new Number(1) // Number {[[PrimitiveValue]]: 1} typeof (a) // number typeof (b) // object a == b // true a === b // false ~~~ * **var a = 1 是一个常量**,而 **Number(1)是一个函数** * **new Number(1)**返回的是一个对象 * a==b 为 true 是因为所以在求值过程中,**总是会强制转为原始数据类型而非对象**,例如下面的代码: ~~~ typeof 123 // "number" typeof new Number(123) // "object" 123 instanceof Number // false (new Number(123)) instanceof Number // true 123 === new Number(123) // false ~~~ #### 1-4-5. 【大数运算】Number()的存储空间是多大,如果后台发送了一个超过最大字节的数字咋办? js的number类型有个最大值(安全值),即**2的53次方**,为9007199254740992。 如果超过这个值,那么js会出现不精确的问题。这个值为**16位**。 解决**大数运算**: * 将大数的每一位存进数组,然后对两个数组的每一个位单独运算,得到运算结果 * 使用插件 * 字符串转换 #### 1-4-6. 区分`==`和`===` * 用`==`只有一种情况:**在参数或者属性已经定义的情况下,判断它是否存在** * 其他都用`===` ~~~ var obj = {}; //判断一个对象的属性是否存在用双等 if(obj.a == null){ //相当于obj.a===null || obj.a===undefined //jQuery源码推荐写法 } function x(a,b) {if (a == null){}};//判断一个函数的参数是否存在用双等 //==的使用场景,注意该参数或者属性必须是已经定义了的 ~~~ ##### 1\. `0.1+0.2 === 0.3` 我们知道**浮点数计算是不精确**的,上面的返回式实际上是这样的: * 0.1 + 0.2 = 0.30000000000000004 * 0.1 + 0.2 - 0.3 = 5.551115123125783e-17 * 5.551115123125783e-17.toFixed(20) = '0.00000000000000005551' 如何相等呢? `(0.1*10+0.2*10)/10===0.3` true `withinErrorMargin(0.1 + 0.2, 0.3)` true `(0.1+0.2)-0.3<Number.EPSILON` true ##### 2\. == 操作符是怎么进行类型转换的 对于 == 来说,如果对比双方的类型不一样的话,就会进行类型转换 * 判断流程: 1. 首先会判断两者类型是否相同。相同的话就是比大小了 2. 类型不相同的话,那么就会进行类型转换 3. 会先判断是否在对比 **null 和 undefined**,是的话就会返回 true ~~~ null == undefined ~~~ 4. 判断两者类型是否为 **string 和 number**,是的话就会将字符串转换为 **number** * ~~~ 1 == '1' ↓ 1 == 1 ~~~ 5. 判断其中一方是否为 **boolean**,是的话就会把 boolean 转为 **number** 再进行判断 * ~~~ '1' == true ↓ '1' == 1 ↓ 1 == 1 ~~~ 6. 判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断 * ~~~ '1' == { a: 'b' } ↓ '1' == '[object Object]' ~~~ 7. 两边都是对象的话,那么只要不是同一对象的不同引用,都为false 注意,**只要出现NaN,就一定是false**,因为就连NaN自己都不等于NaN 对于NaN,判断的方法是使用全局函数 `isNaN()` ##### 3\. == 操作符特例{ } * {} 等于true还是false ~~~ var a = {}; a == true // -> ? a == false // -> ? ~~~ * 答案是两个都为false * 因为 a.toString() -> '\[object Object\]' -> NaN ##### 4\. === 操作符怎么比较呢?怎么处理NaN * 不转类型,直接判断类型和值是否相同。 * 但n是 NaN === NaN 还是false ## 2\. 内置函数/API * [JS标准内置对象](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects) * 数据封装类对象(函数) * **String、Number、Boolean、Array、Object** * 其他对象 * **Function**、Math、Date、RegExp、Error、Arguments * ES6 新增对象 * Promise、Map、Set、Symbol、Proxy、Reflect ### 2-1. Array #### 2-1-1. 修改器 * 下面的这些方法会**改变调用它们的对象自身的值**: * **Array.prototype.pop()** * 删除数组的最后一个元素,并返回这个元素。 * **Array.prototype.push()** * 在数组的末尾增加一个或多个元素,并返回数组的新长度。 * **Array.prototype.shift()** * 删除数组的第一个元素,并返回这个元素。 * **Array.prototype.unshift()** * 在数组的开头增加一个或多个元素,并返回数组的新长度。 * **Array.prototype.splice()** * 在任意的位置给数组添加或删除任意个元素。 * **Array.prototype.reverse()** * 颠倒数组中元素的排列顺序,即原先的第一个变为最后一个,原先的最后一个变为第一个。 * **Array.prototype.sort()** * 对数组元素进行排序,并返回当前数组。 * ![img](https://img.kancloud.cn/54/3e/543ed42ef47662fcfd376b7d2b5fea3a_417x232.png) * **Array.prototype.fill()** * 将数组中指定区间的所有元素的值,都替换成某个固定的值。 * Array.prototype.copyWithin() * 在数组内部,将一段元素序列拷贝到另一段元素序列上,覆盖原有的值。 #### 2-1-2. 访问器 * 下面的这些方法绝对不会改变调用它们的对象的值,只会返回**一个新的数组**或者返回一个其它的期望值。 * **Array.prototype.join()** * 连接所有数组元素组成一个字符串。 * **Array.prototype.slice()** * 抽取当前数组中的一段元素组合成一个新数组。 * **Array.prototype.concat()** * 返回一个由当前数组和其它若干个数组或者若干个非数组值组合而成的新数组。 * **Array.prototype.includes()** * 判断当前数组是否包含某指定的值,如果是返回 true,否则返回 false。 * **Array.prototype.indexOf()** * 返回数组中第一个与指定值相等的元素的**索引**,如果找不到这样的元素,则返回 -1。 * **Array.prototype.lastIndexOf()** * 返回数组中最后一个(从右边数第一个)与指定值相等的元素的索引,如果找不到这样的元素,则返回 -1。 * Array.prototype.toSource() * 返回一个表示当前数组字面量的字符串。遮蔽了原型链上的 Object.prototype.toSource() 方法。 * Array.prototype.toString() * 返回一个由所有数组元素组合而成的字符串。遮蔽了原型链上的 Object.prototype.toString() 方法。 * Array.prototype.toLocaleString() * 返回一个由所有数组元素组合而成的本地化后的字符串。遮蔽了原型链上的 Object.prototype.toLocaleString() 方法。 #### <mark>2-1-3. 迭代器</mark> * 在下面的众多遍历方法中,有很多方法都需要指定一个回调函数作为参数。在每一个数组元素都分别执行完回调函数之前,数组的length属性会被缓存在某个地方,所以,如果你在回调函数中为当前数组添加了新的元素,那么那些新添加的元素是不会被遍历到的。此外,如果在回调函数中对当前数组进行了其它修改,比如改变某个元素的值或者删掉某个元素,那么随后的遍历操作可能会受到未预期的影响。总之,**不要尝试在遍历过程中对原数组进行任何修改**,虽然规范对这样的操作进行了详细的定义,但为了可读性和可维护性,请不要这样做。 * **Array.prototype.forEach()** * 为数组中的每个元素**执行一次回调函数**。 * ![img](https://img.kancloud.cn/1d/fd/1dfdf95acc0ac132258d22445dc91373_288x212.png) * **Array.prototype.map()** * 返回一个由回调函数的返回值**组成的新数组**。 * ![img](https://img.kancloud.cn/6c/ad/6cad9b884288788e7db79f3aa0223076_596x161.png) * **Array.prototype.reduce()** * 从左到右为每个数组元素执行一次回调函数,并把上次回调函数的返回值放在一个暂存器中传给下次回调函数,并返回**最后一次回调函数的返回值**。 * **Array.prototype.filter()** * 将所有在**过滤函数**中返回 true 的数组元素放进一个**新数组**中并返回。 * ![img](https://img.kancloud.cn/27/1a/271a42973f0b201e16f31e8d0536c42e_350x187.png) * **Array.prototype.every()** * 如果数组中的每个元素都满足**测试函数**,则返回 true,否则返回 false。 * ![img](https://img.kancloud.cn/a5/4d/a54dfa636121c729e5074c6784224267_377x207.png) * **Array.prototype.some()** * 如果数组中至少有一个元素满足**测试函数**,则返回 true,否则返回 false。 * ![img](https://img.kancloud.cn/f1/28/f128558acb80643b6839604cf14a12f3_409x186.png) * **Array.prototype.find()** * 找到**第一个满足测试函数**的元素并返回那个元素的**值**,如果找不到,则返回 undefined。 * **Array.prototype.findIndex()** * 找到**第一个满足测试函数**的元素并返回那个元素的**索引**,如果找不到,则返回 -1。 * ![img](https://img.kancloud.cn/94/82/9482c88545df80842defbbb8b763856e_187x82.png) * **Array.prototype.keys()** * 返回一个数组迭代器对象,该迭代器会包含所有数组元素的键。 * **Array.prototype.entries()** * 返回一个数组迭代器对象,该迭代器会包含所有数组元素的键值对。 #### 2-1-4. forEach、Map、filter的区别 * 相同: * 都是循环遍历**数组**中的每一项 * 每一次执行匿名函数都支持**三个参数**,数组中的当前项item,当前项的索引index,原始数组input * 匿名函数中的this都是指**window** * **只能遍历数组** * 不同: * forEach没有返回值,只能进行操作(回调函数) * map有返回值,是访问方法,返回的是新数组,不改变原来的数组 * filter有返回值,也是访问方法 ![img](https://img.kancloud.cn/1a/80/1a80e3288d468fb0343e73f1a5cbe131_224x179.png) forEach、Map、filter三个迭代方法都各有优势,如果不需要返回值,只需要操作的话,可以直接使用forEach,如果需要返回值的话,可以使用Map、filter方法,如果需要条件筛选的话,可以直接用filter。[链接](https://blog.csdn.net/qq_41559229/article/details/90087456?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task) #### 2-1-5. ES6新增 * fill ~~~ { // fill const list = [1, 2, 3, 4, 5] let list2 = [...list].fill(3) let list3 = [...list].fill(3, 1, 4) //填充值,开始填充位置,停止填充位置(1,2,3位) console.log(list2, list3) //[ 3, 3, 3, 3, 3 ] [ 1, 3, 3, 3, 5 ] } ~~~ * find * findIndex ~~~ { // find findIndex const list = [{ title: 'es6' }, { title: 'webpack', id: 2 }, { title: 'vue' }, { title: 'webpack', id: 3 }] let result = list.find(function (item) { return item.title === 'webpack' }) let resultIndex = list.findIndex(function (item) { return item.title === 'webpack' }) console.log(result,resultIndex) //result: { title: 'webpack', id: 2 } // resultIndex : 1 } ~~~ * Includes ~~~ { // includes 和 indexOf const list = [1, 2, 3, 4, 5, 6] let result = list.includes(2) console.log('includes', result) //includes true //和indexOf在简便性、精确性有所区别,但是根据判断存在和查找索引的情境来区分使用 [1, 2, NaN].includes(NaN) // true [1, 2, NaN].indexOf(NaN) // -1 } ~~~ * flat ~~~ { // flat展开数组的操作 const list = [1, 2, 3, ['2nd', 4, 5, 6, ['3rd', 7, 8]]] let flatList = [].concat(...list) console.log(flatList) //[ 1, 2, 3, '2nd', 4, 5, 6, [ '3rd', 7, 8 ] ] // 默认只展开第一层数组 let flatList2 = list.flat(2) console.log('flat', flatList2) //[1, 2, 3, "2nd", 4, 5, 6, "3rd", 7, 8] } ~~~ * filter ### 2-2. Object **for in** * ![img](https://img.kancloud.cn/3a/d6/3ad65a2164fbc3954c449ecf77a6a0e9_711x293.png) #### 2-2-1. ES6新增 * [Object.is](http://object.is/) ~~~ // Object.is()和'===' 一样判断两个值是否相等 // 只在如下场景下不同,其他场景都相同(同类型同值) let result = Object.is(NaN, NaN) //true console.log(result, NaN === NaN) //false ~~~ * Object.assign ~~~ // Object.assign()浅拷贝 const person = { name: '小米', age: 18, info: { height: 180 } } let person2 = {} Object.assign(person2, person) person.age = 20 person.info.height = 160 console.log(person2) //{ name: '小米', age: 18, info: { height: 160 } } // Object.assign()合并对象 const obj1 = { a: 1 } const obj = Object.assign({c: 2}, obj1) console.log(obj);//{c: 2, a: 1} ~~~ * Object.keys(可以实现深拷贝) ~~~ { // 对象里面的新遍历方法(也是一种深度拷贝) // Object.keys() Object.values(), Object.entries() const json = { name: {lo:'ss'}, video: 'es6', date: 2019 } let obj = {} for (const key of Object.keys(json)) { obj[key] = json[key] } console.log(obj) // { name: 'Nick', video: 'es6', date: 2019 } //{ name: { lo: 'ss' }, video: 'es6', date: 2019 } } ~~~ * Object.values * Object.entries ### 2-3. String [参考](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String) * **String.prototype.split()** * 通过分离字符串成字串,将字符串对象分割成字符串数组。 * **String.prototype.slice(start, end)** * 摘取一个字符串区域,返回一个新的字符串。 * String.prototype.substr(start, len) * 通过指定字符数返回在指定位置开始的字符串中的字符。 * String.prototype.substring() * 返回在字符串中指定两个下标之间的字符。 * **String.prototype.trim()** * 从字符串的开始和结尾去除空格 * **String.prototype.concat()** * 连接两个字符串文本,并返回一个新的字符串。 * **String.prototype.match()** * 使用正则表达式与字符串相比较。 * **String.prototype.replace()** * 被用来在正则表达式和字符串直接比较,然后用新的子串来替换被匹配的子串。 * **String.prototype.search()** * 对正则表达式和指定字符串进行匹配搜索,返回第一个出现的匹配项的下标。 * String.prototype.toString() * 返回用字符串表示的特定对象。重写 Object.prototype.toString 方法。 * **String.prototype.indexOf()** * 找到字符所在索引,如果找不到,则返回 -1。 * ![img](https://img.kancloud.cn/15/05/1505fdd81a2f92766d45aa58ae2ffa63_203x86.png) #### 2-3-1. 结构化数据JSON? * 结构化数据对象用来表示和操作结构化的缓冲区数据,或使用 JSON (JavaScript Object Notation)编码的数据。 * 常用的内置对象就是**JSON** * JSON有两个常用的API * 把对象变成字符串的stringfy()和把字符串变成对象的parse() * **JSON.stringify()**方法将一个 **JavaScript 值(对象或者数组)转换为一个 JSON 字符串**,如果指定了 replacer 是一个函数,则可以选择性地替换值,或者如果指定了 replacer 是一个数组,则可选择性地仅包含数组指定的属性。 * `JSON.stringify(value[, replacer [, space]])` * ![img](https://img.kancloud.cn/a5/47/a547d9d535565e49656247f75de1d03c_310x41.png) * **JSON.parse()**方法用**来解析JSON字符串**,构造由字符串描述的JavaScript值或对象。提供可选的 reviver 函数用以在返回之前对所得到的对象执行变换(操作)。 * `JSON.parse(text[, reviver])` * ![img](https://img.kancloud.cn/dd/21/dd2109259c3a1094382f4933ff392509_359x73.png) #### 2-3-2. ES6新增 ![img](https://img.kancloud.cn/40/d1/40d1f78e6117f69187fa1f082e1624bd_897x633.png) ### 2-4. 其他 #### 2-4-1. Date日期API ~~~ Date.now() //获取当前时间毫秒数 var dt = new Date() console.log(dt) //Mon Jan 27 2020 09:32:40 GMT+0800 (中国标准时间) console.log(dt.getTime(), //获取毫秒数 1580088760487 dt.getFullYear(), //年 2020 dt.getMonth(),//月(0-11)---打印需要加一 0 dt.getDate(), //日(0-31) 27 dt.getHours(), //小时(0-23) 9 dt.getMinutes(), //分钟(0-59) 32 dt.getSeconds()) //秒(0-59) 40 //日期API ~~~ * 请生成一个日期转换函数生成2020-03-02格式的日期 ~~~ function formatDate(dt){ if(!dt){ //参数不存在,重新获取(演示效果,保证不报错) dt = new Data() } var year = dt.getFullYear(); var month = dt.getMonth()+1; var date = dt.getDate() //强制类型转换 return year + '-' + (month<10?'0'+month:month) + '-' + (date<10?'0'+date:date) } var dt = new Date() var formatDate = formatDate(dt) console.log(formatDate) ~~~ #### 2-4-2. Math随机数API ~~~ console.log( Math.random(), Math.random(), Math.random() ) //Math的API 常用的是random,生成0-1之间的小数且长度不定,一般用于清除缓存 //在多次频繁访问同一链接时,获取页面更新并清除缓存 ~~~ * 请生成一个长度一致的随机字符串 ~~~ var random = Math.random() var random = random + '0000000000' //后面加上10个0 var random = random.slice(0,10) console.log(random) ~~~ #### 2-4-3. length ~~~ Window.length //返回在当前窗口中frames的数量(包括IFRAMES) String.length //返回字符串中的字符数目 Function.length //获取一个函数定义的参数数目 Array.length //返回数组中元素的数目 ~~~ # 二、API汇总 ## 1\. DOM API ### 1-1. DOM本质 * DOM的本质是树,对应的基本数据结构也是树 * 浏览器把获得的HTML代码,结构化成为一个**浏览器可以识别**并且**js可以操作**的模型 ### <mark>1-2. DOM操作API</mark> #### 1-2-1. 节点操作API(property和attribute) 即对**节点的属性**进行**获取和修改**。 ~~~ <div id="div1" class="class-div1"> <p id="p1" data-name="xyz">this is p1</p> <p id="p2">this is p2</p> </div> <div id="div2"> <p id="p3">this is p3</p> <p id="p4">this is p4</p> </div> ~~~ **\-->property属性**,是**JavaScript里的对象**。 * 可以根据id、标签名、类名等,获取和修改节点的**property**属性。![img](https://img.kancloud.cn/69/99/699986e11cd39b8a8525bd4c0adb05b3_398x125.png) ![img](https://img.kancloud.cn/50/1b/501bd8c87e86d43fd61d39fa34d35802_620x132.png)![img](https://img.kancloud.cn/6f/b8/6fb8e296dcff82a054934195db869009_352x198.png) **\-->Attribute属性**,是**标签属性**。 * 是标签上的属性,是property的子集![img](https://img.kancloud.cn/5b/43/5b435107a31dc2456f4a5c7bf51549fa_392x213.png) **\-->Attribute属性和Property属性的[区别](https://www.cnblogs.com/lmjZone/p/8760232.html)** 1. Property是**JS对象的属性**,而Attribute是**标签上的属性** 2. Attribute是Property的一个**子集** 3. Attribute的**基本属性**比如id和class会作为Property同步到JS对象,但是一些**附加的自定义标签属性**不会 4. 对属性Property可以赋任何类型的值,而对特性Attribute只能赋值字符串! 5. 更改Attribute,Property可以同步;但是**更改Property,Attribute不能同步**(吃了冰淇淋上的巧克力,冰淇淋可以同步;吃了冰淇淋,巧克力怎么同步呢) \--举个🌰![img](https://img.kancloud.cn/3a/19/3a19c7480be1a1fc2564fd4453f6dcc6_417x219.png) 一个input标签 ,它的attribute是3 `<input value="3" />` 但如果使用(property )`input.value = 5` 或 直接修改值为5,这时使用**getAttribute**得到的还是"3"。 #### 1-2-2. 结构操作 创建、添加、移动、替换、插入、查找/获取 * 创建 * **createDocumentFragment(**) //创建一个 DOM 片段 * **createElement()**//创建一个具体的元素 * **createTextNode()**//创建一个文本节点 ~~~ var p = document.createElement('p') //新建一个元素 p.innerHTML = 'new p' //注意写法 var div1 = document.getElementById('div1') div1.appendChild(p) //新增 console.log('新增new p,并作为div1的子元素') ~~~ * 添加≠创建、移除、替换、插入 * **appendChild()** * **removeChild()** * **replaceChild()** * **insertBefore()** //在已有的子节点前插入一个新的子节点 * 查找 * **getElementsByTagName()** //通过标签名称 * **getElementsByName()** // 通过元素的 Name 属性的值(IE 容错能力较强,会得到一个数组,其中包括 id 等于 name 值的) * **getElementsByClassName()** //通过元素类名 * **getElementById()** //通过元素 Id,唯一性 * 获取父元素 **parentElement** * 获取子元素 **childNodes** ### 1-3. DOM事件类 #### <mark>1-3-1. DOM事件流</mark> * DOM标准采用捕获+冒泡。两种事件流都会触发DOM的所有对象,从**window对象**开始,也在**window对象**结束。 * DOM标准规定事件流包括三个阶段: * 事件捕获阶段 * 从上到下,从window -> document -> html -> body -> ... -> 目标元素 * 处于目标阶段 * 事件冒泡阶段【从下到上,与捕获反向】![img](https://img.kancloud.cn/ad/90/ad90ded8edcd2c52db91b613626d8c06_489x439.png) * 以用户点击了按钮为例,如何上传到页面,页面又如何做出响应? 1. 用户行为事件 2. 事件捕获阶段,从window对象往**事件触发处**传播,中间遇到注册的**捕获事件**会**触发** 3. 目标阶段,传播到事件触发处,**触发**目标DOM元素注册的事件 4. 事件冒泡阶段,从**事件触发处**往window对象传播,中间遇到注册的**冒泡事件**会**触发** 事件触发一般来说会按照上面的顺序进行,但是也有特例,如果给一个 body 中的子节点**同时注册**冒泡和捕获事件,事件触发会按照**注册的顺序**执行。 #### 1-3-2. DOM事件的级别 * DOM0 * onXXX类型的定义事件 * element.onclick = function(e) { ... } * DOM2 * addEventListener方式 * element.addEventListener('click', function (e) { ... }) * btn.removeEventListener('click', func, false) * btn.attachEvent("onclick", func); * btn.detachEvent("onclick", func); * DOM3 * 增加了很多事件类型 * element.addEventListener('keyup', function (e) { ... }) * eventUtil 是自定义对象,textInput 是 DOM3 级事件 * **DOM0**的事件具有极好的跨浏览器优势, 会以**最快的速度**绑定 如果通过DOM2绑定要等到JS运行, DOM0不用, 因为DOM0是写在元素上面的哇【DOM0是松鼠,DOM2是猴子,松鼠爬树快吧】 * **DOM2级**的事件规定了事件流包含三个阶段包括:**1:事件捕获, 2:处于目标阶段, 3:事件冒泡阶段** * **DOM0**不能绑定多个事件处理函数,会覆盖;但是**DOM2级别**的事件可以绑定**多个处理函数** #### <mark>1-3-3. addEventListener()的第三个参数</mark> * DOM方法 addEventListener() 和 removeEventListener()是用来分配/**注册和删除**事件的函数。 这两个方法都需要三个参数,分别为: * **事件名称(String)、要触发的事件处理函数(Function)、指定事件处理函数的时期或阶段(boolean)**。 * 其中,当第三个参数设置为**true**就在**捕获**过程中执行,反之就在冒泡过程中执行处理函数。捕获(true),冒泡(false) * 注意,默认是false! ~~~ var ev=document.getElementById('ev'); ev.addEventListener('click',function(){ console.log('ev capture'); },true); window.addEventListener('click',function(){ console.log('window capture'); },true); //捕获window document.addEventListener('click',function(){ console.log('document capture'); },true); //捕获document document.documentElement.addEventListener('click',function(){ console.log('html capture'); },true); //捕获html document.body.addEventListener('click',function(){ console.log('body capture'); },true); //捕获body ~~~ ![img](https://img.kancloud.cn/12/08/12081a1d509578c2dbe0eca7233bdc45_163x111.png) 将第三个参数由true改为false之后【从里到外冒泡,再里面的不执行】: ![img](https://img.kancloud.cn/0e/8b/0e8bd36a4108f61d24e7d2e9aa01b282_167x114.png) #### <mark>1-3-4. Event对象api</mark> 1. **用户行为事件相关** * event.target * **触发事件的某个具体的对象,只会出现在事件机制的目标阶段** * 即“谁触发了事件,谁就是 target ” * event.currentTarget * **绑定事件的元素(可以是父元素)** * 一个父元素有10个子元素,不想都让子元素绑定事件,那么可以在响应时再区分event.target处理 * event.preventDefault() * **阻止默认行为**,见后续实例 * event.cancelBubble()和event.preventBubble 都已经废弃 * event.stopPropagation() * **阻止在捕获阶段或冒泡阶段继续传播,而不是阻止冒泡** * 子元素单击,父元素不被响应 * 如果我们只希望事件只触发在目标上,这时候可以使用 **stopPropagation** 来阻止事件的进一步冒泡传播;当然它也可以阻止捕获事件 * event.stopImmediatePropagation() * **阻止事件冒泡并且阻止相同事件的其他侦听器被调用** * 一个按钮绑定两个事件A和B,想要A被执行,B不被执行;即阻止该事件目标执行别的注册事件 #### <mark>1-3-5. 事件api应用</mark> ##### **1\. 自定义事件** * Event【注意addEventListener时用**事件名**,dispatchEvent时用**事件对象**】 * CustomEvent CustomEvent不仅可以用来做自定义事件,还可以在后面跟一个[object](https://www.jianshu.com/p/3dda35dc9b98?utm_source=oschina-app)做参数 ~~~ var ev=document.getElementById('ev'); var eve = new Event('test'); //事件对象 ev.addEventListener('test',function(){ console.log('test dispatch'); //处理自定义事件 }); // 绑定的不是事件名而是事件对象(且不是字符串) setTimeout(function(){ ev.dispatchEvent(eve);//直接触发事件,不需动作 },1000) //------------------------------------------- // create and dispatch the event var nev = new CustomEvent('custome',{ bubbles:true,cancelable:true,composed:true }); // add an appropriate event listener---事件名 ev.addEventListener("custome", function(e) { console.log('我是CustomEvent');//处理自定义事件 }); ev.dispatchEvent(nev); ~~~ ##### 2\. 阻止默认行为 * 以**绑定一个链接**事件为例,封装事件函数且**阻止默认跳转**行为 ~~~ <a href="http://imooc.com" id="link1">imooc.com</a> var link1 = document.getElementById('link1') //封装前 link1.addEventListener('click',function(e){ e.preventDefault() //取消默认跳转行为 console.log('链接被点击') },true) //封装后 function bindEvent(elem,type,fn){ elem.addEventListener(type,fn) } bindEvent(link1,'click',function(e){ e.preventDefault() //取消默认跳转行为 console.log('链接被点击') }) ~~~ ##### **3\. 阻止冒泡** * 一个确定,三个取消都在一个页面;确定按钮绑定一个确定事件,父元素/body绑定一个取消事件即可 * 这时需要**阻止确定按钮绑定的事件冒泡**到父元素,否则点完确定还会触发取消事件 ~~~ <div id="div1"> <p id="p1">确定</p> <p id="p2">取消</p> <p id="p3">取消</p> <p id="p4">取消</p> </div> <script type="text/javascript"> console.log('点确定只弹出确定,点三个取消都弹出取消') function bindEvent(elem,type,fn){ elem.addEventListener(type,fn) } var p1 = document.getElementById('p1') //确定 bindEvent(p1,'click',function(e){ e.stopPropagation(); //阻止冒泡 alert('确定') }) var body = document.body bindEvent(body,'click',function(e){ alert('取消') }) </script> ~~~ ##### **4\. 事件的代理/委托** * 事件委托是指 1. 将**事件绑定到目标元素的父元素**上 2. 利用**冒泡机制**触发该事件后,将target重新赋值为**e.target子元素** 3. 再根据子元素的特征采取相应的动作和响应 * 优点: * 可以减少事件注册绑定,节省大量内存占用 * 可以将事件应用于动态添加的子元素上 * 但使用不当会造成事件在不应该触发时触发 ~~~ <div id="div1"> <a href="http://imooc.com" id="link1">我是1号</a> <a href="http://imooc.com" id="link2">我是2号</a> <a href="http://imooc.com" id="link3">我是3号</a> <a href="http://imooc.com" id="link4">我是4号</a> </div> <script type="text/javascript"> function bindEvent(elem,type,fn){ elem.addEventListener(type,fn) } var div1 = document.getElementById('div1') bindEvent(div1,'click',function(e){ e.preventDefault(); var target = e.target if (target.nodeName.toUpperCase()==="A"){ alert(target.innerHTML) } }) </script> ~~~ ##### 5\. 对于一个下拉加载图片/标签的页面,如何给每个图片/标签绑定事件? * 这是**事件代理的典型应用场景** * 如下是一个对于走代理和非代理都适用的事件封装函数 * 原理 * 判断最后一个参数是否存在,存在则是代理,不存在则把selector参数赋值给它且把selector置为空 * 绑定事件,如果selector存在,就把target重新赋值为**e.target子元素** * 使用[**matches**](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/matches)判断子元素是否被指定的选择器字符串选择,是的话直接使用**call**函数修改this指向新的**target** ~~~ function bindEvent(elem,type,selector,fn){ if(fn == null){ fn = selector selector = null } elem.addEventListener(type,function(e){ if(selector){ target = e.target if (target.matches(selector)){ fn.call(target, e) //修改this指向 } } else { fn(e) } }) } var div1 = document.getElementById('div1') bindEvent(div1,'click','a',function(e){ e.preventDefault(); console.log(this.innerHTML) //走代理,这里的this是target,也就是a标签 }) var p1 = document.getElementById('p1') bindEvent(p1,'click',function(e){ console.log(p1.innerHTML) //不走代理 }) ~~~ #### 1-3-6. 不支持冒泡的三大事件 * UI事件 * load * unload * scroll * resize * 焦点事件 * blur * focus * 鼠标事件 * mouseleave * mouseenter ### 1-4. 其他 #### 1-4-1. window和document对象 #### 1-4-1. window和document对象 * window * Window 对象表示**当前浏览器的窗口**,是 JavaScript 的**顶级对象**【Vvip】。 * 我们创建的所有对象、函数、变量都是 Window 对象的成员。 * Window 对象的方法和属性是在**全局范围**内有效的。 * document * Document 对象是 **HTML 文档的根节点与所有其他节点**(元素节点,文本节点,属性节点, 注释节点) * Document 对象使我们可以通过脚本对 HTML 页面中的所有元素进行访问 * Document 对象是 Window 对象的一部分,即 **window.document** #### 1-4-2. IE 的事件处理和 W3C 的事件处理有哪些区别? * 绑定事件 * W3C: targetEl.addEventListener('click', handler, false); * IE: targetEl.attachEvent('onclick', handler); * 删除事件 * W3C: targetEl.removeEventListener('click', handler, false); * IE: targetEl.detachEvent(event, handler); * 事件对象 * W3C: var e = arguments.callee.caller.arguments\[0\] * IE: window.event * 事件目标 * W3C: e.target * IE: window.event.srcElement * 阻止事件默认行为 * W3C: e.preventDefault() * IE: window.event.returnValue = false' * 阻止事件传播 * W3C: e.stopPropagation() * IE: window.event.cancelBubble = true **兼容写法示例** ~~~ function cancelHandler(event){ var event = event || window.event; //用于IE if(event.preventDefault) event.preventDefault(); //标准技术 if(event.returnValue) event.returnValue = false; //IE return false; //用于处理使用对象属性注册的处理程序 } ~~~ #### 1-4-3. DOM 元素的 dom.getAttribute(propName)和 dom.propName 有什么区别和联系? * **访问特性属性**的不同 * dom.getAttribute(),是**标准 DOM 操作文档元素属性**的方法,具有通用性可在任意文档上使用,返回元素在源文件中设置的属性 * dom.propName 通常是在 HTML 文档中**访问特定元素的特性**,浏览器解析元素后生成对应对象(如 a 标签生成 HTMLAnchorElement),这些对象的特性会根据特定规则结合属性设置得到,对于没有对应特性的属性,只能使用 getAttribute 进行访问 * **返回值类型**的不同 * dom.getAttribute()返回值是源文件中设置的值,类型是**字符串或者 null**(有的实现返回"") * dom.propName 返回值可能是**字符串、布尔值、对象、undefined** 等 * 大部分 attribute 与 property 是一一对应关系,修改其中一个会影响另一个,如 id,title 等属性;但是其余的修改attribute时property同步但修改property时attribute不同步。 * 一些布尔属性`<input hidden/>`的检测设置需要 hasAttribute 和 removeAttribute 来完成,或者设置对应 property * 像`<a href="../index.html">link</a>`中 href 属性,转换成 property 的时候需要通过转换得到完整 URL * 一些 attribute 和 property 不是一一对应如:form 控件中`<input value="hello"/>`对应的是 defaultValue,修改或设置 value property 修改的是控件当前值,setAttribute 修改 value 属性不会改变 value property #### 1-4-4. JS获取dom的CSS样式 ~~~ function getStyle(obj, attr){ if(obj.currentStyle){ return obj.currentStyle[attr]; } else { return window.getComputedStyle(obj, false)[attr]; } } ~~~ #### 1-4-5. focus/blur与focusin/focusout的区别与联系 1. focus/blur不冒泡,focusin/focusout冒泡 2. focus/blur兼容性好,focusin/focusout在除FireFox外的浏览器下都保持良好兼容性,如需使用事件托管,可考虑在FireFox下使用事件捕获elem.addEventListener('focus', handler, true) #### 1-4-6. mouseover/mouseout与mouseenter/mouseleave的区别与联系 1. mouseover/mouseout是标准事件,**所有浏览器都支持**;mouseenter/mouseleave是IE5.5引入的特有事件后来被DOM3标准采纳,现代标准浏览器也支持 2. mouseover/mouseout是**冒泡**事件;mouseenter/mouseleave**不冒泡**。需要为**多个元素监听鼠标移入/出事件时,推荐mouseover/mouseout托管,提高性能** 3. 标准事件模型中event.target表示发生移入/出的元素,**vent.relatedTarget**对应移出/如元素;在老IE中event.srcElement表示发生移入/出的元素,**event.toElement**表示移出的目标元素,**event.fromElement**表示移入时的来源元素 ## 2\. BOM API BOM 是**Browser Object Model**的缩写, 简称浏览器对象模型。 主要处理浏览器窗口和框架,描述了与浏览器进行交互的方法和接口, 可以对浏览器窗口进行访问和操作, 譬如可以弹出新的窗口, 回退历史记录, 获取 url…… ### 2-1. BOM与DOM的关系? 1. javacsript 是通过访问 BOM 对象来**访问、 控制、 修改**浏览器 2. BOM 的 **window 包含了 document对象**, 因此通过 window 对象的 document 属性就可以访问、检索、 修改文档内容与结构。 3. **document** 对象又是 **DOM 模型**的**根节点**。 4. 因此, BOM 包含了 DOM, 浏览器提供出来给予访问的是 BOM 对象, 从 BOM 对象再访问到 DOM 对象, 从而 js 可以操作浏览器以及浏览器读取到的文档 ### 2-2. BOM 对象包含哪些内容?(好累威尼斯) * **History** 包含了浏览器窗口访问过的 URL。 * **Location** 包含了当前 URL 的信息。 * **Window** JavaScript 层级中的顶层对象, 表示浏览器窗口。 * **Navigator** 包含客户端浏览器的信息。 * **Screen** 包含客户端显示屏的信息。 #### 2-2-1. **History 对象** History 对象包含用户(在浏览器窗口中) 访问过的 URL | 方法/属性 | 描述 | | --- | --- | | length | 返回浏览器历史列表中的 URL 数量。 | | **back()** | 加载 history 列表中的前一个 URL。 | | **forward()** | 加载 history 列表中的下一个 URL。 | | go() | 加载 history 列表中的某个具体页面 | #### 2-2-2. **Location 对象** Location 对象包含有关当前 URL 的信息。 | 属性 | 描述 | | --- | --- | | hash | 设置或返回从井号 (#) 开始的 URL(锚) 。 | | **host** | 设置或返回主机名和当前 URL 的端口号。 | | hostname | 设置或返回当前 URL 的主机名。 | | **href** | 设置或返回**完整的 URL**。 | | **pathname** | 设置或返回当前 URL 的路径部分。 | | port | 设置或返回当前 URL 的端口号。 | | **protocol** | 设置或返回当前 URL 的协议。 | | **search** | 设置或返回从问号 (?) 开始的 URL(查询部分) 。 | | 方法 | 描述 | | --- | --- | | assign() | 加载新的文档。 | | reload(‘force’) | 重新加载当前文档。参数可选,不填或填 false 则取浏览器缓存的文档 | | replace() | 用新的文档替换当前文档。 | #### 2-2-3. **Window 对象** Window 对象表示一个浏览器窗口或一个框架。 在客户端 JavaScript 中,**Window 对象** 是**全局**对象,所有的表达式都在当前的环境中计算。 例如,可以只写 **document**, 而 不必写 window.document。 | 属性 | 描述 | | --- | --- | | closed | 返回窗口是否已被关闭。 | | defaultStatus | 设置或返回窗口状态栏中的默认文本。 (仅 Opera 支持) | | document | 对 Document 对象的只读引用。 请参阅 Document 对象。 | | history | 对 History 对象的只读引用。 请参数 History 对象。 | | innerheight | 返回窗口的文档显示区的高度。 | | innerwidth | 返回窗口的文档显示区的宽度。 | | length | 设置或返回窗口中的框架数量。 | | location | 用于窗口或框架的 Location 对象。 请参阅 Location 对象。 | | name | 设置或返回窗口的名称。 | | Navigator | 对 Navigator 对象的只读引用。 请参数 Navigator 对象。 | | opener | 返回对创建此窗口的窗口的引用。 | | outerheight | 返回窗口的外部高度。 | | outerwidth | 返回窗口的外部宽度。 | | pageXOffset | 设置或返回当前页面相对于窗口显示区左上角的 X 位置。 | | pageYOffset | 设置或返回当前页面相对于窗口显示区左上角的 Y 位置。 | | parent | 返回父窗口。 | | Screen | 对 Screen 对象的只读引用。 请参数 Screen 对象。 | | self | 返回对当前窗口的引用。 等价于 Window 属性。 | | status | 设置窗口状态栏的文本。 (默认只支持 Opera) | | top | 返回最顶层的先辈窗口。 | | window | window 属性等价于 self 属性, 它包含了对窗口自身的引用。 | | screenLeft/ screenTop/ screenX/ screenY | 只读整数。声明了窗口的左上角在屏幕上的的 x 坐标和 y 坐标。 IE、 Safari、 Chrome 和 Opera 支持 screenLeft 和 screenTop, 而 Chrome、 Firefox 和 Safari 支持 screenX 和 screenY。 | | 方法 | 描述 | | --- | --- | | alert() | 显示带有一段消息和一个确认按钮的警告框。 | | blur() | 把键盘焦点从顶层窗口移开。 | | confirm() | 显示带有一段消息以及确认按钮和取消按钮的对话框。 | | createPopup() | 创建一个弹出窗口。 只有 ie 支持(不包括 ie11) | | focus() | 把键盘焦点给予一个窗口。 | | moveBy() | 可相对窗口的当前坐标把它移动指定的像素。 | | moveTo() | 把窗口的左上角移动到一个指定的坐标。 | | open() | 打开一个新的浏览器窗口或查找一个已命名的窗口。 window.open(URL,name,features,replace) | | print() | 打印当前窗口的内容。 | | prompt() | 显示可提示用户输入的对话框。 | | resizeBy() | 按照指定的像素调整窗口的大小。 | | resizeTo() | 把窗口的大小调整到指定的宽度和高度。 | | scrollBy() | 按照指定的像素值来滚动内容。 | | scrollTo() | 把内容滚动到指定的坐标。 | | setInterval() | 按照指定的周期(以毫秒计) 来调用函数或计算表达式。 | | setTimeout() | 在指定的毫秒数后调用函数或计算表达式。 | | clearInterval() | 取消由 setInterval() 设置的 timeout。 | | clearTimeout() | 取消由 setTimeout() 方法设置的 timeout。close() 关闭浏览器窗口 | #### 2-2-4. **Navigator 对象** Navigator 对象包含的属性描述了正在使用的浏览器。 可以使用这些属性进行平台专用的配置。 虽然这个对象的名称显而易见的是 Netscape 的 Navigator 浏览器, 但其他实现了 JavaScript 的浏览器也支持这个对象。 | 属性 | 描述 | | --- | --- | | appCodeName | 返回浏览器的代码名。 以 Netscape 代码为基础的浏览器中, 它的值是 "Mozilla"。Microsoft 也是 | | appMinorVersion | 返回浏览器的次级版本。 (IE4、 Opera 支持) | | appName | 返回浏览器的名称。 | | appVersion | 返回浏览器的平台和版本信息。 | | browserLanguage | 返回当前浏览器的语言。 (IE 和 Opera 支持)cookieEnabled 返回指明浏览器中是否启用 cookie 的布尔值。 | | cpuClass | 返回浏览器系统的 CPU 等级。 (IE 支持) | | onLine | 返回指明系统是否处于脱机模式的布尔值。 | | platform | 返回运行浏览器的操作系统平台。 | | systemLanguage | 返回当前操作系统的默认语言。 (IE 支持) | | **userAgent** | 返回由客户机发送服务器的 user-agent 头部的值。 | | userLanguage | 返回操作系统设定的自然语言。 (IE 和 Opera 支持) | | plugins | 返回包含客户端安装的所有插件的数组 | | 方法 | 描述 | | --- | --- | | javaEnabled() | 规定浏览器是否支持并启用了 Java。 | | taintEnabled() | 规定浏览器是否启用数据污点 (data tainting)。 | #### 2-2-5. Screen 对象 Screen 对象包含有关客户端显示屏幕的信息。 每个 Window 对象的 screen 属性都引用一个 Screen 对象。 Screen 对象中存放着有关显示**浏览器屏幕**的信息。 JavaScript 程序将利用这些信息来优化它们的输出, 以达到用户的显示要求。 例如,一个程序可以根据显示器的尺寸选择使用大图像还是使用小图像,它还可以根据显示器的颜色深度选择使用 16 位色还是使用 8 位色的图形。 另外,JavaScript 程序还能根有关屏幕尺寸的信息将新的浏览器窗口定位在屏幕中间。 | 属性 | 描述 | | --- | --- | | availHeight | 返回显示屏幕的高度 (除 Windows 任务栏之外)。 | | availWidth | 返回显示屏幕的宽度 (除 Windows 任务栏之外)。 | | bufferDepth | 设置或返回调色板的比特深度。 (仅 IE 支持)colorDepth 返回目标设备或缓冲器上的调色板的比特深度。 | | deviceXDPI | 返回显示屏幕的每英寸水平点数。 (仅 IE 支持) | | deviceYDPI | 返回显示屏幕的每英寸垂直点数。 (仅 IE 支持) | | fontSmoothingEnabled | 返回用户是否在显示控制面板中启用了字体平滑。 (仅 IE 支持) | | height | 返回显示屏幕的高度。 | | logicalXDPI | 返回显示屏幕每英寸的水平方向的常规点数。 (仅 IE 支持) | | logicalYDPI | 返回显示屏幕每英寸的垂直方向的常规点数。 (仅 IE 支持) | | pixelDepth | 返回显示屏幕的颜色分辨率(比特每像素) 。 | | updateInterval | 设置或返回屏幕的刷新率。 (仅 IE11 以下支持) | | width | 返回显示器屏幕的宽度。 | ### 2-3. BOM 对象应用 #### 2-3-1. 检测浏览器版本版本有哪些方式? * 判断机型方法一:根据**navigator.userAgent** ~~~ var ua = navigator.userAgent var isChrome = ua.toLowerCase().indexOf('chrome') //注意转小写 console.log (isChrome) //true则是chrome浏览器 //类似地,安卓和ios也是一样的判断方法 ~~~ * 判断机型方法二:根据**screen对象** ~~~ console.log(screen.width) console.log(screen.height) ~~~ * 方法三:根据 window 对象的成员 // 'ActiveXObject' in window #### 2-3-2. offsetWidth/offsetHeight,clientWidth/clientHeight 与 scrollWidth/scrollHeight 的区别 [https://www.cnblogs.com/laixiangran/p/8849887.html](https://www.cnblogs.com/laixiangran/p/8849887.html) * offsetWidth/offsetHeight 返回值包含 **content + padding + border**,效果与 e.getBoundingClientRect()相同 * clientWidth/clientHeight 返回值只包含 **content + padding**,如果有滚动条,也**不包含滚动条** * scrollWidth/scrollHeight 返回值包含 **content + padding + 溢出内容**的尺寸 #### 2-3-3. pageX/Y,offsetX/Y,clientX/Y,screenX/Y 区分什么是**“客户区坐标”、“页面坐标”、“屏幕坐标”** * 客户区坐标 * 鼠标指针在可视区中的水平坐标(clientX)和垂直坐标(clientY) * clientX/Y获取到的是**触发点相对浏览器可视区域左上角距离**,不随页面滚动而改变。 * 页面坐标 * 鼠标指针在页面布局中的水平坐标(pageX)和垂直坐标(pageY) * pageX/Y获取到的是**触发点相对文档区域左上角距离**,会随着页面滚动而改变 * 屏幕坐标 * 设备物理屏幕的水平坐标(screenX)和垂直坐标(screenY) * screenX/Y获取到的是**触发点相对显示器屏幕左上角的距离**,不随页面滚动而改变。 * 偏移坐标 * offsetX/Y获取到是**触发点相对被触发dom的左上角距离**,不过左上角基准点在不同浏览器中有区别,其中在IE中以内容区左上角为基准点**不包括边框**,如果触发点在边框上会返回负值,而**chrome中以边框**左上角为基准点 #### <mark>2-3-4. JS实现鼠标拖拽</mark> * [利用clientX和offsetX实现](https://blog.csdn.net/qq_37746973/article/details/80748879) * 实现原理: 1. 在允许拖拽的节点元素上,监听mousedown(按下鼠标按钮)事件,鼠标按下后,事件触发 2. 监听mousemove(鼠标移动)事件,修改克隆出来的节点的坐标,实现节点跟随鼠标的效果 3. 监听mouseup(放开鼠标按钮)事件,将原节点克隆到鼠标放下位置的容器里,将绑定的mousemove和mouseup事件清除掉,拖拽完成。 #### 2-3-5. JS实现监听移动端上下左右滑动事件 * 在touchstart时记录下**pageX**和**pageY**;在touchend时再次记录; * 二者作差,若大于某个正阈值就是向下/右;若小于某个负阈值就是向上/左。 [链接](https://www.jianshu.com/p/84e995404b96) ![img](https://img.kancloud.cn/6d/2a/6d2a57f4f437e529fea3402960905283_770x609.png) ## 3\. 网络请求 API ### <mark>3-1. AJAX请求</mark> [https://www.cnblogs.com/chaoyuehedy/p/10991132.html](https://www.cnblogs.com/chaoyuehedy/p/10991132.html) [Ajax | MDN](https://developer.mozilla.org/zh-CN/docs/Web/Guide/AJAX) * AJAX是 **异步** 的JavaScript和XML(Asynchronous JavaScript And XML)。简单点说,就是**使用 XMLHttpRequest 对象与服务器通信**。 它可以使用JSON,XML,HTML和text文本等格式发送和接收数据。AJAX最吸引人的就是它的“异步”特性,也就是说他可以**在不重新刷新页面的情况下与服务器通信**,**交换数据,或更新页面** #### 3-1-1. 手写一个AJAX * 完整示例 ~~~ function ajax(url,cb){ let xhr; if(window.XMLHttpRequest){ xhr = new XMLHttpRequest(); }else{ xhr = ActiveXObject("Microsoft.XMLHTTP"); } xhr.onreadystatechange = function() { if(xhr.readyState == 4 && xhr.status == 200){ cb(xhr.responseText); } } xhr.open('GET', url, true) ;//第三个参数是async xhr.send() } ~~~ * 创建 XMLHttpRequest 对象(考虑兼容性) ~~~ if (window.XMLHttpRequest) { // Mozilla, Safari, IE7+ ... xhr = new XMLHttpRequest(); } else if (window.ActiveXObject) { // IE 6 and older xhr = new ActiveXObject("Microsoft.XMLHTTP"); } ~~~ * 绑定onreadystatechange 事件 ~~~ xhr.onreadystatechange = function(){ // Process the server response here. }; ~~~ * 向服务器发送请求 ~~~ xhr.open('GET', 'http://www.example.org/some.file', true); xhr.send(); ~~~ #### 3-1-2. AJAX相关API ##### 1\. xhr.readyState的值 * 0 (未初始化) or (请求还未初始化,未调用send方法) * 1 (正在加载) or (已建立服务器链接,已调用send方法) * 2 (加载成功) or (接收到响应,已完成send方法) * 3 (交互) or (正在处理响应,解析相应内容) * 4 (完成) or (请求已完成并且响应已准备好,可以在客户端调用了) ##### 2\. xhr.readyState的值 和HTTP协议的状态码一致 * [1XX 指示信息](https://www.kancloud.cn/book/lujiaobei/front/edit#1XX__92) * [2XX 请求成功](https://www.kancloud.cn/book/lujiaobei/front/edit#2XX__94) * [3XX 重定向](https://www.kancloud.cn/book/lujiaobei/front/edit#3XX__99) * [4XX 客户端错误](https://www.kancloud.cn/book/lujiaobei/front/edit#4XX__106) * [5XX 服务器错误](https://www.kancloud.cn/book/lujiaobei/front/edit#5XX__111) ##### 3\. 访问服务端返回的数据 * xhr.responseText * 服务器以文本字符的形式返回 * xhr.responseXML * 以 XMLDocument 对象方式返回,之后就可以使用JavaScript来处理 ##### 4\. GET/POST [文档](https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/open) * ~~~ xhr.open(method, url, async); ~~~ * 第一个参数:要使用的HTTP方法,比如「GET」、「POST」、「PUT」、「DELETE」等。对于非HTTP(S) URL被忽略。 第二个参数: 提交的路径,如果是GET传参send(null); 可以写为这样。 第三个参数:是以异步(默认)的方式还是同步的方式提交。 * **GET请求**如果不设置响应头`Cache-Control: no-cache`,浏览器将会把响应缓存下来而且再也无法重新提交请求。也可以添加一个总是不同的 GET 参数,比如时间戳或者随机数 (详情见[bypassing the cache](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache)) ~~~ xhr.open('GET', 'http://www.example.org/some.file', true); xhr.send();//GET不传参 ~~~ * **POST请求**则需要设置`RequestHeader`告诉后台传递内容的**编码方式以及在send方法里传入对应的值** ~~~ xhr.open("POST", url, true); xhr.setRequestHeader("Content-Type": "application/x-www-form-urlencoded"); xhr.send("key1=value1&key2=value2"); ~~~ #### 3-1-3. Ajax与cookie 【头条】 * Ajax会自动**带上同源的cookie**,不会带上不同源的cookie * 可以通过前端设置**withCredentials为true**, 后端设置Header的方式让ajax自动带上不同源的cookie,但是这个属性对同源请求没有任何影响。会被自动忽略。 [withCredentials | MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/withCredentials) ~~~ var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://example.com/', true); xhr.withCredentials = true; xhr.send(null); ~~~ ### 3-2. AJAX、Axios、fecth #### 3-2-1. jQuery AJAX ~~~ $.ajax({ type: 'POST', url: url, data: data, dataType: dataType, success: function () {}, error: function () {} }); ~~~ * 优点: * Ajax是基于底层**XMLHttpRequest**对象,是我们最早使用的对象。 * 使用起来十分方便。 * 缺点: * 多个请求有先后关系的话,会出现**回调地狱**的问题。 * 基于XHR,但是XHR本身架构不太清晰。 * 需要特地引入**jQuery**(单独打包的话不能使用CDN)。 * 基于事件的异步模型不是很友好。 * 不太符合关注分离的思想和MVVM的框架(基于MVC)。 #### 3-2-2. Axios ~~~ axios({ method: 'post', url: '/user/12345', data: { firstName: 'Fred', lastName: 'Flintstone' } }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }); ~~~ * 优点: * 和ajax一样,也是基于**XMLHttpRequest** * 支持**Promise API**,处理异步更加方便 * **防止CSRF攻击**(因为会让每个请求都携带一个cookie的key值,而根据同源策略假冒的网站是拿不到这个key的,那么服务器就可以根据是不是有这个key去做对应的处理了) * 【重要】提供了一些[并发请求](https://www.cnblogs.com/zmdblog/p/11240226.html)的接口(利用Promise.all的原理) * 使用node.js创建HTTP请求 * 可以拦截[请求和响应](https://www.cnblogs.com/xbzhu/p/11810384.html) * 可以自动转换[JSON数据](https://www.cnblogs.com/daijinxue/p/8309476.html) * 转换请求和响应数据 * 取消请求 #### 3-2-3. fetch ~~~ try { let response = await fetch(url); let data = response.json(); console.log(data); } catch(e) { console.log("Oops, error", e); } ~~~ * 优点: * 符合关注分离,没有将输入、输出和用事件来跟踪的状态混杂在一个对象里 * 更好更方便的写法 * 更加底层,**提供的API丰富**(request, response) * **脱离了XHR**,是ES规范里新的实现方式 * 支持**Promise,async/await** * 缺点: * 1)fetcht**只对网络请求报错**,对400,500都当做成功的请求,需要封装去处理 * 2)fetch默认不会带cookie,**需要添加配置项,credentials** * 3)fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了量的浪费 * 4)fetch**没有办法原生监测请求的进度**,而XHR可以 * **fetch 为什么发送两次请求?** * 发送post请求的时候,总是发送2次,第一次状态码是204,第二次才成功 * **fetch的实现机制**:当发送跨域请求时,fetch会先发送一个OPTIONS请求,来确认服务器是否允许接收请求;服务器同意后(返回状态码204),才会发送真正的请求 ### 3-3. 其他 #### 3-3-1. 和后端API服务通信的方式有哪些 1. **ajax** 2. **websocket** 3. **SSE** 4. **服务器端渲染** #### 3-3-2. CDN加速 CDN(Content Delivery Network,内容分发网络)是构建在现有互联网基础之上的一层**智能虚拟网络**,通过在**网络各处部署节点服务器**,实现将源站内容分发至所有CDN节点,**使用户可以就近获得所需的内容**。CDN服务**缩短了用户查看内容的访问延迟**,提高了用户访问网站的响应速度与网站的可用性,解决了**网络带宽小、用户访问量大、网点分布不均**等问题。 ##### 加速原理 当用户访问使用CDN服务的网站时,本地DNS服务器通过CNAME方式将最终域名请求重定向到CDN服务。CDN通过一组预先定义好的策略(如内容类型、地理区域、网络负载状况等),将当时能够最快响应用户的CDN节点IP地址提供给用户,使用户可以以最快的速度获得网站内容。使用CDN后的HTTP请求处理流程如下: * CDN节点有缓存场景 1. 用户在浏览器输入要访问的网站域名,向本地DNS发起域名解析请求。 2. 域名解析的请求被发往网站授权DNS服务器。 3. [网站DNS服务器解析发现域名已经CNAME到了www.example.com.c.cdnhwc1.com](http://xn--dnscnamewww-qx9q749abqbk0ip1a360a5yggm6a3l4bxicez1iko8ax4tq2cn43h.example.com.c.cdnhwc1.com/)。 4. 请求被指向CDN服务。 5. **CDN对域名进行智能解析,将响应速度最快的CDN节点IP地址返回给本地DNS**。 6. 用户获取响应速度最快的CDN节点IP地址。 7. 浏览器在得到速度最快节点的IP地址以后,向CDN节点发出访问请求。 8. CDN节点将用户所需资源返回给用户。 * CDN节点无缓存场景 1. 用户在浏览器输入要访问的网站域名,向本地DNS发起域名解析请求。 2. 域名解析的请求被发往网站授权DNS服务器。 3. [网站DNS服务器解析发现域名已经CNAME到了www.example.com.c.cdnhwc1.com](http://xn--dnscnamewww-qx9q749abqbk0ip1a360a5yggm6a3l4bxicez1iko8ax4tq2cn43h.example.com.c.cdnhwc1.com/)。 4. 请求被指向CDN服务。 5. CDN对域名进行智能解析,将响应速度最快的CDN节点IP地址返回给本地DNS。 6. 用户获取响应速度最快的CDN节点IP地址。 7. 浏览器在得到速度最快节点的IP地址以后,向CDN节点发出访问请求。 8. **CDN节点回源站拉取用户所需资源**。 9. **将回源拉取的资源缓存至节点。** 10. 将用户所需资源返回给用户。 PS:**CNAME别名解析**是将域名指向一个网址(域名) ### <mark>3-4. 跨域</mark> #### 3-4-1. 同源策略 * 同源是什么(有一个不同就是跨域) * 端口相同 * 域名相同 * 协议相同 * 例子:[http://www.example.com/dir/page.html](http://www.example.com/dir/page.html) 这个网址,协议是http,[域名是www.example.com](http://xn--www-q33er8of2z.example.com/),端口是80 * 跨域/非同源限制范围 * **Cookie、LocalStorage 和 IndexDB** 无法读取 * **DOM 无法获得** * **AJAX 请求不能发送** * 目的 * 为了保证用户信息的安全,防止恶意的网站窃取数据 #### 3-4-2. 跨域 * 可以跨域的三个标签 * `<img src=xxx>` ,多用于打点统计,做统计的网站可能是其他域(有些网站会防盗链) * `<link href=xxx>`,可以用**CDN**,[CDN](https://www.kancloud.cn/book/lujiaobei/front/edit#CDN_1)也是其他域(BOOTCDN) * `<script src=xxx>`,可以用于**JSONP**处理跨域请求 * 跨域的注意事项 * 所有跨域请求都必须经过信息提供方允许 * 未经允许即可获取,那是浏览器同源策略出现了漏洞 #### 3-4-3. 跨域解决方案 * JSONP * CORS * Hash * postMessage * WebSoket ##### 1\. JSONP 浏览器上虽然有同源限制,但是一些**标签通过src地址来加载一些内容的时候浏览器是允许进行跨域请求的**。JSOP就是利用**script标签**可以跨域的条件,原理如下: * 创建一个**script标签**,这个script标签的**src就是请求的地址**; * 这个script标签插入到DOM中,浏览器就根据src地址访问服务器资源; * **返回的资源是一个文本**,但是因为是在script标签中,浏览器会执行它; * 而这个文本**恰好是函数调用的形式**,即函数名(数据),**浏览器**会把它当作JS代码来执行即**调用**这个函数; * 只要提前约定好这个函数名,并且这个函数存在于window对象中,就可以把数据传递给处理函数。 ~~~ <script> window.callback=function(data){ console.log(data) } </script> <script src="http://coding.com/api.js"></script> ~~~ 1.优点 1.1它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制,JSONP可以跨越同源策略; 1.2它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持 1.3在请求完毕后可以通过调用callback的方式回传结果。将回调方法的权限给了调用方。这个就相当于将controller层和view层终于分开了。我提供的jsonp服务只提供纯服务的数据,至于提供服务以 后的页面渲染和后续view操作都由调用者来自己定义就好了。如果有两个页面需要渲染同一份数据,你们只需要有不同的渲染逻辑就可以了,逻辑都可以使用同 一个jsonp服务。 2.缺点 2.1它只支持GET请求而不支持POST等其它类型的HTTP请求 2.2它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。 2.3jsonp在调用失败的时候不会返回各种HTTP状态码。 2.4缺点是安全性。万一假如提供jsonp的服务存在页面注入漏洞,即它返回的javascript的内容被人控制的。那么结果是什么?所有调用这个 jsonp的网站都会存在漏洞。于是无法把危险控制在一个域名下…所以在使用jsonp的时候必须要保证使用的jsonp服务必须是安全可信的。 ##### 2\. CORS * CORS跨域资源共享 * CORS(Cross-origin resource sharing)跨域资源共享 * 浏览器在请求一个跨域资源的时候,如果是跨域的Ajax请求,他会在**请求头中加一个`origin`字段**,但他是不知道这个资源**服务端是否允许跨域请求**的 * 然后浏览器会发送到服务端,如果服务器返回的头中**没有**`'Access-Control-Allow-Origin': '对应网址或* '`的话,那么浏览器就会把请求内容给忽略掉,并且在控制台报错 * CORS限制允许的请求方法 * GET * POST * HEAD允许的**Content-Type** * text/plain * multipart/form-data * application/x-www-form-ulencoded * 其他类型的请求方法和Content-Type需要通过**预请求验证**后然后才能发送 * CORS预请求 * 跨域资源共享标准新增了**一组 HTTP 首部字段**,允许服务器声明哪些源站有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求。 * **服务器在HTTP header中**加入**允许请求的方法和Content-Type**后,其他指定的方法和Content-Type就可以成功请求了 * ~~~ 'Access-Control-Allow-Headers': '允许Content-Type' 'Access-Control-Allow-Methods': '允许的请求方法' 'Access-Control-Max-Age': '预请求允许其他方法和类型传输的时间' ~~~ ![img](https://img.kancloud.cn/85/4c/854ca2bc41f122b3f1c9f4a08f0b3d67_618x130.png) ~~~ Access-Control-Allow-Origin: <origin> | * // 授权的访问源 Access-Control-Max-Age: <delta-seconds> // 预检授权的有效期,单位:秒 Access-Control-Allow-Credentials: true | false // 是否允许携带 cookie Cookie Access-Control-Allow-Methods: <method>[, <method>]* // 允许的请求动词 Access-Control-Allow-Headers: <field-name>[, <field-name>]* // 额外允许携带的请求头 Access-Control-Expose-Headers: <field-name>[, <field-name>]* // 额外允许访问的响应头 ~~~ 上面的预检请求并不是CORS请求的必须的请求过程,在一定的条件下并不需要发生预检请求。那么发生预检请求的条件是什么呢?根据[HTTP访问控制(CORS)](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS)介绍,其实发生预检请求的条件:**是否是简单请求**。简单请求则直接发送具体的请求而不会产生预检请求。具体来说如下: 满足下面的**所有条件**就不会产生预检请求,也就是该请求是简单请求: * **请求方法是`GET`、`POST`、`HEAD`其中任意一个** * **必须是下面定义对CORS安全的首部字段集合,不能是集合之外的其他首部字段。** Accept、Accept-Language、Content-Language、Content-Type、DPR、Downlink、Save-Data、Viewport-Width、Width。 * **Content-Type的值必须是`text/plain`、`multipart/form-data`、`application/x-www-form-urlencoded`中任意一个值** 满足上面所有的条件才不会发送预检请求,在实际项目中我们的请求格式可能是`application/json`格式编码,或者使用自定义请求头都会触发CORS的预检请求。 所以,在项目中是否会触发CORS的预检请求要做到心中有数。 * <mark>CORS 携带cookie * 此外在客户端浏览器中,我们仍然需要对 XMLHttpRequest 设置其withCredentials参数,才能实现携带 Cookie 的目标。示例代码如下: ~~~ var xhr = new XMLHttpRequest(); xhr.withCredentials = true; ~~~ * 注意,为了安全,标准里不允许**Access-Control-Allow-Origin: \***,必须指定明确的、与请求网页一致的域名。同时,Cookie 依然遵循“同源策略”,只有用目标服务器域名设置的 Cookie 才会上传,而且使用**document.cookie**也无法读取目标服务器域名下的 Cookie。 **如何让Jquery的AJAX使用CORS时携带Cookie?** * 跨域请求想要带上cookies必须在请求头里面加上**xhrFields: {withCredentials: true}设置**。 ~~~ $.ajax({ url: "http://localhost:8080/orders", type: "GET", xhrFields: { withCredentials: true }, success: function (data) { render(data); } }); ~~~ ##### 3\. Hash值跨域通信 * 背景:在页面A下提供iframe或frame嵌入了跨域的页面B * 容器页面 -> 嵌入页通信:**在A页面中改变B的url中的hash值**,B不会刷新,但是B可以用过`window.onhashchange`事件监听到hash变化('#') ##### 4\. postMessage通信 [WebWorker的实现和应用](https://www.kancloud.cn/book/lujiaobei/front/edit#WebWorker_166) ~~~ // 窗口A中 window.postMessage('data', 'http://A.com'); // 窗口B中 window.addEventListener('message', function(event) { console.log(event.origin); // http://A.com console.log(event.source); // A 对象window引用 console.log(event.data); // 数据 }) ~~~ ##### 5\. WebSocket跨域通信 [WebSocket的实现和应用](https://www.kancloud.cn/book/lujiaobei/front/edit#WebSocket_116) ~~~ var ws = new WebSocket('wss://echo.websoket.org') //这个是后端端口 ws.onopen = function(evt) { ws.send('some message') } ws.onmessage = function (evt) { console.log(evt.data); } ws.onclose = function(evt){ console.log('连接关闭'); } ~~~ ##### 6\. document.domain 该方式只能用于二级域名相同的情况下,比如[a.test.com](http://a.test.com/)和[b.test.com](http://b.test.com/)适用于该方式。 只需要给页面添加 document.domain = '[test.com](http://test.com/)' 表示二级域名都相同就可以实现跨域