iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > html >JavaScript中各种源码是怎样实现的
  • 527
分享到

JavaScript中各种源码是怎样实现的

2024-04-02 19:04:59 527人浏览 安东尼
摘要

这期内容当中小编将会给大家带来有关javascript中各种源码是怎样实现的,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。 前言最近很多人和我一样在积极地准备前

这期内容当中小编将会给大家带来有关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中各种源码是怎样实现的了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注编程网html频道。

--结束END--

本文标题: JavaScript中各种源码是怎样实现的

本文链接: https://www.lsjlt.com/news/82601.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • JavaScript中各种源码是怎样实现的
    这期内容当中小编将会给大家带来有关JavaScript中各种源码是怎样实现的,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。 前言最近很多人和我一样在积极地准备前...
    99+
    2024-04-02
  • JavaScript中怎么实现各种交互效果
    今天就跟大家聊聊有关JavaScript中怎么实现各种交互效果,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。一、了解HTML5 details, s...
    99+
    2024-04-02
  • 12款各种编程语言实现的Git代码托管系统是怎样的
    12款各种编程语言实现的Git代码托管系统是怎样的,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。尽管 SVN 在企业中还是占据着主导的位置,但在互联网世界的版本控制系统中,...
    99+
    2023-06-17
  • C#打印源码的具体实现是怎样的
    本篇文章给大家分享的是有关C#打印源码的具体实现是怎样的,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。C#打印源码也是打印控件的功能之一,这里介绍的C#打印源码可以实现自动打印...
    99+
    2023-06-17
  • MySQL中各种字段取值范围是怎么样的
    小编给大家分享一下MySQL中各种字段取值范围是怎么样的,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧! ...
    99+
    2024-04-02
  • CSS3中怎么实现各种图形
    CSS3中怎么实现各种图形,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。1、自适应的椭圆代码如下:<div class=...
    99+
    2024-04-02
  • 汇编语言怎么实现各种码制的转换
    本篇内容主要讲解“汇编语言怎么实现各种码制的转换”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“汇编语言怎么实现各种码制的转换”吧!1.十六进制转换为二进制数设计1.1设计要求:设计转换程序,将键...
    99+
    2023-06-21
  • Python实现各种中间件的连接
    目录连接数据库1、连接Redis单节点2、连接Redis cluster集群 3、连接Redis哨兵集群连接数据库 Redis连接   1、连接Redis单节点 import red...
    99+
    2024-04-02
  • 怎么在linux中实现shell的if各种判断
    本篇文章为大家展示了怎么在linux中实现shell的if各种判断,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。shell编程中使用到得if语句内判断参数  –b当file存在并且是块文件时返回真 ...
    99+
    2023-06-09
  • 各种数据库的SQL执行计划是怎么样的
    各种数据库的SQL执行计划是怎么样的,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。执行计划(execution plan,也叫查询计划或者解释...
    99+
    2024-04-02
  • GitHub上的JavaScript开源项目是怎样的
    本篇文章为大家展示了GitHub上的JavaScript开源项目是怎样的,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。1. iptvhttps://github.c...
    99+
    2024-04-02
  • java实现的各种排序算法代码示例
    折半插入排序折半插入排序是对直接插入排序的简单改进。此处介绍的折半插入,其实就是通过不断地折半来快速确定第i个元素的插入位置,这实际上是一种查找算法:折半查找。Java的Arrays类里的binarySearch()方法,就是折半查找的实现...
    99+
    2023-05-31
    java 排序 算法
  • Java中List排序的三种实现方法是怎样的
    Java中List排序的三种实现方法是怎样的,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。前言在某些特殊的场景下,我们需要在 Java 程序中对 List 集合...
    99+
    2023-06-22
  • 怎么在Android中实现绘制各种图形
    怎么在Android中实现绘制各种图形?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。首先自定义一个View类,这个view类里面需要一个Paint对象来控制图形的属性,需要...
    99+
    2023-05-30
    android
  • 热门的JavaScript开源项目是怎么样的
    热门的JavaScript开源项目是怎么样的,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。1. Signal-Desktophttps:/...
    99+
    2024-04-02
  • JavaScript中的DOM是怎样的
    JavaScript中的DOM是怎样的,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。  其实就是操作 html 中的标签的...
    99+
    2024-04-02
  • mysql5.7forcentos7.6源码安装的步骤是怎样的
    这篇文章给大家介绍mysql5.7forcentos7.6源码安装的步骤是怎样的,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。详细部署步骤如下:1.安装前的准备1) 关闭防火墙修改SE...
    99+
    2024-04-02
  • Javascript代码是怎样被压缩的
    Javascript代码是怎样被压缩的,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。随着前端的发展,特别是 React,Vue 等构造单页...
    99+
    2024-04-02
  • Python如何实现各种中间件的连接
    今天小编给大家分享一下Python如何实现各种中间件的连接的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。连接数据库Redis...
    99+
    2023-06-30
  • JavaScript的三种BOM对象分别是怎样的
    这篇文章将为大家详细讲解有关JavaScript的三种BOM对象分别是怎样的,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。window 对象给我们提供了一个 location 属性用于获取或...
    99+
    2023-06-21
软考高级职称资格查询
推荐阅读
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作