<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                [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 指示信息](http://www.hmoore.net/book/lujiaobei/front/edit#1XX__92) * [2XX 請求成功](http://www.hmoore.net/book/lujiaobei/front/edit#2XX__94) * [3XX 重定向](http://www.hmoore.net/book/lujiaobei/front/edit#3XX__99) * [4XX 客戶端錯誤](http://www.hmoore.net/book/lujiaobei/front/edit#4XX__106) * [5XX 服務器錯誤](http://www.hmoore.net/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](http://www.hmoore.net/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的實現和應用](http://www.hmoore.net/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的實現和應用](http://www.hmoore.net/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/)' 表示二級域名都相同就可以實現跨域
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看