中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JavaScript中各種源碼是怎樣實現的

發布時間:2021-09-30 17:58:49 來源:億速云 閱讀:152 作者:柒染 欄目:web開發

這期內容當中小編將會給大家帶來有關JavaScript中各種源碼是怎樣實現的,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

 前言

最近很多人和我一樣在積極地準備前端的面試筆試,所以我也就整理了一些前端面試筆試中非常容易被問到的原生函數實現和各種前端原理實現。

能夠手寫實現各種JavaScript原生函數,可以說是擺脫API調用師帽子的第一步,我們不光要會用,更要去探究其實現原理!

對JavaScript源碼的學習和實現能幫助我們快速和扎實地提升自己的前端編程能力。

實現一個new操作符

我們首先知道new做了什么:

  1. 創建一個空的簡單JavaScript對象(即{});

  2. 鏈接該對象(即設置該對象的構造函數)到另一個對象 ;

  3. 將步驟(1)新創建的對象作為this的上下文 ;

  4. 如果該函數沒有返回對象,則返回this。

知道new做了什么,接下來我們就來實現它

function create(Con, ...args){   // 創建一個空的對象   this.obj = {};   // 將空對象指向構造函數的原型鏈   Object.setPrototypeOf(this.obj, Con.prototype);   // obj綁定到構造函數上,便可以訪問構造函數中的屬性,即this.obj.Con(args)   let result = Con.apply(this.obj, args);   // 如果返回的result是一個對象則返回   // new方法失效,否則返回obj   return result instanceof Object ? result : this.obj; }

實現一個Array.isArray

Array.myIsArray = function(o) {    return Object.prototype.toString.call(Object(o)) === '[object Array]';  };

實現一個Object.create()方法

function create =  function (o) {     var F = function () {};     F.prototype = o;     return new F(); };

實現一個EventEmitter

真實經歷,最近在字節跳動的面試中就被面試官問到了,讓我手寫實現一個簡單的Event類。

class Event {   constructor () {     // 儲存事件的數據結構     // 為查找迅速, 使用對象(字典)     this._cache = {}   }    // 綁定   on(type, callback) {     // 為了按類查找方便和節省空間     // 將同一類型事件放到一個數組中     // 這里的數組是隊列, 遵循先進先出     // 即新綁定的事件先觸發     let fns = (this._cache[type] = this._cache[type] || [])     if(fns.indexOf(callback) === -1) {       fns.push(callback)     }     return this     }    // 解綁   off (type, callback) {     let fns = this._cache[type]     if(Array.isArray(fns)) {       if(callback) {         let index = fns.indexOf(callback)         if(index !== -1) {           fns.splice(index, 1)         }       } else {         // 全部清空         fns.length = 0       }     }     return this   }   // 觸發emit   trigger(type, data) {     let fns = this._cache[type]     if(Array.isArray(fns)) {       fns.forEach((fn) => {         fn(data)       })     }     return this   }    // 一次性綁定   once(type, callback) {     let wrapFun = () => {       callback.call(this);       this.off(type, callback);     };     this.on(wrapFun, callback);     return this;   } }  let e = new Event()  e.on('click',function(){   console.log('on') }) e.on('click',function(){   console.log('onon') }) // e.trigger('click', '666') console.log(e)

實現一個Array.prototype.reduce

首先觀察一下Array.prototype.reduce語法

Array.prototype.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

然后就可以動手實現了:

Array.prototype.myReduce = function(callback, initialValue) {   let accumulator = initialValue ? initialValue : this[0];   for (let i = initialValue ? 0 : 1; i < this.length; i++) {     let _this = this;     accumulator = callback(accumulator, this[i], i, _this);   }   return accumulator; };  // 使用 let arr = [1, 2, 3, 4]; let sum = arr.myReduce((acc, val) => {   acc += val;   return acc; }, 5);  console.log(sum); // 15

實現一個call或apply

先來看一個call實例,看看call到底做了什么:

let foo = {   value: 1 }; function bar() {   console.log(this.value); } bar.call(foo); // 1

從代碼的執行結果,我們可以看到,call首先改變了this的指向,使函數的this指向了foo,然后使bar函數執行了。

總結一下:

  1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

  2. call改變函數this指向

  3. 調用函數

思考一下:我們如何實現上面的效果呢?代碼改造如下:

Function.prototype.myCall = function(context) {   context = context || window;   //將函數掛載到對象的fn屬性上   context.fn = this;   //處理傳入的參數   const args = [...arguments].slice(1);   //通過對象的屬性調用該方法   const result = context.fn(...args);   //刪除該屬性   delete context.fn;   return result };

我們看一下上面的代碼:

  1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

  2. 首先我們對參數context做了兼容處理,不傳值,context默認值為window;

  3. 然后我們將函數掛載到context上面,context.fn = this;

  4. 處理參數,將傳入myCall的參數截取,去除第一位,然后轉為數組;

  5. 調用context.fn,此時fn的this指向context;

  6. 刪除對象上的屬性 delete context.fn;

  7. 將結果返回。

以此類推,我們順便實現一下apply,唯一不同的是參數的處理,代碼如下:

Function.prototype.myApply = function(context) {   context = context || window   context.fn = this   let result   // myApply的參數形式為(obj,[arg1,arg2,arg3]);   // 所以myApply的第二個參數為[arg1,arg2,arg3]   // 這里我們用擴展運算符來處理一下參數的傳入方式   if (arguments[1]) {     result = context.fn(&hellip;arguments[1])   } else {     result = context.fn()   }   delete context.fn;   return result };

以上便是call和apply的模擬實現,唯一不同的是對參數的處理方式。

實現一個Function.prototype.bind

function Person(){   this.name="zs";   this.age=18;   this.gender="男" } let obj={   hobby:"看書" } //  將構造函數的this綁定為obj let changePerson = Person.bind(obj); //  直接調用構造函數,函數會操作obj對象,給其添加三個屬性; changePerson(); //  1、輸出obj console.log(obj); //  用改變了this指向的構造函數,new一個實例出來 let p = new changePerson(); // 2、輸出obj console.log(p);

仔細觀察上面的代碼,再看輸出結果。

我們對Person類使用了bind將其this指向obj,得到了changeperson函數,此處如果我們直接調用changeperson會改變obj,若用new調用changeperson會得到實例  p,并且其__proto__指向Person,我們發現bind失效了。

我們得到結論:用bind改變了this指向的函數,如果用new操作符來調用,bind將會失效。

這個對象就是這個構造函數的實例,那么只要在函數內部執行 this instanceof 構造函數  來判斷其結果是否為true,就能判斷函數是否是通過new操作符來調用了,若結果為true則是用new操作符調用的,代碼修正如下:

// bind實現 Function.prototype.mybind = function(){   // 1、保存函數   let _this = this;   // 2、保存目標對象   let context = arguments[0]||window;   // 3、保存目標對象之外的參數,將其轉化為數組;   let rest = Array.prototype.slice.call(arguments,1);   // 4、返回一個待執行的函數   return function F(){     // 5、將二次傳遞的參數轉化為數組;     let rest2 = Array.prototype.slice.call(arguments)     if(this instanceof F){       // 6、若是用new操作符調用,則直接用new 調用原函數,并用擴展運算符傳遞參數       return new _this(...rest2)     }else{       //7、用apply調用第一步保存的函數,并綁定this,傳遞合并的參數數組,即context._this(rest.concat(rest2))       _this.apply(context,rest.concat(rest2));     }   } };

實現一個JS函數柯里化

Currying的概念其實并不復雜,用通俗易懂的話說:只傳遞給函數一部分參數來調用它,讓它返回一個函數去處理剩下的參數。

function progressCurrying(fn, args) {      let _this = this     let len = fn.length;     let args = args || [];      return function() {         let _args = Array.prototype.slice.call(arguments);         Array.prototype.push.apply(args, _args);          // 如果參數個數小于最初的fn.length,則遞歸調用,繼續收集參數         if (_args.length < len) {             return progressCurrying.call(_this, fn, _args);         }          // 參數收集完畢,則執行fn         return fn.apply(this, _args);     } }

手寫防抖(Debouncing)和節流(Throttling)

節流

防抖函數 onscroll 結束時觸發一次,延遲執行

function debounce(func, wait) {   let timeout;   return function() {     let context = this; // 指向全局     let args = arguments;     if (timeout) {       clearTimeout(timeout);     }     timeout = setTimeout(() => {       func.apply(context, args); // context.func(args)     }, wait);   }; } // 使用 window.onscroll = debounce(function() {   console.log('debounce'); }, 1000);

節流

節流函數 onscroll 時,每隔一段時間觸發一次,像水滴一樣

function throttle(fn, delay) {   let prevTime = Date.now();   return function() {     let curTime = Date.now();     if (curTime - prevTime > delay) {       fn.apply(this, arguments);       prevTime = curTime;     }   }; } // 使用 var throtteScroll = throttle(function() {   console.log('throtte'); }, 1000); window.onscroll = throtteScroll;

手寫一個JS深拷貝

乞丐版

JSON.parse(JSON.stringfy));

非常簡單,但缺陷也很明顯,比如拷貝其他引用類型、拷貝函數、循環引用等情況。

基礎版

function clone(target){   if(typeof target === 'object'){     let cloneTarget = {};     for(const key in target){       cloneTarget[key] = clone(target[key])     }     return cloneTarget;   } else {     return target   } }

寫到這里已經可以幫助你應付一些面試官考察你的遞歸解決問題的能力。但是顯然,這個深拷貝函數還是有一些問題。

一個比較完整的深拷貝函數,需要同時考慮對象和數組,考慮循環引用:

function clone(target, map = new WeakMap()) {   if(typeof target === 'object'){     let cloneTarget = Array.isArray(target) ? [] : {};     if(map.get(target)) {       return target;     }     map.set(target, cloneTarget);     for(const key in target) {       cloneTarget[key] = clone(target[key], map)     }     return cloneTarget;   } else {     return target;   } }

實現一個instanceOf

原理: L 的 proto 是不是等于 R.prototype,不等于再找 L.__proto__.__proto__ 直到 proto 為  null

// L 表示左表達式,R 表示右表達式 function instance_of(L, R) {     var O = R.prototype;   L = L.__proto__;   while (true) {         if (L === null){             return false;         }         // 這里重點:當 O 嚴格等于 L 時,返回 true         if (O === L) {             return true;         }         L = L.__proto__;   } }

實現原型鏈繼承

function myExtend(C, P) {     var F = function(){};     F.prototype = P.prototype;     C.prototype = new F();     C.prototype.constructor = C;     C.super = P.prototype; }

實現一個async/await

原理

就是利用 generator(生成器)分割代碼片段。然后我們使用一個函數讓其自迭代,每一個yield 用 promise 包裹起來。執行下一步的時機由  promise 來控制

實現

function _asyncToGenerator(fn) {   return function() {     var self = this,       args = arguments;     // 將返回值promise化     return new Promise(function(resolve, reject) {       // 獲取迭代器實例       var gen = fn.apply(self, args);       // 執行下一步       function _next(value) {         asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value);       }       // 拋出異常       function _throw(err) {         asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err);       }       // 第一次觸發       _next(undefined);     });   }; }

實現一個Array.prototype.flat()函數

最近字節跳動的前端面試中也被面試官問到,要求手寫實現。

Array.prototype.myFlat = function(num = 1) {   if (Array.isArray(this)) {     let arr = [];     if (!Number(num) || Number(num) < 0) {       return this;     }     this.forEach(item => {       if(Array.isArray(item)){         let count = num         arr = arr.concat(item.myFlat(--count))       } else {         arr.push(item)       }       });     return arr;   } else {     throw tihs + ".flat is not a function";   } };

實現一個事件代理

這個問題一般還會讓你講一講事件冒泡和事件捕獲機制

<ul id="color-list">     <li>red</li>     <li>yellow</li>     <li>blue</li>     <li>green</li>     <li>black</li>     <li>white</li>   </ul>   <script>     (function () {       var color_list = document.getElementById('color-list');       color_list.addEventListener('click', showColor, true);       function showColor(e) {         var x = e.target;         if (x.nodeName.toLowerCase() === 'li') {           alert(x.innerHTML);         }       }     })();   </script>

實現一個雙向綁定

Vue 2.x的Object.defineProperty版本

// 數據 const data = {   text: 'default' }; const input = document.getElementById('input'); const span = document.getElementById('span'); // 數據劫持 Object.defineProperty(data, 'text', {   // 數據變化 &mdash;> 修改視圖   set(newVal) {     input.value = newVal;     span.innerHTML = newVal;   } }); // 視圖更改 --> 數據變化 input.addEventListener('keyup', function(e) {   data.text = e.target.value; });

Vue 3.x的proxy 版本

// 數據 const data = {   text: 'default' }; const input = document.getElementById('input'); const span = document.getElementById('span'); // 數據劫持 const handler = {   set(target, key, value) {     target[key] = value;     // 數據變化 &mdash;> 修改視圖     input.value = value;     span.innerHTML = value;     return value;   } }; const proxy = new Proxy(data, handler);  // 視圖更改 --> 數據變化 input.addEventListener('keyup', function(e) {   proxy.text = e.target.value; });

上述就是小編為大家分享的JavaScript中各種源碼是怎樣實現的了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

博乐市| 五华县| 黄冈市| 衢州市| 巫山县| 鸡泽县| 封丘县| 防城港市| 吴旗县| 资溪县| 莆田市| 涡阳县| 洞头县| 屏南县| 安康市| 安宁市| 宜兴市| 师宗县| 罗源县| 乐都县| 蒲城县| 宁都县| 邛崃市| 九龙坡区| 高尔夫| 洛宁县| 乐至县| 大冶市| 台中市| 乌恰县| 彩票| 温泉县| 渝北区| 石渠县| 梁河县| 阿合奇县| 布拖县| 彝良县| 平乐县| 郸城县| 松江区|