整合營銷服務商

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

          免費咨詢熱線:

          JavaScript中各種源碼實現(前端面試筆試必備)

          能夠手撕各種JavaScript原生函數,可以說是進大廠必備!同時對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

          思路很簡單,就是利用Object.prototype.toString

          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, wrapFun); // 執行完以后立即解綁
              };
              this.on(type, wrapFun); // 綁定
              return this;
            }
          }
          
          let e = new Event()
          
          e.on('click',function(){
            console.log('on')
          })
          // 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函數執行了??偨Y一下:

          1. call改變函數this指向
          2. 調用函數

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

          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. 首先我們對參數context做了兼容處理,不傳值,context默認值為window;
          2. 然后我們將函數掛載到context上面,context.fn = this;
          3. 處理參數,將傳入myCall的參數截取,去除第一位,然后轉為數組;
          4. 調用context.fn,此時fn的this指向context;
          5. 刪除對象上的屬性 delete context.fn;
          6. 將結果返回。

          以此類推,我們順便實現一下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(…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的雙向綁定

          Vue 2.x的Object.defineProperty版本

          // 數據
          const data = {
            text: 'default'
          };
          const input = document.getElementById('input');
          const span = document.getElementById('span');
          // 數據劫持
          Object.defineProperty(data, 'text', {
            // 數據變化 —> 修改視圖
            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;
              // 數據變化 —> 修改視圖
              input.value = value;
              span.innerHTML = value;
              return value;
            }
          };
          const proxy = new Proxy(data, handler);
          
          // 視圖更改 --> 數據變化
          input.addEventListener('keyup', function(e) {
            proxy.text = e.target.value;
          });
          

          思考:Vue雙向綁定的實現,使用 ES6 的 Proxy 相比 Object.defineProperty 有什么優勢?

          實現一個Array.prototype.map()

          先看看reduce和map的使用方法

          let new_array = arr.map(function callback(currentValue[, index[,array) {}[, thisArg])
          
          let result = arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

          最常見的方式我們可以用一個for循環來實現:

          Array.prototype.myMap = function(callback, thisArg) {
            let arr = [];
            for (let i = 0; i < this.length; i++) {
              arr.push(callback.call(thisArg, this[i], i, this));
            }
            return arr;
          };

          同樣的我們也可以用數組的reduce方法實現

          Array.prototype.myMap2 = function(callback, thisArg) {
            let result = this.reduce((accumulator, currentValue, index, array) => {
              accumulator.push(callback.call(thisArg, currentValue, index, array));
              return accumulator;
            }, []);
            return result;
          };

          結語

          看完如果覺得對你有幫助,勞煩點個贊哈,你的鼓勵就是我更新最大的動力!

          學習使我快樂!

          介紹

          一個基于 vue、Echart 框架的大數據可視化(大屏展示)模板,提供數據動態刷新渲染、屏幕適應、內部圖表自由替換、Mixins注入等功能。

          開源地址

          https://gitee.com/MTrun/big-screen-vue-datav?_from=gitee_search

          項目展示

          項目使用

          1. 如何啟動項目需要提前安裝好nodejs與npm,下載項目后在項目主目錄下運行npm/cnpm install拉取依賴包,然后使用 vue-cli 或者直接使用命令npm run serve,就可以啟動項目,啟動項目后需要手動全屏(按 F11)。
          2. 如何請求數據現在的項目未使用前后端數據請求,建議使用 axios 進行數據請求,在 main.js 位置進行全局配置,在 views/xx.vue 文件里進行前后端數據請求。
          1. 如何動態渲染圖表在components/echart下的文件,比如drawPie()是渲染函數,echartData是需要動態渲染的數據,當外界通過props傳入新數據,我們可以使用watch()方法去監聽,一旦數據變化就調用this.drawPie()并觸發內部的.setOption函數,重新渲染一次圖表。
          1. 如何復用圖表組件因為 Echart 圖表是根據id/class去獲取 Dom 節點并進行渲染的,所以我們只要傳入唯一的 id 值與需要的數據就可以進行復用。如中間部分的任務通過率與任務達標率組件就是采用復用的方式。
          1. 如何更換邊框邊框是使用了 DataV 自帶的組件,只需要去 views 目錄下去尋找對應的位置去查找并替換就可以,具體的種類請去 DavaV 官網查看 如:
          1. 如何更換圖表直接進入 components/echart 下的文件修改成你要的 echarts 模樣,可以去echarts 官方社區里面查看案例。
          2. Mixins 注入的問題使用 mixins 注入解決了圖表重復書寫響應式適配的代碼,如果要更換(新增)圖形,需要將echarts.init()函數賦值給this.chart,然后 mixins 才會自動幫你注入響應式功能。
          3. 屏幕適配問題本項目借助了 flexible 插件,通過改變 rem 的值來進行適配,原設計為 1920px。 ,適配區間為:1366px ~ 2560px,本項目有根據實際情況進行源文件的更改,小屏幕(如:寬為 1366px)需要自己舍棄部分動態組件進行適配,如'動態文字變換組件'會影響布局,需要手動換成一般節點。

          具體使用請移步https://gitee.com/MTrun/big-screen-vue-datav?_from=gitee_search地址參考。

          目說明

          • X-SpringBoot 是一個輕量級的Java快速開發平臺,基于各大開源項目組合而來,用于快速構建中小型API、RESTful API項目,該項目已經有過多個真實項目的實踐,穩定、簡單、快速,使我們擺脫那些重復勞動。
          • 本項目已大量重構,精簡了大量代碼減少第三方依賴,最干凈的腳手架。
          • 引入了lombok 大量簡化了代碼
          • 引入了MyBatis Plus 大量簡化了SQL
          • 引入hutool 工具包 規范工具類
          • 引入minio 分布式文件系統
          • 前后端完全脫離,前端代碼可單獨部署
          • 自定義Spring Security 支持獲取token
          • 賬號密碼:admin/admin

          版本信息

          • 核心框架:Spring Boot 2.1.8
          • 安全框架:Spring Security
          • 視圖框架:Spring MVC 5.1.x
          • 持久層框架:MyBatis Plus 3.1.0
          • 日志管理:SLF4J 1.7、Log4j
          • 頁面交互:Vue2.x

          環境

          • jdk 1.8
          • mysql 5.7+
          • redis
          • nginx

          項目結構

          X-SpringBoot
          ├─doc  
          │  ├─db.sql 項目SQL語句
          │  ├─nginx.confi nginx 配置文件
          │  ├─updateLog 更新日志
          │
          ├─authentication 權限認證
          ├─common 公共模塊
          │  ├─annotation 自定義注解
          │  ├─aspect 系統日志
          │  ├─base base包
          │  ├─exception 異常處理
          │  ├─utils 一些工具類
          │  ├─validator 后臺校驗
          │  └─xss XSS過濾
          │ 
          ├─config 配置信息
          ├─interceptor token攔截器
          │ 
          ├─modules 功能模塊
          │  ├─app API接口模塊(APP調用)
          │  ├─oss 文件服務模塊
          │  ├─sys 權限模塊
          │  ├─apkversion APK 版本管理
          │  └─gen 代碼生成
          │ 
          ├─Application 項目啟動類
          ├─Swagger2 swagger2類
          │  
          ├──resources 
          │  ├─mapper SQL對應的XML文件

          部署

          • 后臺部署
            1、 $git clone https://github.com/yzcheng90/X-SpringBoot.git
            
            2 、IDEA 打開項目引入依賴
            
            3、 創建數據庫x_springboot,數據庫編碼為UTF-8,執行doc/db.sql文件,初始化數據
            
            4、 IDEA運行Application.java,則可啟動項目 http://localhost:8080
          • 前臺部署
             1、 打開nginx 目錄 /conf/nginx.conf 
             
             2、 在server中修改 root 和 index  
                 
                 ...
                 server {
                     ....
                     #靜態頁面目錄
                     root  E:\github\X-SpringBoot\x-springboot-ui;
                     #默認首頁
                     index  login.html;
                     ....
                     
                     location ^~// {
                          proxy_pass   http://127.0.0.1:8080; #這里為后臺服務地址
                     }
                 }
                 ...
                 
                 ( 這里建議直接復制項目中的doc/nginx.conf替換你的nginx配置文件,然后修改靜態頁面目錄 )
                 
              3、啟動nginx 訪問 localhost

          系統截圖

          X-SpringBoot項目源碼已經全部上傳完畢,如果您有需要的話, 可以關注轉發文章之后私信我【源碼】來免費獲取到項目源碼


          主站蜘蛛池模板: 美女福利视频一区二区| 一区二区乱子伦在线播放| 成人免费区一区二区三区| 中文字幕一区二区三区5566| 制服美女视频一区| 精品一区精品二区制服| 国产精品免费大片一区二区| 精品一区二区三区在线播放视频| 无码少妇一区二区浪潮av| 极品尤物一区二区三区| 在线精品亚洲一区二区小说| 国产一区二区三区露脸| 国产欧美一区二区精品仙草咪| 国产av一区二区三区日韩| 国产vr一区二区在线观看| 日本精品一区二区在线播放| 亚洲码一区二区三区| 国产av一区二区三区日韩| 亚洲无人区一区二区三区| 一区二区三区在线视频播放| 相泽南亚洲一区二区在线播放 | 国产免费av一区二区三区| 国产成人精品日本亚洲专一区 | 国产精品一区二区久久沈樵| 老熟妇仑乱一区二区视頻| 久久国产视频一区| 一区二区乱子伦在线播放| 亚洲综合一区国产精品| 激情内射亚洲一区二区三区| 亚洲乱码日产一区三区| 精品无码人妻一区二区三区不卡| 动漫精品一区二区三区3d | 亚洲色精品VR一区区三区| 精品一区二区三区免费毛片爱 | 波多野结衣一区二区三区| 人妻夜夜爽天天爽爽一区| 国产精品特级毛片一区二区三区 | 日韩视频一区二区| 日韩视频在线一区| 福利一区福利二区| 久久精品国产一区二区电影|