溫馨提示×

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

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

JavaScript中各種源碼的實(shí)現(xiàn)是怎樣的

發(fā)布時(shí)間:2021-09-30 17:57:54 來(lái)源:億速云 閱讀:131 作者:柒染 欄目:開(kāi)發(fā)技術(shù)

JavaScript中各種源碼的實(shí)現(xiàn)是怎樣的,很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來(lái)學(xué)習(xí)下,希望你能有所收獲。

  能夠手寫(xiě)實(shí)現(xiàn)各種JavaScript原生函數(shù),可以說(shuō)是擺脫 API調(diào)用師 帽子的第一步,我們不光要會(huì)用,更要去探究其實(shí)現(xiàn)原理!

  對(duì)JavaScript源碼的學(xué)習(xí)和實(shí)現(xiàn)能幫助我們快速和扎實(shí)地提升自己的前端編程能力。

  實(shí)現(xiàn)一個(gè)new操作符

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

  創(chuàng)建一個(gè)空的簡(jiǎn)單JavaScript對(duì)象(即{});

  鏈接該對(duì)象(即設(shè)置該對(duì)象的構(gòu)造函數(shù))到另一個(gè)對(duì)象 ;

  將步驟(1)新創(chuàng)建的對(duì)象作為this的上下文 ;

  如果該函數(shù)沒(méi)有返回對(duì)象,則返回this。

  知道new做了什么,接下來(lái)我們就來(lái)實(shí)現(xiàn)它

  function create(Con, ...args){

  // 創(chuàng)建一個(gè)空的對(duì)象

  this.obj = {};

  // 將空對(duì)象指向構(gòu)造函數(shù)的原型鏈

  Object.setPrototypeOf(this.obj, Con.prototype);

  // obj綁定到構(gòu)造函數(shù)上,便可以訪問(wèn)構(gòu)造函數(shù)中的屬性,即this.obj.Con(args)

  let result = Con.apply(this.obj, args);

  // 如果返回的result是一個(gè)對(duì)象則返回

  // new方法失效,否則返回obj

  return result instanceof Object ? result : this.obj;

  }

  實(shí)現(xiàn)一個(gè)Array.isArray

  思路很簡(jiǎn)單,就是利用Object.prototype.toString

  Array.myIsArray = function(o) {

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

  };

  實(shí)現(xiàn)一個(gè)Object.create()方法

  function create = function (o) {

  var F = function () {};

  F.prototype = o;

  return new F();

  };

  實(shí)現(xiàn)一個(gè)EventEmitter

  真實(shí)經(jīng)歷,最近在字節(jié)跳動(dòng)的面試中就被面試官問(wèn)到了,讓我手寫(xiě)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Event類(lèi)。

  class Event {

  constructor () {

  // 儲(chǔ)存事件的數(shù)據(jù)結(jié)構(gòu)

  // 為查找迅速, 使用對(duì)象(字典)

  this._cache = {}

  }

  // 綁定

  on(type, callback) {

  // 為了按類(lèi)查找方便和節(jié)省空間

  // 將同一類(lèi)型事件放到一個(gè)數(shù)組中

  // 這里的數(shù)組是隊(duì)列, 遵循先進(jìn)先出

  // 即新綁定的事件先觸發(fā)

  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

  }

  // 觸發(fā)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)

  實(shí)現(xiàn)一個(gè)Array.prototype.reduce

  首先觀察一下Array.prototype.reduce語(yǔ)法;

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

  然后就可以動(dòng)手實(shí)現(xiàn)了:

  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

  實(shí)現(xiàn)一個(gè)call或apply

  先來(lái)看一個(gè)call實(shí)例,看看call到底做了什么:

  let foo = {

  value: 1

  };

  function bar() {

  console.log(this.value);

  }

  bar.call(foo); // 1

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

  總結(jié)一下:

  call改變函數(shù)this指向

  調(diào)用函數(shù)

  思考一下:我們?nèi)绾螌?shí)現(xiàn)上面的效果呢?代碼改造如下:

  Function.prototype.myCall = function(context) {

  context = context || window;

  //將函數(shù)掛載到對(duì)象的fn屬性上

  context.fn = this;

  //處理傳入的參數(shù)

  const args = [...arguments].slice(1);

  //通過(guò)對(duì)象的屬性調(diào)用該方法

  const result = context.fn(...args);

  //刪除該屬性

  delete context.fn;

  return result

  };

  我們看一下上面的代碼:

  首先我們對(duì)參數(shù)context做了兼容處理,不傳值,context默認(rèn)值為window;

  然后我們將函數(shù)掛載到context上面,context.fn = this;

  處理參數(shù),將傳入myCall的參數(shù)截取,去除第一位,然后轉(zhuǎn)為數(shù)組;

  調(diào)用context.fn,此時(shí)fn的this指向context;

  刪除對(duì)象上的屬性 delete context.fn;

  將結(jié)果返回。

  以此類(lèi)推,我們順便實(shí)現(xiàn)一下apply,唯一不同的是參數(shù)的處理,代碼如下:

  Function.prototype.myApply = function(context) {

  context = context || window

  context.fn = this

  let result

  // myApply的參數(shù)形式為(obj,[arg1,arg2,arg3]);

  // 所以myApply的第二個(gè)參數(shù)為[arg1,arg2,arg3]

  // 這里我們用擴(kuò)展運(yùn)算符來(lái)處理一下參數(shù)的傳入方式

  if (arguments[1]) {

  result = context.fn(…arguments[1])

  } else {

  result = context.fn()

  }

  delete context.fn;

  return result

  };

  以上便是call和apply的模擬實(shí)現(xiàn),唯一不同的是對(duì)參數(shù)的處理方式。

  實(shí)現(xiàn)一個(gè)Function.prototype.bind

  function Person(){

  this.name="zs";

  this.age=18;

  this.gender="男"

  }

  let obj={

  hobby:"看書(shū)"

  }

  // 將構(gòu)造函數(shù)的this綁定為obj

  let changePerson = Person.bind(obj);

  // 直接調(diào)用構(gòu)造函數(shù),函數(shù)會(huì)操作obj對(duì)象,給其添加三個(gè)屬性;

  changePerson();

  // 1、輸出obj

  console.log(obj);

  // 用改變了this指向的構(gòu)造函數(shù),new一個(gè)實(shí)例出來(lái)

  let p = new changePerson();

  // 2、輸出obj

  console.log(p);

  仔細(xì)觀察上面的代碼,再看輸出結(jié)果。

  我們對(duì)Person類(lèi)使用了bind將其this指向obj,得到了changeperson函數(shù),此處如果我們直接調(diào)用changeperson會(huì)改變obj,若用new調(diào)用changeperson會(huì)得到實(shí)例 p,并且其__proto__指向Person,我們發(fā)現(xiàn)bind失效了。

  我們得到結(jié)論: 用bind改變了this指向的函數(shù),如果用new操作符來(lái)調(diào)用,bind將會(huì)失效。

  這個(gè)對(duì)象就是這個(gè)構(gòu)造函數(shù)的實(shí)例,那么只要在函數(shù)內(nèi)部執(zhí)行 this instanceof 構(gòu)造函數(shù) 來(lái)判斷其結(jié)果是否為true,就能判斷函數(shù)是否是通過(guò)new操作符來(lái)調(diào)用了,若結(jié)果為true則是用new操作符調(diào)用的,代碼修正如下:

  // bind實(shí)現(xiàn)

  Function.prototype.mybind = function(){

  // 1、保存函數(shù)

  let _this = this;

  // 2、保存目標(biāo)對(duì)象

  let context = arguments[0]||window;

  // 3、保存目標(biāo)對(duì)象之外的參數(shù),將其轉(zhuǎn)化為數(shù)組;

  let rest = Array.prototype.slice.call(arguments,1);

  // 4、返回一個(gè)待執(zhí)行的函數(shù)

  return function F(){

  // 5、將二次傳遞的參數(shù)轉(zhuǎn)化為數(shù)組;

  let rest2 = Array.prototype.slice.call(arguments)

  if(this instanceof F){

  // 6、若是用new操作符調(diào)用,則直接用new 調(diào)用原函數(shù),并用擴(kuò)展運(yùn)算符傳遞參數(shù)

  return new _this(...rest2)

  }else{

  //7、用apply調(diào)用第一步保存的函數(shù),并綁定this,傳遞合并的參數(shù)數(shù)組,即context._this(rest.concat(rest2))

  _this.apply(context,rest.concat(rest2));

  }

  }

  };

  實(shí)現(xiàn)一個(gè)JS函數(shù)柯里化

  Currying的概念其實(shí)并不復(fù)雜,用通俗易懂的話說(shuō):只傳遞給函數(shù)一部分參數(shù)來(lái)調(diào)用它,讓它返回一個(gè)函數(shù)去處理剩下的參數(shù)。

  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);

  // 如果參數(shù)個(gè)數(shù)小于最初的fn.length,則遞歸調(diào)用,繼續(xù)收集參數(shù)

  if (_args.length < len) {   return progressCurrying.call(_this, fn, _args);   }   // 參數(shù)收集完畢,則執(zhí)行fn   return fn.apply(this, _args);   }   }   手寫(xiě)防抖(Debouncing)和節(jié)流(Throttling)   節(jié)流

  防抖函數(shù) onscroll 結(jié)束時(shí)觸發(fā)一次,延遲執(zhí)行

  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);

  節(jié)流函數(shù) onscroll 時(shí),每隔一段時(shí)間觸發(fā)一次,像水滴一樣

  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;

  JSON.parse(JSON.stringfy));

  非常簡(jiǎn)單,但缺陷也很明顯,比如拷貝其他引用類(lèi)型、拷貝函數(shù)、循環(huán)引用等情況。

  基礎(chǔ)版

  function clone(target){

  if(typeof target === 'object'){

  let cloneTarget = {};

  for(const key in target){

  cloneTarget[key] = clone(target[key])

  }

  return cloneTarget;

  } else {

  return target

  }

  }

  寫(xiě)到這里已經(jīng)可以幫助你應(yīng)付一些面試官考察你的遞歸解決問(wèn)題的能力。但是顯然,這個(gè)深拷貝函數(shù)還是有一些問(wèn)題。

  一個(gè)比較完整的深拷貝函數(shù),需要同時(shí)考慮對(duì)象和數(shù)組,考慮循環(huán)引用:

  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;

  }

  }

  實(shí)現(xiàn)一個(gè)instanceOf

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

  // L 表示左表達(dá)式,R 表示右表達(dá)式

  function instance_of(L, R) {

  var O = R.prototype;

  L = L.__proto__;

  while (true) {

  if (L === null){

  return false;

  }

  // 這里重點(diǎn):當(dāng) O 嚴(yán)格等于 L 時(shí),返回 true

  if (O === L) {

  return true;

  }

  L = L.__proto__;

  }

  }

  實(shí)現(xiàn)原型鏈繼承

  function myExtend(C, P) {

  var F = function(){};

  F.prototype = P.prototype;

  C.prototype = new F();

  C.prototype.constructor = C;

  C.super = P.prototype;

  }

  實(shí)現(xiàn)一個(gè)async/await

  原理

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

  實(shí)現(xiàn)

  function _asyncToGenerator(fn) {

  return function() {

  var self = this,

  args = arguments;

  // 將返回值promise化

  return new Promise(function(resolve, reject) {

  // 獲取迭代器實(shí)例

  var gen = fn.apply(self, args);

  // 執(zhí)行下一步

  function _next(value) {

  asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value);

  }

  // 拋出異常

  function _throw(err) {

  asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err);

  }

  // 第一次觸發(fā)

  _next(undefined);

  });

  };

  }

  實(shí)現(xiàn)一個(gè)Array.prototype.flat()函數(shù)

  最近字節(jié)跳動(dòng)的前端面試中也被面試官問(wèn)到,要求手寫(xiě)實(shí)現(xiàn)。

  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";

  }

  };

  實(shí)現(xiàn)一個(gè)事件代理

  這個(gè)問(wèn)題一般還會(huì)讓你講一講事件冒泡和事件捕獲機(jī)制

red

yellow

blue

green

black

white
  實(shí)現(xiàn)一個(gè)雙向綁定

  Vue 2.x的Object.defineProperty版本

  // 數(shù)據(jù)

  const data = {

  text: 'default'

  };

  const input = document.getElementById('input');

  const span = document.getElementById('span');

  // 數(shù)據(jù)劫持

  Object.defineProperty(data, 'text', {

  // 數(shù)據(jù)變化 —> 修改視圖

  set(newVal) {

  input.value = newVal;

  span.innerHTML = newVal;

  }

  });

  // 視圖更改 --> 數(shù)據(jù)變化

  input.addEventListener('keyup', function(e) {

  data.text = e.target.value;

  });

  Vue 3.x的proxy 版本

  // 數(shù)據(jù)

  const data = {

  text: 'default'

  };

  const input = document.getElementById('input');

  const span = document.getElementById('span');

  // 數(shù)據(jù)劫持

  const handler = {

  set(target, key, value) {

  target[key] = value;

  // 數(shù)據(jù)變化 —> 修改視圖

  input.value = value;

  span.innerHTML = value;

  return value;

  }

  };

  const proxy = new Proxy(data, handler);

  // 視圖更改 --> 數(shù)據(jù)變化

  input.addEventListener('keyup', function(e) {

  proxy.text = e.target.value;

  });

看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝您對(duì)億速云的支持。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI