沐鳴代理_前端面試js高頻手寫大全

介紹

在前端面試中,手撕代碼顯然是不可避免的,並且占很大的一部分比重。

一般來說,如果代碼寫的好,即使理論知識答得不夠清楚,也能有大概率通過面試。並且其實很多手寫往往背後就考察了你對相關理論的認識。

編程題主要分為這幾種類型:

* 算法題
* 涉及js原理的題以及ajax請求
* 業務場景題: 實現一個具有某種功能的組件
* 其他(進階,對計算機綜合知識的考察,考的相對較少):實現訂閱發布者模式;分別用面向對象編程,面向過程編程,函數式編程實現把大象放進冰箱等等

其中前兩種類型所佔比重最大。
算法題建議養成每天刷一道leetcode的習慣,重點刷數據結構(棧,鏈表,隊列,樹),動態規劃,DFS,BFS

本文主要涵蓋了第二種類型的各種重點手寫。

建議優先掌握

  • instanceof (考察對原型鏈的理解)
  • new (對創建對象實例過程的理解)
  • call&apply&bind (對this指向的理解)
  • 手寫promise (對異步的理解)
  • 手寫原生ajax (對ajax原理和http請求方式的理解,重點是get和post請求的實現)
  • 其他:數組,字符串的api的實現,難度相對較低。只要了解數組,字符串的常用方法的用法,現場就能寫出來個大概。(ps:筆者認為數組的reduce方法比較難,這塊有餘力可以單獨看一些,即使面試沒讓你實現reduce,寫其他題時用上它也是很加分的)

話不多說,直接開始

1. 手寫instanceof

instanceof作用:

判斷一個實例是否是其父類或者祖先類型的實例。

instanceof 在查找的過程中會遍歷左邊變量的原型鏈,直到找到右邊變量的 prototype查找失敗,返回 false

 let myInstanceof = (target,origin) => {
     while(target) {
         if(target.__proto__===origin.prototype) {
            return true
         }
         target = target.__proto__
     }
     return false
 }
 let a = [1,2,3]
 console.log(myInstanceof(a,Array));  // true
 console.log(myInstanceof(a,Object));  // true

2. 實現數組的map方法

數組的map() 方法會返回一個新的數組,這個新數組中的每個元素對應原數組中的對應位置元素調用一次提供的函數后的返回值。

用法:

const a = [1, 2, 3, 4];
const b = array1.map(x => x * 2);
console.log(b);   // Array [2, 4, 6, 8]

原生實現:

 Array.prototype.myMap = function(fn, thisValue) {
     let res = []
     thisValue = thisValue||[]
     let arr = this
     for(let i in arr) {
        res.push(fn(arr[i]))
     }
     return res
 }

3. reduce實現數組的map方法

利用數組內置的reduce方法實現map方法,考察對reduce原理的掌握

Array.prototype.myMap = function(fn,thisValue){
     var res = [];
     thisValue = thisValue||[];
     this.reduce(function(pre,cur,index,arr){
         return res.push(fn.call(thisValue,cur,index,arr));
     },[]);
     return res;
}

var arr = [2,3,1,5];
arr.myMap(function(item,index,arr){
 console.log(item,index,arr);
})

4. 手寫數組的reduce方法

reduce() 方法接收一個函數作為累加器,數組中的每個值(從左到右)開始縮減,最終為一個值,是ES5中新增的又一個數組逐項處理方法

參數:

  • callback(一個在數組中每一項上調用的函數,接受四個函數:)

    • previousValue(上一次調用回調函數時的返回值,或者初始值)
    • currentValue(當前正在處理的數組元素)
    • currentIndex(當前正在處理的數組元素下標)
    • array(調用reduce()方法的數組)
  • initialValue(可選的初始值。作為第一次調用回調函數時傳給previousValue的值)
 function reduce(arr, cb, initialValue){
     var num = initValue == undefined? num = arr[0]: initValue;
     var i = initValue == undefined? 1: 0
     for (i; i< arr.length; i++){
        num = cb(num,arr[i],i)
     }
     return num
 }
 
 function fn(result, currentValue, index){
     return result + currentValue
 }
 
 var arr = [2,3,4,5]
 var b = reduce(arr, fn,10) 
 var c = reduce(arr, fn)
 console.log(b)   // 24

5. 數組扁平化

數組扁平化就是把多維數組轉化成一維數組

1. es6提供的新方法 flat(depth)

let a = [1,[2,3]]; 
a.flat(); // [1,2,3] 
a.flat(1); //[1,2,3]

其實還有一種更簡單的辦法,無需知道數組的維度,直接將目標數組變成1維數組。 depth的值設置為Infinity。

let a = [1,[2,3,[4,[5]]]]; 
a.flat(Infinity); // [1,2,3,4,5]  a是4維數組

2. 利用cancat

function flatten(arr) {
     var res = [];
     for (let i = 0, length = arr.length; i < length; i++) {
     if (Array.isArray(arr[i])) {
     res = res.concat(flatten(arr[i])); //concat 並不會改變原數組
     //res.push(...flatten(arr[i])); //或者用擴展運算符 
     } else {
         res.push(arr[i]);
       }
     }
     return res;
 }
 let arr1 = [1, 2,[3,1],[2,3,4,[2,3,4]]]
flatten(arr1); //[1, 2, 3, 1, 2, 3, 4, 2, 3, 4]

6. 函數柯里化

柯里化的定義:接收一部分參數,返回一個函數接收剩餘參數,接收足夠參數后,執行原函數。

當柯里化函數接收到足夠參數后,就會執行原函數,如何去確定何時達到足夠的參數呢?

有兩種思路:

  1. 通過函數的 length 屬性,獲取函數的形參個數,形參的個數就是所需的參數個數
  2. 在調用柯里化工具函數時,手動指定所需的參數個數

將這兩點結合一下,實現一個簡單 curry 函數:


/**
 * 將函數柯里化
 * @param fn    待柯里化的原函數
 * @param len   所需的參數個數,默認為原函數的形參個數
 */
function curry(fn,len = fn.length) {
 return _curry.call(this,fn,len)
}

/**
 * 中轉函數
 * @param fn    待柯里化的原函數
 * @param len   所需的參數個數
 * @param args  已接收的參數列表
 */
function _curry(fn,len,...args) {
    return function (...params) {
         let _args = [...args,...params];
         if(_args.length >= len){
             return fn.apply(this,_args);
         }else{
          return _curry.call(this,fn,len,..._args)
         }
    }
}

我們來驗證一下:

let _fn = curry(function(a,b,c,d,e){
 console.log(a,b,c,d,e)
});

_fn(1,2,3,4,5);     // print: 1,2,3,4,5
_fn(1)(2)(3,4,5);   // print: 1,2,3,4,5
_fn(1,2)(3,4)(5);   // print: 1,2,3,4,5
_fn(1)(2)(3)(4)(5); // print: 1,2,3,4,5

我們常用的工具庫 lodash 也提供了 curry 方法,並且增加了非常好玩的 placeholder 功能,通過佔位符的方式來改變傳入參數的順序。

比如說,我們傳入一個佔位符,本次調用傳遞的參數略過佔位符, 佔位符所在的位置由下次調用的參數來填充,比如這樣:

直接看一下官網的例子:

接下來我們來思考,如何實現佔位符的功能。

對於 lodash 的 curry 函數來說,curry 函數掛載在 lodash 對象上,所以將 lodash 對象當做默認佔位符來使用。

而我們的自己實現的 curry 函數,本身並沒有掛載在任何對象上,所以將 curry 函數當做默認佔位符

使用佔位符,目的是改變參數傳遞的順序,所以在 curry 函數實現中,每次需要記錄是否使用了佔位符,並且記錄佔位符所代表的參數位置。

直接上代碼:


/**
 * @param  fn           待柯里化的函數
 * @param  length       需要的參數個數,默認為函數的形參個數
 * @param  holder       佔位符,默認當前柯里化函數
 * @return {Function}   柯里化后的函數
 */
function curry(fn,length = fn.length,holder = curry){
 return _curry.call(this,fn,length,holder,[],[])
}
/**
 * 中轉函數
 * @param fn            柯里化的原函數
 * @param length        原函數需要的參數個數
 * @param holder        接收的佔位符
 * @param args          已接收的參數列表
 * @param holders       已接收的佔位符位置列表
 * @return {Function}   繼續柯里化的函數 或 最終結果
 */
function _curry(fn,length,holder,args,holders){
 return function(..._args){
 //將參數複製一份,避免多次操作同一函數導致參數混亂
 let params = args.slice();
 //將佔位符位置列表複製一份,新增加的佔位符增加至此
 let _holders = holders.slice();
 //循環入參,追加參數 或 替換佔位符
 _args.forEach((arg,i)=>{
 //真實參數 之前存在佔位符 將佔位符替換為真實參數
 if (arg !== holder && holders.length) {
     let index = holders.shift();
     _holders.splice(_holders.indexOf(index),1);
     params[index] = arg;
 }
 //真實參數 之前不存在佔位符 將參數追加到參數列表中
 else if(arg !== holder && !holders.length){
     params.push(arg);
 }
 //傳入的是佔位符,之前不存在佔位符 記錄佔位符的位置
 else if(arg === holder && !holders.length){
     params.push(arg);
     _holders.push(params.length - 1);
 }
 //傳入的是佔位符,之前存在佔位符 刪除原佔位符位置
 else if(arg === holder && holders.length){
    holders.shift();
 }
 });
 // params 中前 length 條記錄中不包含佔位符,執行函數
 if(params.length >= length && params.slice(0,length).every(i=>i!==holder)){
 return fn.apply(this,params);
 }else{
 return _curry.call(this,fn,length,holder,params,_holders)
 }
 }
}

驗證一下:;

let fn = function(a, b, c, d, e) {
 console.log([a, b, c, d, e]);
}

let _ = {}; // 定義佔位符
let _fn = curry(fn,5,_);  // 將函數柯里化,指定所需的參數個數,指定所需的佔位符

_fn(1, 2, 3, 4, 5);                 // print: 1,2,3,4,5
_fn(_, 2, 3, 4, 5)(1);              // print: 1,2,3,4,5
_fn(1, _, 3, 4, 5)(2);              // print: 1,2,3,4,5
_fn(1, _, 3)(_, 4,_)(2)(5);         // print: 1,2,3,4,5
_fn(1, _, _, 4)(_, 3)(2)(5);        // print: 1,2,3,4,5
_fn(_, 2)(_, _, 4)(1)(3)(5);        // print: 1,2,3,4,5

至此,我們已經完整實現了一個 curry 函數~~

7. 實現深拷貝

淺拷貝和深拷貝的區別:

淺拷貝:只拷貝一層,更深層的對象級別的只拷貝引用

深拷貝:拷貝多層,每一級別的數據都會拷貝。這樣更改拷貝值就不影響另外的對象

ES6淺拷貝方法:Object.assign(target,…sources)


let obj={
 id:1,
 name:'Tom',
 msg:{
 age:18
 }
}
let o={}
//實現深拷貝  遞歸    可以用於生命遊戲那個題對二維數組的拷貝,
//但比較麻煩,因為已知元素都是值,直接複製就行,無需判斷
function deepCopy(newObj,oldObj){
     for(var k in oldObj){
         let item=oldObj[k]
         //判斷是數組?對象?簡單類型?
         if(item instanceof Array){
             newObj[k]=[]
             deepCopy(newObj[k],item)
         }else if(item instanceof Object){
             newObj[k]={}
             deepCopy(newObj[k],item)
         }else{  //簡單數據類型,直接賦值
             newObj[k]=item
         }
     }
}

8. 手寫call, apply, bind

手寫call

Function.prototype.myCall=function(context=window){  // 函數的方法,所以寫在Fuction原型對象上
 if(typeof this !=="function"){   // 這裏if其實沒必要,會自動拋出錯誤
    throw new Error("不是函數")
 }
 const obj=context||window   //這裏可用ES6方法,為參數添加默認值,js嚴格模式全局作用域this為undefined
 obj.fn=this      //this為調用的上下文,this此處為函數,將這個函數作為obj的方法
 const arg=[...arguments].slice(1)   //第一個為obj所以刪除,偽數組轉為數組
 res=obj.fn(...arg)
 delete obj.fn   // 不刪除會導致context屬性越來越多
 return res
}
//用法:f.call(obj,arg1)
function f(a,b){
 console.log(a+b)
 console.log(this.name)
}
let obj={
 name:1
}
f.myCall(obj,1,2) //否則this指向window

obj.greet.call({name: 'Spike'}) //打出來的是 Spike

手寫apply(arguments[this, [參數1,參數2…..] ])

Function.prototype.myApply=function(context){  // 箭頭函數從不具有參數對象!!!!!這裏不能寫成箭頭函數
 let obj=context||window
 obj.fn=this
 const arg=arguments[1]||[]    //若有參數,得到的是數組
 let res=obj.fn(...arg)
 delete obj.fn
 return res
} 
function f(a,b){
 console.log(a,b)
 console.log(this.name)
}
let obj={
 name:'張三'
}
f.myApply(obj,[1,2])  //arguments[1]

手寫bind

this.value = 2
var foo = {
 value: 1
};
var bar = function(name, age, school){
 console.log(name) // 'An'
 console.log(age) // 22
 console.log(school) // '家裡蹲大學'
}
var result = bar.bind(foo, 'An') //預置了部分參數'An'
result(22, '家裡蹲大學') //這個參數會和預置的參數合併到一起放入bar中

簡單版本

Function.prototype.bind = function(context, ...outerArgs) {
 var fn = this;
 return function(...innerArgs) {   //返回了一個函數,...rest為實際調用時傳入的參數
 return fn.apply(context,[...outerArgs, ...innerArgs]);  //返回改變了this的函數,
 //參數合併
 }
}

new失敗的原因:

例:

// 聲明一個上下文
let thovino = {
 name: 'thovino'
}

// 聲明一個構造函數
let eat = function (food) {
 this.food = food
 console.log(`${this.name} eat ${this.food}`)
}
eat.prototype.sayFuncName = function () {
 console.log('func name : eat')
}

// bind一下
let thovinoEat = eat.bind(thovino)
let instance = new thovinoEat('orange')  //實際上orange放到了thovino裏面
console.log('instance:', instance) // {}

生成的實例是個空對象

在new操作符執行時,我們的thovinoEat函數可以看作是這樣:

function thovinoEat (...innerArgs) {
 eat.call(thovino, ...outerArgs, ...innerArgs)
}

在new操作符進行到第三步的操作thovinoEat.call(obj, …args)時,這裏的obj是new操作符自己創建的那個簡單空對象{},但它其實並沒有替換掉thovinoEat函數內部的那個上下文對象thovino。這已經超出了call的能力範圍,因為這個時候要替換的已經不是thovinoEat函數內部的this指向,而應該是thovino對象。

換句話說,我們希望的是new操作符將eat內的this指向操作符自己創建的那個空對象。但是實際上指向了thovino,new操作符的第三步動作並沒有成功

可new可繼承版本

Function.prototype.bind = function (context, ...outerArgs) {
 let that = this;

function res (...innerArgs) {
     if (this instanceof res) {
         // new操作符執行時
         // 這裏的this在new操作符第三步操作時,會指向new自身創建的那個簡單空對象{}
         that.call(this, ...outerArgs, ...innerArgs)
     } else {
         // 普通bind
         that.call(context, ...outerArgs, ...innerArgs)
     }
     }
     res.prototype = this.prototype //!!!
     return res
}

9. 手動實現new

new的過程文字描述:

  1. 創建一個空對象 obj;
  2. 將空對象的隱式原型(proto)指向構造函數的prototype。
  3. 使用 call 改變 this 的指向
  4. 如果無返回值或者返回一個非對象值,則將 obj 返回作為新對象;如果返回值是一個新對象的話那麼直接直接返回該對象。
function Person(name,age){
 this.name=name
 this.age=age
}
Person.prototype.sayHi=function(){
 console.log('Hi!我是'+this.name)
}
let p1=new Person('張三',18)

////手動實現new
function create(){
 let obj={}
 //獲取構造函數
 let fn=[].shift.call(arguments)  //將arguments對象提出來轉化為數組,arguments並不是數組而是對象    !!!這種方法刪除了arguments數組的第一個元素,!!這裏的空數組裡面填不填元素都沒關係,不影響arguments的結果      或者let arg = [].slice.call(arguments,1)
 obj.__proto__=fn.prototype
 let res=fn.apply(obj,arguments)    //改變this指向,為實例添加方法和屬性
 //確保返回的是一個對象(萬一fn不是構造函數)
 return typeof res==='object'?res:obj
}

let p2=create(Person,'李四',19)
p2.sayHi()

細節:

[].shift.call(arguments)  也可寫成:
 let arg=[...arguments]
 let fn=arg.shift()  //使得arguments能調用數組方法,第一個參數為構造函數
 obj.__proto__=fn.prototype
 //改變this指向,為實例添加方法和屬性
 let res=fn.apply(obj,arg)

10. 手寫promise(常見promise.all, promise.race)

// Promise/A+ 規範規定的三種狀態
const STATUS = {
 PENDING: 'pending',
 FULFILLED: 'fulfilled',
 REJECTED: 'rejected'
}

class MyPromise {
 // 構造函數接收一個執行回調
 constructor(executor) {
     this._status = STATUS.PENDING // Promise初始狀態
     this._value = undefined // then回調的值
     this._resolveQueue = [] // resolve時觸發的成功隊列
     this._rejectQueue = [] // reject時觸發的失敗隊列
    
 // 使用箭頭函數固定this(resolve函數在executor中觸發,不然找不到this)
 const resolve = value => {
     const run = () => {
         // Promise/A+ 規範規定的Promise狀態只能從pending觸發,變成fulfilled
         if (this._status === STATUS.PENDING) {
             this._status = STATUS.FULFILLED // 更改狀態
             this._value = value // 儲存當前值,用於then回調
            
             // 執行resolve回調
             while (this._resolveQueue.length) {
                 const callback = this._resolveQueue.shift()
                 callback(value)
             }
         }
     }
     //把resolve執行回調的操作封裝成一個函數,放進setTimeout里,以實現promise異步調用的特性(規範上是微任務,這裡是宏任務)
     setTimeout(run)
 }

 // 同 resolve
 const reject = value => {
     const run = () => {
         if (this._status === STATUS.PENDING) {
         this._status = STATUS.REJECTED
         this._value = value
        
         while (this._rejectQueue.length) {
             const callback = this._rejectQueue.shift()
             callback(value)
         }
     }
 }
     setTimeout(run)
 }

     // new Promise()時立即執行executor,並傳入resolve和reject
     executor(resolve, reject)
 }

 // then方法,接收一個成功的回調和一個失敗的回調
 function then(onFulfilled, onRejected) {
  // 根據規範,如果then的參數不是function,則忽略它, 讓值繼續往下傳遞,鏈式調用繼續往下執行
  typeof onFulfilled !== 'function' ? onFulfilled = value => value : null
  typeof onRejected !== 'function' ? onRejected = error => error : null

  // then 返回一個新的promise
  return new MyPromise((resolve, reject) => {
    const resolveFn = value => {
      try {
        const x = onFulfilled(value)
        // 分類討論返回值,如果是Promise,那麼等待Promise狀態變更,否則直接resolve
        x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
      } catch (error) {
        reject(error)
      }
    }
  }
}

  const rejectFn = error => {
      try {
        const x = onRejected(error)
        x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
      } catch (error) {
        reject(error)
      }
    }

    switch (this._status) {
      case STATUS.PENDING:
        this._resolveQueue.push(resolveFn)
        this._rejectQueue.push(rejectFn)
        break;
      case STATUS.FULFILLED:
        resolveFn(this._value)
        break;
      case STATUS.REJECTED:
        rejectFn(this._value)
        break;
    }
 })
 }
 catch (rejectFn) {
  return this.then(undefined, rejectFn)
}
// promise.finally方法
finally(callback) {
  return this.then(value => MyPromise.resolve(callback()).then(() => value), error => {
    MyPromise.resolve(callback()).then(() => error)
  })
}

 // 靜態resolve方法
 static resolve(value) {
      return value instanceof MyPromise ? value : new MyPromise(resolve => resolve(value))
  }

 // 靜態reject方法
 static reject(error) {
      return new MyPromise((resolve, reject) => reject(error))
    }

 // 靜態all方法
 static all(promiseArr) {
      let count = 0
      let result = []
      return new MyPromise((resolve, reject) =>       {
        if (!promiseArr.length) {
          return resolve(result)
        }
        promiseArr.forEach((p, i) => {
          MyPromise.resolve(p).then(value => {
            count++
            result[i] = value
            if (count === promiseArr.length) {
              resolve(result)
            }
          }, error => {
            reject(error)
          })
        })
      })
    }

 // 靜態race方法
 static race(promiseArr) {
      return new MyPromise((resolve, reject) => {
        promiseArr.forEach(p => {
          MyPromise.resolve(p).then(value => {
            resolve(value)
          }, error => {
            reject(error)
          })
        })
      })
    }
}

11. 手寫原生AJAX

步驟

  1. 創建 XMLHttpRequest 實例
  2. 發出 HTTP 請求
  3. 服務器返回 XML 格式的字符串
  4. JS 解析 XML,並更新局部頁面

    不過隨着歷史進程的推進,XML 已經被淘汰,取而代之的是 JSON。

了解了屬性和方法之後,根據 AJAX 的步驟,手寫最簡單的 GET 請求。

version 1.0:

myButton.addEventListener('click', function () {
  ajax()
})

function ajax() {
  let xhr = new XMLHttpRequest() //實例化,以調用方法
  xhr.open('get', 'https://www.google.com')  //參數2,url。參數三:異步
  xhr.onreadystatechange = () => {  //每當 readyState 屬性改變時,就會調用該函數。
    if (xhr.readyState === 4) {  //XMLHttpRequest 代理當前所處狀態。
      if (xhr.status >= 200 && xhr.status < 300) {  //200-300請求成功
        let string = request.responseText
        //JSON.parse() 方法用來解析JSON字符串,構造由字符串描述的JavaScript值或對象
        let object = JSON.parse(string)
      }
    }
  }
  request.send() //用於實際發出 HTTP 請求。不帶參數為GET請求
}

promise實現

function ajax(url) {
  const p = new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest()
    xhr.open('get', url)
    xhr.onreadystatechange = () => {
      if (xhr.readyState == 4) {
        if (xhr.status >= 200 && xhr.status <= 300) {
          resolve(JSON.parse(xhr.responseText))
        } else {
          reject('請求出錯')
        }
      }
    }
    xhr.send()  //發送hppt請求
  })
  return p
}
let url = '/data.json'
ajax(url).then(res => console.log(res))
  .catch(reason => console.log(reason))

12. 手寫節流防抖函數

函數節流與函數防抖都是為了限制函數的執行頻次,是一種性能優化的方案,比如應用於window對象的resize、scroll事件,拖拽時的mousemove事件,文字輸入、自動完成的keyup事件。

節流:連續觸發事件但是在 n 秒中只執行一次函數

例:(連續不斷動都需要調用時用,設一時間間隔),像dom的拖拽,如果用消抖的話,就會出現卡頓的感覺,因為只在停止的時候執行了一次,這個時候就應該用節流,在一定時間內多次執行,會流暢很多。

防抖:指觸發事件后在 n 秒內函數只能執行一次,如果在 n 秒內又觸發了事件,則會重新計算函數執行時間。

例:(連續不斷觸發時不調用,觸發完後過一段時間調用),像仿百度搜索,就應該用防抖,當我連續不斷輸入時,不會發送請求;當我一段時間內不輸入了,才會發送一次請求;如果小於這段時間繼續輸入的話,時間會重新計算,也不會發送請求。

防抖的實現:

function debounce(fn, delay) {
     if(typeof fn!=='function') {
        throw new TypeError('fn不是函數')
     }
     let timer; // 維護一個 timer
     return function () {
         var _this = this; // 取debounce執行作用域的this(原函數掛載到的對象)
         var args = arguments;
         if (timer) {
            clearTimeout(timer);
         }
         timer = setTimeout(function () {
            fn.apply(_this, args); // 用apply指向調用debounce的對象,相當於_this.fn(args);
         }, delay);
     };
}

// 調用
input1.addEventListener('keyup', debounce(() => {
 console.log(input1.value)
}), 600)

節流的實現:

function throttle(fn, delay) {
  let timer;
  return function () {
    var _this = this;
    var args = arguments;
    if (timer) {
      return;
    }
    timer = setTimeout(function () {
      fn.apply(_this, args); // 這裏args接收的是外邊返回的函數的參數,不能用arguments
      // fn.apply(_this, arguments); 需要注意:Chrome 14 以及 Internet Explorer 9 仍然不接受類數組對象。如果傳入類數組對象,它們會拋出異常。
      timer = null; // 在delay后執行完fn之後清空timer,此時timer為假,throttle觸發可以進入計時器
    }, delay)
  }
}

div1.addEventListener('drag', throttle((e) => {
  console.log(e.offsetX, e.offsetY)
}, 100))

13. 手寫Promise加載圖片

function getData(url) {
  return new Promise((resolve, reject) => {
    $.ajax({
      url,
      success(data) {
        resolve(data)
      },
      error(err) {
        reject(err)
      }
    })
  })
}
const url1 = './data1.json'
const url2 = './data2.json'
const url3 = './data3.json'
getData(url1).then(data1 => {
  console.log(data1)
  return getData(url2)
}).then(data2 => {
  console.log(data2)
  return getData(url3)
}).then(data3 =>
  console.log(data3)
).catch(err =>
  console.error(err)
)

14. 函數實現一秒鐘輸出一個數

for(let i=0;i<=10;i++){   //用var打印的都是11
 setTimeout(()=>{
    console.log(i);
 },1000*i)
}

15. 創建10個標籤,點擊的時候彈出來對應的序號?

var a
for(let i=0;i<10;i++){
 a=document.createElement('a')
 a.innerhtml=i+'<br>'
 a.addEventListener('click',function(e){
     console.log(this)  //this為當前點擊的<a>
     e.preventDefault()  //如果調用這個方法,默認事件行為將不再觸發。
     //例如,在執行這個方法后,如果點擊一個鏈接(a標籤),瀏覽器不會跳轉到新的 URL 去了。我們可以用 event.isDefaultPrevented() 來確定這個方法是否(在那個事件對象上)被調用過了。
     alert(i)
 })
 const d=document.querySelector('div')
 d.appendChild(a)  //append向一個已存在的元素追加該元素。
}

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/10065

沐鳴登陸地址_深入淺出Javascript閉包

一、引子

閉包(closure)是 JavaScript 語言的一個難點,面試時常被問及,也是它的特色,很多高級應用都要依靠閉包實現。本文盡可能用簡單易懂的話,講清楚閉包的概念、形成條件及其常見的面試題。

我們先來看一個例子:

var n = 999;
function f1() {
console.log(n);
}
f1() // 999

上面代碼中,函數f1可以讀取全局變量n。但是,函數外部無法讀取函數內部聲明的變量。

function f1() {
var n = 999;
}
console.log(n)
// Uncaught ReferenceError: n is not defined

上面代碼中,函數f1內部聲明的變量n,函數外是無法讀取的。

如果有時需要得到函數內的局部變量。正常情況下,這是辦不到的,只有通過變通方法才能實現。那就是在函數的內部,再定義一個函數。

function f1() {
var n = 999;
function f2() {
  console.log(n); // 999
 }
}

上面代碼中,函數f2就在函數f1內部,這時f1內部的所有局部變量,對f2都是可見的。既然f2可以讀取f1的局部變量,那麼只要把f2作為返回值,我們不就可以在f1外部讀取它的內部變量了嗎!

二、閉包是什麼

我們可以對上面代碼進行如下修改:

   function f1(){
   var a = 999;
   function f2(){
    console.log(a);
   }
   return f2; // f1返回了f2的引用
   }
   var result = f1(); // result就是f2函數了
   result();  // 執行result,全局作用域下沒有a的定義,
         //但是函數閉包,能夠把定義函數的時候的作用域一起記住,輸出999			

上面代碼中,函數f1的返回值就是函數f2,由於f2可以讀取f1的內部變量,所以就可以在外部獲得f1的內部變量了。

閉包就是函數f2,即能夠讀取其他函數內部變量的函數。由於在JavaScript語言中,只有函數內部的子函數才能讀取內部變量,因此可以把閉包簡單理解成“定義在一個函數內部的函數”。閉包最大的特點,就是它可以“記住”誕生的環境,比如f2記住了它誕生的環境f1,所以從f2可以得到f1的內部變量。在本質上,閉包就是將函數內部和函數外部連接起來的一座橋樑。

那到底什麼是閉包呢?

當函數可以記住並訪問所在的詞法作用域,即使函數是在當前詞法作用域之外執行,這就產生了閉包。 —-《你不知道的Javascript上卷》

我個人理解,閉包就是函數中的函數(其他語言不能函數再套函數),裏面的函數可以訪問外面函數的變量,外面的變量的是這個內部函數的一部分。

閉包形成的條件

  • 函數嵌套
  • 內部函數引用外部函數的局部變量

三、閉包的特性

每個函數都是閉包,每個函數天生都能夠記憶自己定義時所處的作用域環境。把一個函數從它定義的那個作用域,挪走,運行。這個函數居然能夠記憶住定義時的那個作用域。不管函數走到哪裡,定義時的作用域就帶到了哪裡。接下來我們用兩個例子來說明這個問題:

//例題1
var inner;
function outer(){
var a=250;
inner=function(){
alert(a);//這個函數雖然在外面執行,但能夠記憶住定義時的那個作用域,a是250
  }
}
outer();
var a=300;
inner();//一個函數在執行的時候,找閉包裏面的變量,不會理會當前作用域。

//例題2
function outer(x){
  function inner(y){
  console.log(x+y);
  }
return inner;
}
var inn=outer(3);//数字3傳入outer函數后,inner函數中x便會記住這個值
inn(5);//當inner函數再傳入5的時候,只會對y賦值,所以最後彈出8

四、閉包的內存泄漏

棧內存提供一個執行環境,即作用域,包括全局作用域和私有作用域,那他們什麼時候釋放內存的?

  • 全局作用域—-只有當頁面關閉的時候全局作用域才會銷毀
  • 私有的作用域—-只有函數執行才會產生

一般情況下,函數執行會形成一個新的私有的作用域,當私有作用域中的代碼執行完成后,我們當前作用域都會主動的進行釋放和銷毀。但當遇到函數執行返回了一個引用數據類型的值,並且在函數的外面被一個其他的東西給接收了,這種情況下一般形成的私有作用域都不會銷毀

如下面這種情況:

function fn(){
var num=100;
return function(){
  }
}
var f=fn();//fn執行形成的這個私有的作用域就不能再銷毀了

也就是像上面這段代碼,fn函數內部的私有作用域會被一直佔用的,發生了內存泄漏。所謂內存泄漏指任何對象在您不再擁有或需要它之後仍然存在。閉包不能濫用,否則會導致內存泄露,影響網頁的性能。閉包使用完了后,要立即釋放資源,將引用變量指向null

接下來我們看下有關於內存泄漏的一道經典面試題:

  function outer(){
  var num=0;//內部變量
  return function add(){//通過return返回add函數,就可以在outer函數外訪問了
  num++;//內部函數有引用,作為add函數的一部分了
  console.log(num);
  };
 }
  var func1=outer();
  func1();//實際上是調用add函數, 輸出1
  func1();//輸出2 因為outer函數內部的私有作用域會一直被佔用
  var func2=outer();
  func2();// 輸出1  每次重新引用函數的時候,閉包是全新的。
  func2();// 輸出2  

五、閉包的作用

1.可以讀取函數內部的變量

2.可以使變量的值長期保存在內存中,生命周期比較長。因此不能濫用閉包,否則會造成網頁的性能問題

3.可以用來實現js模塊

js模塊:具有特定功能的js文件,將所有的數據和功能都封裝在一個函數內部(私有的),只向外暴露一個包信n個方法的對象或函數,模塊的使用者,只需要通過模塊暴露的對象調用方法來實現對應的功能

具體請看下面的例子:

//index.html文件
<script type="text/javascript" src="myModule.js"></script>
<script type="text/javascript">
  myModule2.doSomething()
  myModule2.doOtherthing()
</script>

//myModule.js文件
(function () {
  var msg = 'Beijing'//私有數據
  //操作數據的函數
  function doSomething() {
    console.log('doSomething() '+msg.toUpperCase())
  }
  function doOtherthing () {
    console.log('doOtherthing() '+msg.toLowerCase())
  }
  //向外暴露對象(給外部使用的兩個方法)
  window.myModule2 = {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
})()

六、閉包的運用

我們要實現這樣的一個需求: 點擊某個按鈕, 提示”點擊的是第n個按鈕”,此處我們先不用事件代理:

.....
<button>測試1</button>
<button>測試2</button>
<button>測試3</button>
<script type="text/javascript">
   var btns = document.getElementsByTagName('button')
    for (var i = 0; i < btns.length; i++) {
      btns[i].onclick = function () {
        console.log('第' + (i + 1) + '個')
      }
    }
</script>  

萬萬沒想到,點擊任意一個按鈕,後台都是彈出“第四個”,這是因為i是全局變量,執行到點擊事件時,此時i的值為3。那該如何修改,最簡單的是用let聲明i

 for (let i = 0; i < btns.length; i++) {
      btns[i].onclick = function () {
        console.log('第' + (i + 1) + '個')
      }
    }

另外我們可以通過閉包的方式來修改:

   for (var i = 0; i < btns.length; i++) {
      (function (j) {
        btns[j].onclick = function () {
          console.log('第' + (j + 1) + '個')
        }
      })(i)
    }

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/10081

沐鳴娛樂怎麼樣?_讓小程序代碼包立減 10% 的插件 weapp-css-modules

作者:凹凸曼-冥冥
小程序的簡化版 css-modules,比標準 css-modules 代碼量更少的優化方案

介紹

css-modules 是一種 css 模塊化方案,它在構建過程中生成一個原類名與新類名的 map,根據 map 引用樣式,通過設定 hash 規則,實現了對 CSS 類名作用域的限定,它通常用來解決頁面類名衝突的問題。由於微信小程序內組件樣式默認隔離,為什麼要使用 css-modules 呢?

有以下 2 個原因:

  • hash 化后可以實現更短的命名,減少代碼包體積
  • 跨端項目需要兼顧非小程序環境,避免樣式衝突

weapp-css-modules 做了哪些事?

  • 新類名單字母編排,減少代碼量
  • 移除類名映射 map,替換 js 和 wxml 中變量為編譯后類名

標準 css-modules 方案:

import style from './index.wxss'
<view class="{{style.banner}}"></view> .index_banner_xkpkl { xx } module.exports ={'banner' : 'index_banner_xkpkl'} // 額外生成的 map 文件

weapp-css-modules 編譯後效果:

let style = {}
<view class="a"></view> .a { xx }

安裝

目前只開發了適用於使用 gulp 編譯小程序的 gulp 插件,後續計劃開發 webpack 可用的插件實現相同功能

npm i gulp-weapp-css-modules gulp-sort
// gulpfile.js
const { weappCssModule, wcmSortFn } = require('gulp-weapp-css-modules')
const sort = require('gulp-sort');

gulp.task('css-module', () => {
    return gulp.src('./src/**/*')
        .pipe(sort(wcmSortFn))      // 由於處理文件有順序依賴,需要先對文件排序
        .pipe(weappCssModule())
        .pipe(gulp.dest('./dist'))
})

使用

小程序頁面不具備隔離功能,因此只有具備樣式隔離的 Component 可以改造使用 weapp-css-modules

1、css 文件改名字: weapp-css-modules 通過 css 文件是否帶 module 來識別需要替換的內容

index.wxss -> index.module.wxss

// 或者使用 scss/其他

index.scss -> index.module.scss

2、js 內新增樣式文件的引入,目的是建立 css-modules 的樣式與 js 關係

import styles from './index.module.wxss data:{ ..., styles:styles }

3、修改 js 內類名的地方替換為 styles 的間接引入

query.select('.banner')
.boundingClientRect()
.exec(function (res) {...})

// 改為
query.select('.' + styles['banner'])
.boundingClientRect()
.exec(function (res) {...})

4、修改 wxml 內類名的使用

4.1. 普通類名

<view class="banner"></view> // 改為 <view class="{{styles.banner}}"></view>
// 或者
<view class="{{styles['banner']}}"></view>

4.2. 三目運算符

<view class="banner__dot {{ 'banner__dot--' + (index == swiperCurrent ? 'cur' : '')}"></view> // 改為 <view class="{{styles['banner__dot'] + ' ' + (index == swiperCurrent ? styles['banner__dot--cur'] : '')}}"></view>
// 或者
<view class="{{`${style['banner__dot']} ${index == swiperCurrent ? style['banner__dot--cur'] : ''}`}}"></view>

這裏需要注意幾種有問題的寫法:

4.2.1. 類名間未加空格

<view class="{{styles['banner__dot'] + (index == swiperCurrent ? styles['banner__dot--cur'] : '')}}"></view>

4.2.2. 三目表達式未加括號,運算優先級不明

<view class="{{styles['banner__dot'] + ' ' + index == swiperCurrent ? styles['banner__dot--cur'] : ''}}"></view>

4.2.3. styles 的屬性需要是具體的字符串,不能使用變量表達式(這是 weapp-css-modules 需要單獨關注的地方,因為編譯階段會對 styles.xx 進行求值,所以不能把表達式寫在屬性位置)

<view class="{{styles['banner__dot'] + ' ' + styles[index == swiperCurrent ? 'banner__dot--cur': '']}}"></view>

5、構建過程中關註腳本的紅色提示,類似於這種:

這是由於在 js/wxml 內使用了一個banner__swiper_2,而 css 內並沒有定義banner__swiper_2,css-module 編譯的 map 文件是根據 css 內的樣式定義來生成 key 名的,因此styles[‘banner__swiper_2’]是undefined, 針對這種情況有兩種處理方式:

5.1. 如果 js 內需要通過這個類名選擇到某個元素,但是 css 內不需要編寫樣式,那麼可以將它視為不需要編譯的類名,即:

query.selector('.banner__swiper_2') // 不改成 styles.xx 的寫法
<view class="banner__swiper_2"></view> // 相應的元素也不索引到 styles // 這樣實現了一個組件內不會被編譯的樣式

5.2. 如果 js 內無引用,那麼刪掉 wxml 內該類名的定義吧~

6、構建完進行檢查,關注樣式和交互是否正常

參考示例

gulp 項目:路徑 /demo/gulp-project-demo

聯繫反饋

  • 歡迎通過郵箱來跟我聯繫: smile123ing@163.com
  • 歡迎通過 GitHub issue 提交 BUG、以及其他問題
  • 歡迎給該項目點個贊 ⭐️ star on GitHub ! 點擊文末「閱讀原文」直達,送出 Star

歡迎關注凹凸實驗室博客:aotu.io

或者關注凹凸實驗室公眾號(AOTULabs),不定時推送文章。

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/9877

沐鳴平台註冊登錄_javascript 中==和===的區別

對於JavaScript中比較運算符,可能大家用的比較多的是“==”、對於“===”很多人可能很陌生。=== 表示恆等,首先比較兩邊的變量數據類型是否相等,其次比較兩邊的變量的數值是否相等;== 表示相等即僅僅比較兩邊變量的數值是否相等。

 

一、“===”首先計算其操作數的值,然後比較這兩個值,比較過程沒有任何類型轉換

1、如果兩個值類型不相同,則它們不相等。

2、如果兩個值都是null或者都是undefined,則它們不相等。

3、如果兩個值都是布爾值true或false,則它們相等。

4、如果其中一個值是NaN,或者兩個兩個值都是NaN,則它們不相等。NaN和其他任何值都是不相等的,包括它本身!!!通過x!==x來判斷x是否為NaN,只有在x為NaN的時候,這個表達式的值才為true。

5、如果兩個值為数字,且數值相等,則它們相等。如果一個為0,另一個為-0,則它們同樣相等。

6、如果兩個值為字符串,且所含的對應位上的16位數完全相等,則它們相等。如果它們的長度或內容不同,則它們不等。兩個字符串可能含義完全一樣且所显示出手字符也一樣,但具有不同編碼的16位值。JavaScript並不對Unicode進行標準化的轉換,因此像這樣的字符串通過”===”和”==”運算符的比較結果也不相等。

7、如果兩個引用值同一個對象、數組或函數,則它們是相等的。如果指向不同的對象,則它們是不等的。儘管兩個對象具有完全一樣的屬性。

例如

var param1= ‘1’, param2 = ‘1’ ; 
param1 === param2; //類型和數值同時相等 true
var param3 = 1;
param1 === param3; //類型不相等和數值相等false
var param4 = 2;
param1 === param4; //類型和數值都不相等 false

var param1 = null, param2 = undefined;
param1 === param2; //false

二、 相等運算符“==”如果兩個操作數不是同一類型,那麼相等運算符會嘗試一些類型轉換,然後進行比較

1、如果一個值是null,另一個是undefined,則它們相等。

2、如果一個值是数字,另一個是字符串,先將字符串轉換為数字,然後使用轉換后的值比較。

3、如果其中一個值是true,則將其轉換為1再進行比較。如果其中一個值是false,則將基轉換為0再進行比較。

4、如果一個值是對象,另一個值是数字或字符串,則將對象轉換為原始值,然後再進行比較。對象通過toString()方法或valueOf()方法轉換為原始值。JavaScript核心的內置類首先嘗試使用valueOf(),再嘗試使用toString(),除了日期類,日期類只使用toString()轉換。那些不是JavaScript語言核心中的對象則通過各自的實現中定義的方法轉換為原始值。

5、其他不同類型之間的比較均不相等.

例如

var param1= ‘1’, param2 = ‘1’ ; 
param1 == param2; //類型和數值同時相等true
var param3 = 1;
param1 == param3; //類型不相等和數值相等true
var param4 = 2;
param1 == param4; //類型和數值都不相等false

var param1 = null, param2 = undefined;
param1 === param2; //true

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/9874

沐鳴平台網址_JavaScript 代碼加不加分號有什麼區別

這個問題在很多文章中都討論過,在 ESlint 規範中也因為加不加分號而分為兩大陣營,到於加不加分號,關鍵是需要了解分號對於 JavaScript 的影響,開始之前可以先看看下面這道面試題:

請問這段代碼是否能夠正常運行?

var a = 1
(function() {
  console.log(2)
})()

如果運行這段代碼,會出現下面的錯誤:

Uncaught TypeError: 1 is not a function

什麼鬼!1 is not a function ?我們沒有打算運行数字 1,為什麼說数字 1 不是函數,這種錯誤是很難找到原因的,經常會在出問題的代碼行上打轉。這個錯誤必然是上嗎的代碼在運行時被看作是同一行,其概念如下:

var a = 1(function() { /* */ })()

因此立即函數的 () 被附加在 1 上,這是一個調用函數的語法,所以會產生 1 is not a function 的錯誤,想要避免這個錯誤就需要使用分號:

var a = 1 // 隨便把分號放在哪裡,只要能隔開就行
;(function() {
  console.log(2)
})()

ASI 自動加入分號

ASI是 “Automatic Semicolon Insertion” 的縮寫,在運行時會往有些折行的代碼中自動插入分號,這個機制可以使部分代碼在沒有加入分號時也能正常運行,比如下面的例子:

var b = 1
++b
console.log('b', b)

由於代碼中的 ++ 屬於一元表達式,它只能在表達式的左邊右邊放置變量,如果沒有 ASI 的機制,代碼會被轉換為 var b = 1 ++ b 這樣的錯誤語句。不過好在有 ASI,在實際運行時會自動被加入分號,也就不會出現上面的錯誤。

var b = 1;
++b;
console.log('b', b); // 2

return 與分號的關係

再來看一個例子,下面的代碼在 return 的後面空一行后再寫要返回的值,那麼問運行結果是什麼呢?

function fn() {
  return 
  '小明'
}
console.log(fn())

這段程序代碼因為 ASI 的修正,return 的後面會被加上一個分號,所以 return 與預期返回的值被分開了,結果 return 的內容為空值,最終的結果也只能是 undefined 。

function fn() {
  return;
  '小明';
}
console.log(fn()); // undefined

到底應該怎樣處理分號

本來 ASI 是出於一片好心,用來修正沒有加入分號的代碼片段,但偏偏在有的地方並沒有發揮它的作用(例如本文一開始所介紹的立即函數),導致代碼出現了錯誤;甚至有些代碼不會出錯,但會使你的代碼執行結果和預期相差萬里。

解決 ASI 問題的方式如下:

無論如何都要加上分號,完全由自己決定代碼的分割

牢記不會自動加入分號的規則,當不會自動插入分號時,則手動加入

不會被自動加入分號的規則

下面時各種不會自動加入分號的規則:

新行的代碼是從 (、[、/ 字符開始的,這類情況一般會直接出現 Uncaught TypeError 從而導致代碼無法運行。

var a = 1
var b = a
(a + b).toString()

var a = 1
[1,2,3].forEach(bar)
 
(function() { })()
(function() { })()
 
var a = 1
var b = a
/test/.test(b)

新行以 +,-,*,% 開始,這類情況大多會影響運算結果,所以應該合併為一行。

var a = 2
var b = a
+a

新行以 , 或 . 開始,這種用法經常會出現,主要是為了避免代碼過長而加入的分隔,這種情況並不會影響運行,如果善用的話會使代碼更容易閱讀。

var a = 2
var b = a
  .toString()
console.log(typeof b)
 
var a = 1
,b = 2 // b 同樣會被 var 聲明

如果遇到需要加入分號的情況,除了可以在語句的末尾加入分號外,也可以把分號加在“不會自動加入分號”的最前方,例如 () 本身不會自動加入分號,在有這種需求時可以將 ; 加到前面(ESLint Standard JS 規範就用這個方法避免錯誤)。

// 運行錯誤
(function() { })()
(function() { })()
 
// 正確
;(function() { })()
;(function() { })()

總結

有的人認為不加分號可以讓代碼看起來更乾淨和精簡,而且在大部分情況下並不會出現錯誤,所以很多人在敲代碼時不會加分號。

不過我更傾向於更嚴格的規範,也許是因為我是從後端轉到前端的,習慣了。至於到底怎麼選,只要搞清楚運行上的限制,不管哪種風格都是挺不錯的,只要你喜歡就好。

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/9876

沐鳴怎麼當代理?_調用函數時到底可以傳多少個參數

在創建一個函數並調用時可以傳入一些參數或變量,不過函數究竟可以有多少可用的參數呢?

函數中的變量

以下通過一個例子來檢查在調用函數時會有哪些變量和參數,在這裏可以在瀏覽器的“無痕窗口”中直接運行這段代碼(無痕窗口能避免瀏覽器插件影響運行)。

注意:這裏僅僅針對傳統函數,箭頭函數的運行變量與傳統函數不同,在此不做討論。

var globalvariable = '全局變量';
var obj = {
  aFunction: function (para) {
    var localVariables = '局部變量';
    console.log(para, localVariables, arguments, this, globalVariable);
    // 包含傳入的參數
  }
}
obj.aFunction('我是一段描述', 2, 3);

在運行時切換到 Source 頁,並用 Chrome 的 JavaScript 調試模式來進行驗證。

接下來進入到 aFunction 的函數中時,可以切換到 console.log(…) 一這行停下來,結果如下:

接下來會看到 Scope 表示當前函數的作用域,作用域中可調用的變量也會按順序列出,在這裏可以看到的變量和參數包含:

  • para: 調用時傳入的參數。
  • arguments: 傳統函數默認會帶入的外部參數,這個參數來自於外部所傳入的參數,就算函數本身沒有傳入也可以在這裏取值,本文後方會有更詳細的介紹。
  • this: 函數運行時傳入的變量,調用函數的方式將會影響它的指向,以後我會另外再寫一篇文章進行介紹。
  • localVariables: 局部變量,僅在此函數內的作用域才可以調用。
  • Global: 全局變量

以上五個變量中,前兩個就屬於在調用時傳入的變量,本文也會着重介紹它們的特點。

參數的性質

參數是由主調函數傳入的變量,相對於其它編程語言來說, js 傳遞參數的限制更少,任何值都可以作為參數,也正是因為如此,初次接觸或從其它語言轉過來的會有許多不熟悉的地方,以下是一些常見的問題:

參數名稱是在函數聲明時定義的

這是一個非常基礎的問題,剛開始學編程的新手經常會把參數名搞混,誤以為調用時傳入的參數名就是在函數內使用的名稱,比如下面的例子輸出的值是什麼?

A. ‘a’, ‘b’, ‘c’, undefined
B. ‘d’, ‘c’, ‘b’, ‘a’

function callMore(d, c, b, a) {
  console.log(d, c, b, a);
}
var a = 'a';
var b = 'b';
var c = 'c';
callMore(a, b, c);

函數參數的名稱是在定義函數時就已經確定了的,如下圖:參數取值是按順序帶入,並且不會受到調用時名稱的影響

簡單的結論:

  • 參數名稱不會受到調用名的影響
  • 如果遇到聲明了卻沒有傳入值的參數,那麼是 undefined
  • 如果未定義,但有更多的參數傳入則需要使用其它方式取值

ES6 的參數默認值

如果已經定義了參數,但卻沒有傳入,那麼函數內取到的值為 undefined ,如果遇到這樣的情況,代碼就必須加入許多的容錯機制,以避免 undefined 造的錯誤。

ES6 中新增了“參數默認值”可預先給特定參數設置默認值,除了避免 undefined 所造成的錯誤外,還可以增加使用函數的彈性。

下面的例子通過簡單的語法就可加上“參數默認值”,當外部沒有傳入值時就會使用默認值。

function getMoney(money = 1000) {
  console.log(`我有 ${ money }`);
}
getMoney(); // 不需要傳入參數

arguments

如果無法確認所傳入參數的數量該怎麼辦?

這種情況在項目開發中比較少見,但在開發框架、函數庫時卻很常見,前面所提到的 “如果未定義,但有更多的參數傳入則需要使用其它方式取值”的情況 ,就會用到下面將要介紹的 arguments 參數(ES6 中有更好的方法)。

function callMore(d, c, b, a) {
  // 注意:在此並沒有用到定義的 d, c, b, a 參數
  console.log(arguments);
}
var a = 'a';
var b = 'b';
var c = 'c';
callMore(a, b, c);

所有的傳統函數都有 arguments 參數(注意:箭頭函數沒有),不需要另外定義即可直接調用,且作用域僅限於本函數中。

arguments 的結構本質上是一個數組,其中會包含調用時傳入的所有值,在不確定傳入參數的數量時,是一個很好用的方法。

ES6 剩餘參數

因為 arguments 有一些缺點,如:

  • 結構類似數組,但是無法直接使用大部分的數組方法
  • 與已經定義的參數內容重疊,無法僅用於額外傳入的參數

所以 ES6 新增了剩餘參數語法,可在定義參數時直接傳入剩餘的未定的參數,語法如下:

const callMore = (a, ...args) => {
  console.log(args);
}
var a = 'a';
var b = 'b';
var c = 'c';
callMore(a, b, c);

結果和前面的例子類似,但會是純數組的方式展現,並且只會獲取未定義的參數內容(a 會被跳過)。另外,箭頭函數是可以使用剩餘參數的。

函數也可作為參數

函數除了可以傳入純值、數組和對象外,還可以把函數作為參數,使函數運行時更加豐富而且——呃。。。複雜。

下面的例子中,在調用 functionB 時,還可以傳入另一個函數作為參數,這種手段叫回調函數( callback function)。

function functionB(fn) {
  fn('小明');
}
functionB(function(name) {
  console.log(name + ' 您好');
});

你自己可以試着看看其中的參數是如何傳遞的

JavaScript 可將函數作為參數傳遞這樣的特性也稱為“一級函數”(First-class Function)

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/9870

    沐鳴平台_iframe無刷新提交表單,iframe仿ajax提交表單

    本文摘要

    使用ajax可以實現無刷新提交表單,但有人表示ajax的效率不行,或者是其他缺點,例如客戶端臃腫,太多客戶段代碼造成開發上的成本,如果網速慢,則會出現ajax請求緩慢,頁面空白的情況,對客戶的體驗不好。ajax請求不利於搜索引擎優化,一般搜不到ajax添加到頁面的信息。

    這次就介紹一下iframe仿造ajax異步請求,實際上iframe是同步請求,只是把提交的跳轉,發生在iframe的可視區域內。

    代碼

    index.html

    <!DOCTYPE html>
    <html>
    <head>
        <title>iframe提交表單</title>
        <meta charset="utf-8">
        <style type="text/css"> #result{ border: none; /*去掉默認的邊框*/ width: 300px; /*可視區域的寬度*/ height: 60px; /*可視區域的高度*/ } </style>
    </head>
    <body>
    <!-- 表單 -->
    <h1>iframe提交表單</h1>
    <form action="check.php" method="post" target='result'>
        <input type="text" class="input_css" name="user" placeholder="請輸入賬號"><br/>
        <input type="password" class="input_css" name="pwd" placeholder="請輸入密碼"><br/>
        <input type="submit" class="formbtn" value="登陸"><br/>
    </form>
    
    <!-- 用於查看提交結果 -->
    <iframe name='result' id="result" scrolling="no"></iframe>
    </body>
    </html>

    check.php

    <style type="text/css"> *{ margin:0; padding:0; } </style>
    <?php // 設置編碼 header("Content-type:text/html;charset=utf-8"); // 獲得POST過來的登陸所需參數 $user = $_POST["user"]; $pwd = $_POST["pwd"]; // 過濾參數 if ($user == '' && $pwd == '') { echo "<p style='color:#f00;font-size:15px;margin-top:10px;'>賬號和密碼不得為空</p>"; }else if ($user == '' ) { echo "<p style='color:#f00;font-size:15px;margin-top:10px;'>賬號不得為空</p>"; }else if ($pwd == '' ) { echo "<p style='color:#f00;font-size:15px;margin-top:10px;'>密碼不得為空</p>"; }else{ echo "<p style='color:#000;font-size:15px;margin-top:10px;'>你提交的賬號是:".$user."<br/>你提交的密碼是:".$pwd."</p>"; } ?>

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/10086

    沐鳴開戶_發布Npm包到GitHub Packages

    Github集成了GitHub Packages功能,目前提供了npm、Docker、Maven、NuGet、RubyGems的包管理工具,可以通過Github管理開源包,本文主要介紹使用GitHub Packages發布Npm包。

    發布

    首先需要製作一個package.json文件,可以通過npm init命令根據提示生成一個package.json文件。


    這是已經發布好的package.json文件,作為示例,需要注意的是name字段、publishConfig字段與repository字段的配置,在GitHub Packages發布的包屬於作用域包,需要使用@username/package-name的形式作為name字段,publishConfig是發布到GitHub Packages的必填且值固定的字段,repository字段是必須要指定的倉庫url,可以發布多個包到一個倉庫,可以參考https://github.com/WindrunnerMax/Asse/packages/292805。

    `{ "name": "@WindrunnerMax/mini-program-cli", "version": "1.1.0", "description": "Uniapp小程序開發腳手架", "author": "Czy", "license": "MIT", "bin": { "mini-program": "bin/cli.js" }, "scripts": { "test": "echo "Please use HbuildX import this project"" }, "engines": { "node": ">= 8" }, "publishConfig": { "registry": "https://npm.pkg.github.com/WindrunnerMax" }, "repository": "https://github.com/WindrunnerMax/Asse" }` 

    下面需要授權,首先在Github申請一個Token,user – setting – Developer settings – Personal access tokens – Generate new token,生成一個用以發布Npm包的Token,需要選擇權限,以下權限必選
    hub申請一個Token,user – setting – Developer settings – Personal access tokens – Generate new token,生成一個用以發布Npm包的Token,需要選擇權限,以下權限必選:

    接下來將Token添加至~/.npmrc,win用戶是路徑C://users/current-user。

    //npm.pkg.github.com/:_authToken=TOKEN

    或者使用npm login命令進行授權,注意用戶名要全部小寫,Token的輸入是以密碼的方式輸入,不會显示。

    npm login --registry=https://npm.pkg.github.com
    > Username: USERNAME
    > Password: TOKEN
    > Email: PUBLIC-EMAIL-ADDRESS`

    接下來在項目的根目錄添加一個~/.npmrc文件,並添加如下配置。

    registry=https://npm.pkg.github.com/WindrunnerMax

    接下來就可以使用npm publish命令發布包。

    npm publish --access=public 

    安裝

    需要注意的是,無論發布包還是安裝包都需要授權,也就是上述生成Token以及配置的過程,否則無法安裝指定的包,以我發布的包為例,執行安裝。

    npm install -g @windrunnermax/mini-program-cli@1.1.0 --registry=https://npm.pkg.github.com/

    如果使用Github安裝Npm包很慢的話,可以考慮配置代理,同樣是在~/.npmrc文件中加入配置。

    proxy=socks5://127.0.0.1:1080
    https-proxy=socks5://127.0.0.1:1080`

    Github

    https://github.com/WindrunnerMax

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/10076

    沐鳴登錄測速_JS判斷單、多張圖片加載完成

    在實際的運用中有這樣一種場景,某資源加載完成后再執行某個操作,例如在做導出時,後端通過打開模板頁生成PDF,並返回下載地址。這時前後端通常需要約定一個flag,用以標識模板準備就緒,可以生成PDF了。

    試想,如果模板中有圖片,此時如何判斷圖片是否加載完成?

    在此之前來了解一下jquery的ready與window.onload的區別,ready只是dom結構加載完畢,便視為加載完成。(此時圖片沒有加載完畢),onload是指dom的生成和資源完全加載(比如flash、圖片)出來后才執行。接下來回到正題,先從單張圖片說起。

    (1)、單張圖片(圖片在文檔中)

    // html
    <img id='xiu' src="http://www.daqianduan.com/wp-content/uploads/2014/11/hs-xiu.jpg">  
    
    //js
     $(document).ready(function(){
    
        //jquery
        $('#xiu').load(function(){
           // 加載完成 
        });
    
       //原生  onload
        var xiu = document.getElementById('xiu')
        xiu.onload = xiu.onreadystatechange = function(){
           if(!this.readyState||this.readyState=='loaded'||this.readyState=='complete'){
               // 加載完成 
           }
        };
    
    })

    注:
    1、IE8及以下版本不支持onload事件,但支持onreadystatechange事件;
    2、readyState是onreadystatechange事件的一個狀態,值為loaded或complete的時候,表示已經加載完畢。
    3、以下內容省略兼容

    (2)、單張圖片(圖片動態生成)

    //js
     var xiu = new Image()
     xiu.src = 'http://www.daqianduan.com/wp-content/uploads/2014/11/hs-xiu.jpg'
     xiu.onload = function(){
        // 加載完成 
     }

    (3)、單張圖片(結合ES6 Promise)

    //js
     new Promise((resolve, reject)=>{
        let xiu = new Image()
        xiu.src = 'http://www.daqianduan.com/wp-content/uploads/2014/11/hs-xiu.jpg'
        xiu.onload = function(){
           // 加載完成 
           resolve(xiu)
        }
     }).then((xiu)=>{
     //code
     })

    (4)、多張圖片

    var img = [],  
        flag = 0, 
        mulitImg = [
        'http://www.daqianduan.com/wp-content/uploads/2017/03/IMG_0119.jpg',
        'http://www.daqianduan.com/wp-content/uploads/2017/01/1.jpg',
        'http://www.daqianduan.com/wp-content/uploads/2015/11/jquery.jpg',
        'http://www.daqianduan.com/wp-content/uploads/2015/10/maid.jpg'
     ];
     var imgTotal = mulitImg.length;
     for(var i = 0 ; i < imgTotal ; i++){
        img[i] = new Image()
        img[i].src = mulitImg[i]
        img[i].onload = function(){
           //第i張圖片加載完成
           flag++
           if( flag == imgTotal ){
              //全部加載完成
           }
        }
     }

    (5)、多張圖片(結合ES6 Promise.all())

      let mulitImg = [
         'http://www.daqianduan.com/wp-content/uploads/2017/03/IMG_0119.jpg',
         'http://www.daqianduan.com/wp-content/uploads/2017/01/1.jpg',
         'http://www.daqianduan.com/wp-content/uploads/2015/11/jquery.jpg',
         'http://www.daqianduan.com/wp-content/uploads/2015/10/maid.jpg'
     ];
     let promiseAll = [], img = [], imgTotal = mulitImg.length;
     for(let i = 0 ; i < imgTotal ; i++){
         promiseAll[i] = new Promise((resolve, reject)=>{
             img[i] = new Image()
             img[i].src = mulitImg[i]
             img[i].onload = function(){
                  //第i張加載完成
                  resolve(img[i])
             }
         })
     }
     Promise.all(promiseAll).then((img)=>{
         //全部加載完成
     })

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/10016

    沐鳴主管:_css性能優化-will-change

    will-change通過告知瀏覽器該元素會有哪些變化,使瀏覽器提前做好優化準備,增強頁面渲染性能。

    屬性的取值:

    1、auto:  實行標準瀏覽器優化。

    2、scroll-position:  表示開發者希望在不久后改變滾動條的位置或者使之產生動畫。

    3、contents:  表示開發者希望在不久后改變元素內容中的某些東西,或者使它們產生動畫。

    4、<custom-ident>:  表示開發者希望在不久后改變指定的屬性名或者使之產生動畫,比如transform 或 opacity。

    使用須知:

    1、不要將 will-change 應用到太多元素上,如果過度使用的話,可能導致頁面響應緩慢或者消耗非常多的資源。

    2、通常,當元素恢復到初始狀態時,瀏覽器會丟棄掉之前做的優化工作。但是如果直接在樣式表中顯式聲明了 will-change 屬性,則表示目標元素可能會經常變化,瀏覽器會將優化工作保存得比之前更久。所以最佳實踐是使用完后及時清除。

    3、如果你的頁面在性能方面沒什麼問題,則不要添加 will-change 屬性來榨取一丁點的速度。 will-change 的設計初衷是作為最後的優化手段,用來嘗試解決現有的性能問題,它不應該被用來預防性能問題。

    兼容性:


    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/10015