整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          JavaScript 中問號的三種用法 ??和?.以及?: 的您知道嗎?




          近看了一些關于JavaScript的測試腳本,覺得JS 中問號的用法還是蠻有意思的,于是做了一下總結,在這里分享給大家!JS中的問號大概有三種用法,分別是:空值合并操作符、可選鏈操作符和三目運算。

          問號問號(??)

          空值合并操作符??是一個邏輯操作符,當左側的操作數為 null 或者 undefined 時,返回其右側操作數,否則返回左側操作數。

          例如

          console.log(null ?? "xx")
          輸出 xx
          console.log(1 ?? "xx")
          輸出 1

          問號點 (?.)

          可選鏈操作符(?.)可選鏈操作符允許讀取位于連接對象鏈深處的屬性的值,而不必明確驗證鏈中的每個引用是否有效。 使用它的好處是引用為null 或者 undefined的情況下不會引起錯誤。

          語法:obj?.prop obj?.[expr] arr?.[index] func?.(args)

          例如

          var obj={a:{b:1}}
          console.log(obj?.a?.b)
          輸出1
          console.log(obj?.a?.c)
          輸出 undefined

          問號冒號(?: )

          這是三目運算,具體表達式是(condition ? exprIfTrue : exprIfFalse)

          該表達式的含義是 條件condition是真,則執行exprIfTrue ,否則執行exprIfFalse

          舉個例子大家就懂了

          var n = 10;
          console.log((n >= 11) ? "a" : "b");
          輸出b
          當 var n = 12;
          輸出a

          如果您還知道哪些JS 中關于問號的特殊用法歡迎留言討論。如果文章幫到了您,勞煩點贊轉發!

          原理

          事件高頻觸發后,n秒內函數只會執行一次,若n秒內事件再次觸發,則重新計時,總之就是要等觸發完事件 n 秒內不再觸發事件,函數才執行

          代碼實現

          function debounce(callback, wait) {
              let timer
              return function (...args) {
                  clearTimeout(timer)
                  timer = setTimeout(() => {
                      callback.call(this,args)
                  },wait)
              }
          }
          
          // 使用
          document.body.addEventListener('mousemove',debounce((e)=>{
            console.log(this,e,'mousemove-debounce')
          },1000))
          

          節流

          原理

          如果事件持續觸發,在指定時間內,只執行一次事件

          代碼實現

          時間戳方式

          // 時間戳方式
          /**
          使用時間戳,當觸發事件的時候,我們取出當前的時間戳,然后減去之前的時間戳(最一開始值設為 0 ),
          如果大于設置的時間周期,就執行函數,然后更新時間戳為當前的時間戳,如果小于,就不執行。
          */
          function throttle(callback, wait) {
              let start = 0
              return function(...args) {
                  const now = +new Date()
                  if(now-start >= wait ) {
                      callback.call(this,args)
                      start = now
                  }
          
              }
          }
          //使用
          const cb = throttle(function(e){
              console.log(this)
          },1000)
          document.body.addEventListener('mousemove',()=>{
              cb.call({name:'張三'})
          },1000)
          // {name: '張三'}
          

          定時器方式

          // 定時器方式
          /**
           * 
           * 當觸發事件的時候,我們設置一個定時器,再觸發事件的時候,如果定時器存在,就不執行,
           * 直到定時器執行,然后執行函數,清空定時器。
           */
          function throttle(callback, wait) {
              let timer
              return function(...args) {
                  if(!timer) {
                      timer = setTimeout(()=>{
                          timer = null
                          callback.call(this,args)
                      },wait)
                  }
              }
          }
          
          const cb = throttle(function(e){
              console.log(this)
          },1000)
          document.body.addEventListener('mousemove',()=>{
              cb.call({name:'張三'})
          },1000)
          // {name: '張三'}
          

          模擬new運算符

          new 運算符創建一個用戶定義的對象類型的實例或具有構造函數的內置對象的實例。

          原理

          • 新建一個空對象
          • 鏈接到原型
          • 綁定this
          • 返回該對象

          代碼實現

          function myNew() {
          // 1.新建一個空對象
          let obj = {}
          // 2.獲得構造函數
          let con = [].shift.call(arguments)
          // 3.鏈接原型,實例的 __proto__ 屬性指向構造函數的 prototype
          obj.__proto__ = con.prototype
          // 4.綁定this,執行構造函數
          let res = con.apply(obj, arguments)
          // 5.返回新對象
          return typeof res === 'object' ? res : obj
          }
          
          function Person(name) {
              this.name = name
          }
          let person = myNew(Person,'nanjiu')
          console.log(person) //{name: "nanjiu"}
          console.log(typeof person === 'object') //true
          console.log(person instanceof Person) // true
          

          模擬instanceof

          instanceof 用于檢測構造函數的prototype是否在實例的原型鏈上,需要注意的是instanceof只能用來檢測引用數據類型,對于基本數據檢測都會返回false

          原理

          通過循環檢測實例的__proto__屬性是否與構造函數的prototype屬性相等

          代碼實現

          /**
           * instanceof 用于檢測構造函數的prototype是否在實例的原型鏈上
           */
          function myInstanceof(left, right) {
              // 先排除基本數據類型
              if(typeof left !== 'object' || left === null) return false
              let proto = left.__proto__
              while(proto) {
                  if(proto === right.prototype) return true
                  proto = proto.__proto__
              }
              return false
          }
          
          function Person() {}
          let person = new Person()
          console.log(myInstanceof(person,Person)) // true
          

          模擬Function.prototype.apply()

          apply() 方法調用一個具有給定this值的函數,以及以一個數組(或類數組對象)的形式提供的參數。

          Function.prototype.myApply = function(context) {
              var context = context || window // 獲取需要綁定的this
              context.fn = this // 獲取需要改變this的函數
              const arg = arguments[1] // 獲取傳遞給函數的參數
          
              if(!(arg instanceof Array)) {
                  throw Error('參數需要是一個數組')
              }
              const res = context.fn(...arg) // 執行函數
              delete context.fn // 刪除該方法
              return res // 返回函數返回值
          }
          function say(a,b,c) {
              console.log(this.name,a,b,c)
          }
          say.myApply({name:'nanjiu'},[1,2,3]) //nanjiu 1 2 3
          say.apply({name:'nanjiu'},[1,2,3]) //nanjiu 1 2 3
          

          模擬Function.prototype.call()

          call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數。

          Function.prototype.myCall = function(context) {
              var context = context || window // 獲取需要改變的this
              context.fn = this // 獲取需要改變this的函數
              const args = [...arguments].slice(1) // 獲取參數列表
              const res = context.fn(...args) // 將參數傳給函數并執行
              delete context.fn // 刪除該方法
              return res // 返回函數返回值
          }
          
          function say(a,b,c) {
              console.log(this.name,a,b,c)
          }
          say.myCall({name:'nanjiu'},1,2,3) //nanjiu 1 2 3
          say.call({name:'nanjiu'},1,2,3) //nanjiu 1 2 3
          

          模擬Function.prototype.bind()

          bind() 方法創建一個新的函數,在 bind() 被調用時,這個新函數的 this 被指定為 bind() 的第一個參數,而其余參數將作為新函數的參數,供調用時使用。

          Function.prototype.myBind = function(context) {
              var context = context || window //獲取需要改變的this
              context.fn = this  // 獲取需要改變this的函數
          
              //獲取函數參數
              const args = [...arguments].slice(1)
              // 與apply,call不同的是這里需要返回一個函數
              return () => {
                  return context.fn.apply(context,[...args])
              }
          
          }
          
          function say(a,b,c) {
              console.log(this.name,a,b,c)
          }
          say.bind({name: 'nanjiu'},1,2,3)() //nanjiu 1 2 3
          say.myBind({name: 'nanjiu'},1,2,3)() //nanjiu 1 2 3
          

          模擬Array.prototype.forEach()

          forEach() 方法對數組的每個元素執行一次給定的函數,無返回值。

          語法

          arr.forEach(callback(currentValue [, index [, array]])[, thisArg])
          

          參數

          • callback
          • 為數組中每個元素執行的函數,該函數接收一至三個參數:currentValue數組中正在處理的當前元素。index 可選數組中正在處理的當前元素的索引。array 可選forEach() 方法正在操作的數組。
          • thisArg 可選
          • 可選參數。當執行回調函數 callback 時,用作 this 的值。

          代碼實現

          Array.prototype.myForEach = function(callback, context) {
              const arr = this // 獲取調用的數組
              const len = arr.length || 0
          
              let index = 0  // 數組下標
              while(index < len) {
                  callback.call(context ,arr[index], index)
                  index++
              }
          }
          
          let arr = [1,2,3]
          arr.forEach((item,index) => {
              console.log(`key: ${index} - item: ${item}`)
          })
          console.log('----------')
          arr.myForEach((item,index) => {
              console.log(`key: ${index} - item: ${item}`)
          })
          /**
           * key: 0 - item: 1
          key: 1 - item: 2
          key: 2 - item: 3
          ----------
          key: 0 - item: 1
          key: 1 - item: 2
          key: 2 - item: 3
           */
          

          模擬Array.prototype.map()

          map() 方法創建一個新數組,其結果是該數組中的每個元素是調用一次提供的函數后的返回值。

          語法

          var new_array = arr.map(function callback(currentValue[, index[, array]]) {
           // Return element for new_array 
          }[, thisArg])
          

          參數

          callback

          生成新數組元素的函數,使用三個參數:

          • currentValue
          • 數組中正在處理的當前元素。
          • index可選
          • 數組中正在處理的當前元素的索引。
          • array可選
          • map 方法調用的數組。

          thisArg可選

          執行 callback 函數時值被用作this。

          代碼實現

          /**
           * map() 方法創建一個新數組,其結果是該數組中的每個元素是調用一次提供的函數后的返回值。
           */
          Array.prototype.myMap = function(callback, context) {
              const arr = this,res = []
              const len = arr.length || 0
              let index = 0
              while(index < len) {
                  res.push(callback.call(context, arr[index], index))
                  index ++
              }
              return res  // 與forEach不同的是map有返回值
          }
          const arr = [1,2,3]
          let res1 = arr.map((item,index) => {
              return `k:${index}-v:${item}`
          })
          let res2 = arr.myMap((item,index) => {
              return `k:${index}-v:${item}`
          })
          console.log(res1) // [ 'k:0-v:1', 'k:1-v:2', 'k:2-v:3' ]
          console.log(res2) // [ 'k:0-v:1', 'k:1-v:2', 'k:2-v:3' ]
          

          模擬Array.prototype.filter()

          filter() 方法創建一個新數組, 其包含通過所提供函數實現的測試的所有元素。

          語法

          var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])
          

          參數

          callback

          用來測試數組的每個元素的函數。返回 true 表示該元素通過測試,保留該元素,false 則不保留。它接受以下三個參數:

          • element
          • 數組中當前正在處理的元素。
          • index可選
          • 正在處理的元素在數組中的索引。
          • array可選
          • 調用了 filter 的數組本身。

          thisArg可選

          執行 callback 時,用于 this 的值。

          代碼實現

          /**
           * `filter()` 方法創建一個新數組, 其包含通過所提供函數實現的測試的所有元素。 
           */
          
          Array.prototype.myFilter = function(callback, context) {
              const arr = this,res = []
              const len = arr.length
              let index = 0
              while(index < len) {
                  if(callback.call(context,arr[index],index)) {
                      res.push(arr[index])
                  }
                  index ++   
              }
              return res
          }
          
          const arr = [1,2,3]
          let res1 = arr.filter((item,index) => {
              return item<3
          })
          let res2 = arr.myFilter((item,index) => {
              return item<3
          })
          
          console.log(res1) // [ 1, 2 ]
          console.log(res2) // [ 1, 2 ]
          

          函數柯里化

          柯里化,英語:Currying(果然是滿滿的英譯中的既視感),是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,并且返回接受余下的參數而且返回結果的新函數的技術。

          先來理解一下什么是函數柯里化,上面文縐縐的內容可能不是那么容易理解,我們還是直接上代碼來理解吧

          // 假如有這樣一個函數
          function add (a,b,c) {
              console.log(a+b+c)
          }
          add(1,2,3) //6
          /**
           * 我們希望可以通過add(1,2)(3)或add(1)(2)(3)或add(1)(2,3)這樣調用也能夠得倒正確的計算結果
           這就是函數柯里化的簡單應用
           */
          

          代碼實現

          function curry(fn, curArgs) {
              const len = fn.length  // 需要柯里化函數的參數個數
              curArgs = curArgs || []
          
              return function() {
                  let args = [].slice.call(arguments) // 獲取參數
                  args = curArgs.concat(args) //拼接參數
                  // 基本思想就是當拼接完的參數個數與原函數參數個數相等才執行這個函數,否則就遞歸拼接參數
                  if(args.length < len) {
                      return curry(fn, args)
                  }else{
                      return fn.apply(this, args)
                  }
              }
          }
          
          let fn = curry(function(a,b,c){
              console.log([a,b,c])
          })
          fn(1,2,3) // [ 1, 2, 3 ]
          fn(1,2)(3) // [ 1, 2, 3 ]
          fn(1)(2,3) // [ 1, 2, 3 ]
          fn(1)(2)(3) // [ 1, 2, 3 ]
          

          類數組轉數組

          類數組是具有length屬性,但不具有數組原型上的方法。常見的類數組有arguments、DOM操作方法返回的結果。

          function translateArray() {
              //方法一:Array.from
              const res1 = Array.from(arguments)
              console.log(res1 instanceof Array, res1) // true [ 1, 2, 3 ]
          
              // 方法二:Array.prototype.slice.call
              const res2 = Array.prototype.slice.call(arguments)
              console.log(res2 instanceof Array, res2) // true [ 1, 2, 3 ]
          
              // 方法三:concate
              const res3 = [].concat.apply([],arguments)
              console.log(res3 instanceof Array, res3) // true [ 1, 2, 3 ]
          
              // 方法四:擴展運算符
              const res4 = [...arguments]
              console.log(res4 instanceof Array, res4) // true [ 1, 2, 3 ]
          }
          
          translateArray(1,2,3)
          

          實現深拷貝

          在拷貝的時候判斷一下屬性值的類型,如果是對象,遞歸調用深拷貝函數

          /**
           * 在拷貝的時候判斷一下屬性值的類型,如果是對象,遞歸調用深拷貝函數
           */
          
          function deepClone(obj, cache=new Map()) {
              // 基本數據類型直接返回
              if(typeof obj !== 'object' || obj === null) return obj
              // 防止循環引用
              const cacheTarget = cache.get(obj)
              // 已經存在就直接返回
              if(cacheTarget) return cacheTarget
          
              let newObj = obj instanceof Array ? [] : {} // 新建一個對象
          
              cache.set(obj, newObj)
              // 遍歷原對象
              for(let key in obj) {
                  if(obj.hasOwnProperty(key)) {
                      newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key]
                  }
              }
              return newObj
          }
          const obj = {
              name: '張三'
          }
          const obj1 = obj
          const obj2 = deepClone(obj)
          console.log(obj1===obj) //true
          console.log(obj2===obj) //false
          

          繼承的實現

          原型鏈繼承

          原型鏈繼承實現的原理就是將構造函數的原型設置為另一個構造函數的實例對象,這樣就可以繼承另一個原型對象的所有屬性和方法,可以繼續往上,最終形成原型鏈。

          function Parent1(name, age) {
              this.name = name,
              this.age = age
          }
          Parent1.prototype.say = function() {
              console.log(this.name)
          }
          function Child1(name) {
              this.name = name
          }
          Child1.prototype = new Parent1()
          Child1.prototype.constructor = Child1
          let child1 = new Child1('誠實',18)
          console.log(child1) //Child1 {name: '誠實'}
          child1.say() // 誠實
          

          缺點:

          • 當實現繼承后,另一個原型的實例屬性,變成了現在這個原型的原型屬性,然后該原型的引用類型屬性會被所有的實例共享,這樣繼承原型引用類型屬性的實例之間不再具有自己的獨特性了。
          • 在創建子類型的實例時,沒有辦法在不影響所有對象實例的情況下給超類型的構造函數中傳遞參數。

          構造函數繼承

          為了解決原型中包含引用類型值的問題,開始使用借用構造函數,也叫偽造對象或經典繼承

          構造函數繼承實現的原理就是在子類中調用父類構造函數來實現繼承

          function Parent2(age) {
              this.age = age
              this.say = function() {
                  console.log(this.name)
              }
          }
          function Child2(name,age,gender) {
              this.name = name
              Parent2.call(this, age)
              this.gender = gender
          }
          let child2 = new Child2('張三', 18, 'boy')
          console.log(child2) //Child2 {name: '張三', age: 18, gender: 'boy'}
          child2.say() // 張三
          

          優點: 可以傳遞參數以及避免了引用類型的屬性被所有實例共享

          缺點: 所有方法都在構造函數內,每次創建對象都會創建對應的方法,大大浪費內存

          組合繼承*

          也叫偽經典繼承,將原型鏈和借用構造函數的技術組合到一塊。使用原型鏈實現對原型屬性和方法的繼承,而通過構造函數來實現對實例屬性的繼承。

          function Parent3(age) {
              this.age = age
          }
          Parent3.prototype.say = function() {
              console.log(this.name)
          }
          function Child3(name,age,gender) {
              this.name = name
              Parent3.call(this, age)
              this.gender = gender
          }
          
          Child3.prototype = new Parent3
          Child3.prototype.constructor = Child3
          let child3 = new Child3('張三', 18, 'boy')
          console.log(child3) //Child3 {name: '張三', age: 18, gender: 'boy'}
          child2.say() // 張三
          
          • 將Child3的原型指定為Parent3的一個實例,大致步驟和原型鏈繼承類似,只是多了在Child3中借調Parent3的過程。
          • 實例屬性定義在構造函數中,而方法則定義在構造函數的新原型中,同時將新原型的constructor指向構造函數。
          • 可以通過instanceofisPrototypeOf()來識別基于組合繼承創建的對象。
          • 避免了原型鏈和借用構造函數的缺陷,融合了它們的優點,成為JS中最常用的繼承模式。

          原型式繼承

          借助原型可以基于已有的對象創建新對象,同時還不必因此創建自定義類型。

          function object(obj) {
              function F(){}
              F.prototype = obj
              return new F()
          }
          let parent4 = {
              age: 18,
              name: '張三',
              say() {
                  console.log(this.name)
              }
          }
          let child4 = object(parent4)
          console.log(child4) 
          /**{[[Prototype]]: Object
              age: 18
              name: "張三"
              say: ? say()
              [[Prototype]]: Object
              }
          */
          child4.say() // 張三
          

          在 object()函數內部,先創建了一個臨時性的構造函數,然后將傳入的對象作為這個構造函數的原型,最后返回了這個臨時類型的一個新實例。從本質上講,object()對傳入其中的對象執行了一次淺復制

          • 這種原型式繼承,要求必須要有一個對象可以作為另一個對象的基礎
          • 用這種方式創建的對象相當于是傳入參數對象的副本

          它其實就是ES5 Object.create的模擬實現,將傳入的對象作為創建對象的原型

          在只想讓一個對象與另一個對象保持類似的情況下,原型繼承是完全可以勝任的。原型模式下的缺點:引用類型屬性的共享問題。

          寄生繼承

          寄生式繼承與原型式繼承緊密相關,與寄生構造函數和工廠模式類似,即創建一個僅用于封裝繼承過程的函數,該函數在內部以某種方式來增強對象,最后再返回對象。

          function createAnother(original) {
            var clone = object(original) //通過調用函數創建一個新對象
            clone.say = function(){        // 以某種方式來增強這個對象
              console.log('nanjiu')
            };
            return clone            // 返回這個對象
          }
          

          缺點: 跟借用構造函數模式一樣,每次創建對象都會創建一遍方法。

          寄生組合式繼承*

          組合繼承是 JavaScript 最常用的繼承模式;不過,它也有自己的不足。組合繼承最大的問題就是無論什么情況下,都會調用兩次超類型構造函數:一次是在創建子類型原型的時候,另一次是在子類型構造函數內部。沒錯,子類型最終會包含超類型對象的全部實例屬性,但我們不得不在調用子類型構造函數時重寫這些屬性。

          function object(o) {
              function F() {}
              F.prototype = o;
              return new F();
          }
          
          function prototype(child, parent) {
              var prototype = object(parent.prototype);
              prototype.constructor = child;
              child.prototype = prototype;
          }
          
          
          function Parent6(age) {
              this.age = age
          }
          Parent6.prototype.say = function() {
              console.log(this.name)
          }
          function Child6(name, gender) {
              this.name = name
              this.gender = gender
          }
          
          // 使用
          prototype(Child6, Parent6);
          let child6 = new Child6('nanjiu', 'boy')
          
          console.log(child6) // Child6 {name: 'nanjiu', gender: 'boy'}
          child6.say() // nanjiu
          

          總結

          JavaScript 主要通過原型鏈實現繼承。原型鏈的構建是通過將一個類型的實例賦值給另一個構造函數的原型實現的。這樣,子類型就能夠訪問超類型的所有屬性和方法,這一點與基于類的繼承很相似。

          • 原型鏈的問題是對象實例共享所有繼承的屬性和方法,因此不適宜單獨使用。
          • 解決這個問題的技術是借用構造函數,即在子類型構造函數的內部調用超類型構造函數。這樣就可以做到每個實例都具有自己的屬性,同時還能往超類型構造函數中傳遞參數,但是沒有函數復用。
          • 使用最多的繼承模式是組合繼承,這種模式使用原型鏈繼承共享的屬性和方法,而通過借用構造函數繼承實例屬性。
          • 此外,還存在下列可供選擇的繼承模式。
          • 原型式繼承,可以在不必預先定義構造函數的情況下實現繼承,其本質是執行對給定對象的淺復制。而復制得到的副本還可以得到進一步改造。
          • 寄生式繼承,與原型式繼承非常相似,也是基于某個對象或某些信息創建一個對象,然后增強對象,最后返回對象。為了解決組合繼承模式由于多次調用超類型構造函數而導致的低效率問題,可以將這個模式與組合繼承一起使用。
          • 寄生組合式繼承,集寄生式繼承和組合繼承的優點與一身,是實現基于類型繼承的最有效方式。

          實現AJAX

          步驟:

          • 創建XMLHttpRequest對象
          • 打開鏈接 (指定請求類型,需要請求數據在服務器的地址,是否異步i請求)
          • 向服務器發送請求(get類型直接發送請求,post類型需要設置請求頭)
          • 接收服務器的響應數據(需根據XMLHttpRequest的readyState屬性判定調用哪個回調函數)
          function ajax(url, method, data=null) {
              const xhr = XMLHttpRequest() // 咱們這里就不管IE低版本了
              // open()方法,它接受3個參數:要發送的請求的類型,請求的url和是否異步發送請求的布爾值。
              xhr.open(method, url ,false) // 開啟一個請求,當前還未發送
          
              xhr.onreadyStatechange = function() {
                  if(xhr.readyState == 4) {
                      if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
                          alert(xhr.responseText);
                      } else {
                          console.log("Request was unsuccessful: " + xhr.status);
                      }
                  }
              }
              if(method === 'post') {
                  xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
              }
              xhr.send(data) // get請求,data應為null,參數拼接在URL上
          }
          

          多維數組扁平化

          function flat(arr) {
              const res = []
              // 遞歸實現
              const stack = [...arr] // 復制一份
              while(stack.length) {
                  //取出復制棧內第一個元素
                  const val = stack.shift()
                  if(Array.isArray(val)) {
                      // 如果是數組,就展開推入棧的最后
                      stack.push(...val)
                  }else{
                      // 否則就推入res返回值
                      res.push(val)
                  }
              }
              return res
          }
          const arr = [1,[2],[3,4,[5,6,[7,[8]]]]]
          console.log(flat(arr)) //[1, 2, 3, 4, 5, 6, 7, 8]
          

          當然你也可以用數組自帶的方法flat,將展開層數指定為Infinity無窮大,看看面試官搭不搭理你

          const arr = [1,[2],[3,4,[5,6,[7,[8]]]]]
          console.log(arr.flat(Infinity)) //[1, 2, 3, 4, 5, 6, 7, 8]
          

          setTimeout 模擬 setInterval

          思路就是遞歸調用setTimeout

          function mySetInterval(callback, delay) {
              let timer = null
              let interval = () => {
                  timer = setTimeout(()=>{
                      callback()
                      interval() // 遞歸
                  }, delay)
              }
              interval() // 先執行一次
              return {
                  id: timer,
                  clear: () => {
                      clearTimeout(timer)
                  }
              }
          }
          
          let time = mySetInterval(()=>{
              console.log(1)
          },1000)
          setTimeout(()=>{
              time.clear()
          },2000)
          

          setInterval 模擬 setTimeout

          思路就是setInterval執行一次后將setInterval清除即可

          function mySetTimeout(callback, delay) {
              let timer = null
              timer = setInterval(()=>{
                  callback()
                  clearInterval(timer)
              },delay)
          }
          
          mySetTimeout(()=>{
              console.log(1)
          },1000)
          

          sleep

          實現一個函數,n秒后執行指定函數

          function sleep(func, delay) {
              return new Promise((resolve, reject) => {
                  setTimeout(() => {
                      resolve(func())
                  }, delay)
              })
          }
          
          function say(name) {
              console.log(name)
          }
          async function go() {
              await sleep(()=>say('nanjiu'),1000) //過一秒打印nanjiu
              await sleep(()=>say('前端張三'),2000) // 再過兩秒打印前端張三
          }
          go()
          

          數組去重的多種實現方式

          使用Set

          let arr = [1,2,3,2,4,5,3,6,2]
          function arrayToHeavy1(arr) {
              return [...new Set(arr)]
          }
          console.log(arrayToHeavy1(arr)) //[ 1, 2, 3, 4, 5, 6 ]
          

          使用indexOf

          function arrayToHeavy2(arr) {
              let newArr = []
              for(let i=0; i<arr.length; i++) {
                  if(newArr.indexOf(arr[i]) == -1){
                      newArr.push(arr[i])
                  }
              }
              return newArr
          }
          console.log(arrayToHeavy2(arr)) //[ 1, 2, 3, 4, 5, 6 ]
          

          使用filter

          function arrayToHeavy3(arr) {
              return arr.filter((item, index) => {
                  return arr.indexOf(item) == index
              })
          }
          
          console.log(arrayToHeavy3(arr)) //[ 1, 2, 3, 4, 5, 6 ]
          

          使用Map

          function arrayToHeavy4(arr) {
              let map = new Map()
              for(let i=0; i<arr.length; i++) {
                  if(!map.has(arr[i])){
                      map.set(arr[i], 1)
                  }
              }
              return [...map.keys()]
          }
          console.log(arrayToHeavy4(arr)) //[ 1, 2, 3, 4, 5, 6 ]
          

          使用include

          function arrayToHeavy5(arr) {
              let res = []
              for(let i=0; i<arr.length; i++) {
                  if(!res.includes(arr[i])) {
                      res.push(arr[i])
                  }
              }
              return res
          }
          console.log(arrayToHeavy5(arr)) //[ 1, 2, 3, 4, 5, 6 ]
          

          解析URL參數

          根據key獲取URL上的參數值

          function queryData(key) {
              let url = window.location.href,obj = {}
              let str = url.split('?')[1] // 先拿到問號后面的所有參數
              let arr = str.split('&') // 分割參數
              for(let i=0; i< arr.length; i++) {
                  let kv = arr[i].split('=')
                  obj[kv[0]] = decodeURIComponent(kv[1])
              }
              console.log(url,obj)
              // {a: '1', b: '2', c: '3', name: '張三'}
              return obj[key]
          
          }
          //http://127.0.0.1:5500/src/js/2022/%E6%89%8B%E5%86%99/index.html?a=1&b=2&c=3&name=%E5%8D%97%E7%8E%96
          console.log(queryData('name')) // 張三
          

          斐波那契數列

          F(n) = F(n - 1) + F(n - 2),其中 n > 1

          暴力遞歸版本

          function fib(n) {
              if(n == 0) return 0
              if(n == 1 || n == 2) return 1
              return fib(n-1) + fib(n-2)
          }
          
          // console.log(fib(4)) //F(4)=F(3)+F(2)=F(2)+F(1)+F(2)=1+1+1=3
          let t = +new Date()
          console.log(fib(40)) //102334155
          console.log(+new Date()-t) //783ms
          

          優化版本

          function fib2(n) {
              if(fib2[n] !== undefined) return fib2[n]
              if(n == 0) return 0
              if(n == 1 || n == 2) return 1
          
              const res = fib2(n-1) + fib2(n-2)
              fib2[n] = res
              return res
          }
          let t1 = +new Date()
          console.log(fib2(40)) //102334155
          console.log(+new Date()-t1)  //5ms
          

          發布訂閱

          用過Vue的eventBus的同學應該很熟悉,$on訂閱事件,$emit發布事件

          歡web前端網頁開發和python開發的、請加下企鵝群:526929231 內有大量案例和學習教程,對python、和web感興趣的朋友可以加下哦

          form表單域

          所有的控件(表單元素都寫在form表單標簽中)

          <form>
           使用form標簽建立表單域,當提交數據的時候會收集表單域里面的數據然后發送給服務器</form>

          input表單控件元素

          賦予不同的type值可實現不同的表單控件

          type類型描述
          text文本輸入框 maxlength最大長度、onlyready只讀、 disabled禁止、 placeholder
          password密碼遮掩框
          radio單選按鈕,checked默認選擇
          checkbox多選框
          submit收集表單域的name數據,然后提交到服務器上

          下拉列表框

          <select>
           <option value="music">聽音樂</option>
           <option value="running">跑步</option>
           <option value="study">學習</option>
           <option value="coffee">找小姐姐一起喝咖啡</option></select><!-- selected="selected"默認選中 --><!-- size="2" 現實兩行下拉項 --><!-- disabled 禁止選擇 -->

          selected默認選擇一項

          多行文本輸入框(文本域) textarea

          <textare cols="30" rows="10"></textarea>

          clos顯示多少列,rows現實多少行

          按鈕

          單獨使用沒有效果,一般配合js點擊按鈕的時候執行什么操作

          <input type="button" value="自定義按鈕標題" />

          重置按鈕

          回到表單初識狀態

          <input type="reset" value="重置表單" />

          隱藏域

          目的在于收集或發送信息 頁面上面沒有任何效果 CSRF跨域攻擊在此作用

          <input type="hidden" value="ABCD1234" />

          label元素

          為input元素定義一個標記,label元素不會向用戶呈現任何特殊效果。不過,它為鼠標用戶改進了可用性。如果你在label元素內點擊文本,就會觸發此控件。就是說,當用戶選擇該標簽是,瀏覽器就會自動將焦點轉到和標簽相關的表單控件上

          <input id="man" type="radio" /><label for="man">男</label>

          form表單屬性

          屬性描述
          action指定提交到哪個url上
          method提交方式,常用的GET / POST

          提交方式

          MethodDescription
          GETURL地址欄上做拼接問號再加參數
          POST隱式提交方式,看不到,可以抓包

          格式化文本

          • pre可定義預格式化的文本。

          • pre元素中的文本通常會保留空格和換行符。

            主要用于在網頁上顯示代碼,比如在網頁當中顯示html模板


          主站蜘蛛池模板: 亚洲一区二区三区高清在线观看| 国产成人片视频一区二区| 波多野结衣中文字幕一区 | 国模吧一区二区三区| 亚洲国产一区二区三区| 一区二区在线免费视频| 福利片福利一区二区三区| 日韩少妇无码一区二区三区| 久久久久人妻一区精品果冻| 日本在线一区二区| 精品国产一区二区三区2021| 国产精品丝袜一区二区三区| 无码国产精品一区二区免费式直播| 精品国产一区二区三区久久影院 | 国产精品成人一区二区三区| 亚洲日本久久一区二区va| 国内精品视频一区二区三区| 亚洲V无码一区二区三区四区观看| 国产一区二区电影在线观看| 亚洲综合av一区二区三区| 国产一区视频在线免费观看| 国产一区视频在线| 国产一区二区三区露脸| 亚洲av不卡一区二区三区| www一区二区三区| 无码人妻一区二区三区av| 国产精品丝袜一区二区三区| 国产精品自在拍一区二区不卡| 亚洲综合av一区二区三区| 免费萌白酱国产一区二区三区| 中文字幕乱码亚洲精品一区| 国产乱码精品一区二区三 | 亚洲中文字幕一区精品自拍 | 国产精品视频免费一区二区三区| 国产精品免费一区二区三区四区| 国产中的精品一区的| 日韩精品一区在线| 欧洲精品一区二区三区| 国产精品无码一区二区三区毛片| 91福利一区二区| 无码人妻精品一区二区三区不卡|