关于javascript:前端工程师面试题自检篇二

7次阅读

共计 11680 个字符,预计需要花费 30 分钟才能阅读完成。

请实现 DOM2JSON 一个函数,能够把一个 DOM 节点输入 JSON 的格局

题目形容:

<div>
  <span>
    <a></a>
  </span>
  <span>
    <a></a>
    <a></a>
  </span>
</div>

把上诉 dom 构造转成上面的 JSON 格局

{
  tag: 'DIV',
  children: [
    {
      tag: 'SPAN',
      children: [{ tag: 'A', children: [] }
      ]
    },
    {
      tag: 'SPAN',
      children: [{ tag: 'A', children: [] },
        {tag: 'A', children: [] }
      ]
    }
  ]
}

实现代码如下:

function dom2Json(domtree) {let obj = {};
  obj.name = domtree.tagName;
  obj.children = [];
  domtree.childNodes.forEach((child) => obj.children.push(dom2Json(child)));
  return obj;
}

扩大思考: 如果给定的不是一个 Dom 树结构 而是一段 html 字符串 该如何解析?

那么这个问题就相似 Vue 的模板编译原理 咱们能够利用正则 匹配 html 字符串 遇到开始标签 完结标签和文本 解析结束之后生成对应的 ast 并建设相应的父子关联 一直的 advance 截取残余的字符串 直到 html 全副解析结束

对于原型的继承咱们借助寄生组合继承

function Person(obj) {
    this.name = obj.name
    this.age = obj.age
}
Person.prototype.add = function(value){console.log(value)
}
var p1 = new Person({name:"番茄", age: 18})

function Person1(obj) {Person.call(this, obj)
    this.sex = obj.sex
}
// 这一步是继承的要害
Person1.prototype = Object.create(Person.prototype)
Person1.prototype.play = function(value){console.log(value)
}
var p2 = new Person1({name:"鸡蛋", age: 118, sex: "男"})

说一下数组如何去重, 你有几种办法?

let arr = [1,1,"1","1",true,true,"true",{},{},"{}",null,null,undefined,undefined]

// 办法 1
let uniqueOne = Array.from(new Set(arr)) console.log(uniqueOne)

// 办法 2
let uniqueTwo = arr => {let map = new Map(); // 或者用空对象 let obj = {} 利用对象属性不能反复得个性
    let brr = []
    arr.forEach( item => {if(!map.has(item)) {// 如果是对象得话就判断 !obj[item]
            map.set(item,true) // 如果是对象得话就 obj[item] =true 其余一样
            brr.push(item)
        }
    })
    return brr
}
console.log(uniqueTwo(arr))

// 办法 3
let uniqueThree = arr => {let brr = []
    arr.forEach(item => {
        // 应用 indexOf 返回数组是否蕴含某个值 没有就返回 -1 有就返回下标
        if(brr.indexOf(item) === -1) brr.push(item)
        // 或者应用 includes 返回数组是否蕴含某个值 没有就返回 false 有就返回 true
        if(!brr.includes(item)) brr.push(item)
    })
    return brr
}
console.log(uniqueThree(arr))

// 办法 4
let uniqueFour = arr => {                                         
     // 应用 filter 返回符合条件的汇合
    let brr = arr.filter((item,index) => {return arr.indexOf(item) === index
    })
    return brr
}
console.log(uniqueFour(arr))

说一下类组件和函数组件的区别?

1. 语法上的区别:函数式组件是一个纯函数,它是须要承受 props 参数并且返回一个 React 元素就能够了。类组件是须要继承 React.Component 的,而且 class 组件须要创立 render 并且返回 React 元素,语法上来讲更简单。2. 调用形式

函数式组件能够间接调用,返回一个新的 React 元素;类组件在调用时是须要创立一个实例的,而后通过调用实例里的 render 办法来返回一个 React 元素。3. 状态治理

函数式组件没有状态治理,类组件有状态治理。4. 应用场景

类组件没有具体的要求。函数式组件个别是用在大型项目中来宰割大组件(函数式组件不必创立实例,所有更高效),个别状况下能用函数式组件就不必类组件,晋升效率。

说一下你对盒模型的了解?

CSS3 中的盒模型有以下两种: 规范盒模型、IE 盒模型
盒模型都是由四个局部组成的, 别离是 margin、border、padding 和 content
规范盒模型和 IE 盒模型的区别在于设置 width 和 height 时, 所对应的范畴不同
1、规范盒模型的 width 和 height 属性的范畴只蕴含了 content
2、IE 盒模型的 width 和 height 属性的范畴蕴含了 border、padding 和 content
能够通过批改元素的 box-sizing 属性来扭转元素的盒模型;1、box-sizing:content-box 示意规范盒模型(默认值)2、box-sizing:border-box 示意 IE 盒模型(怪异盒模型)

写版本号排序的办法

题目形容: 有一组版本号如下[‘0.1.1’, ‘2.3.3’, ‘0.302.1’, ‘4.2’, ‘4.3.5’, ‘4.3.4.5’]。当初须要对其进行排序,排序的后果为 [‘4.3.5′,’4.3.4.5′,’2.3.3′,’0.302.1′,’0.1.1’]

实现代码如下:

arr.sort((a, b) => {
  let i = 0;
  const arr1 = a.split(".");
  const arr2 = b.split(".");

  while (true) {const s1 = arr1[i];
    const s2 = arr2[i];
    i++;
    if (s1 === undefined || s2 === undefined) {return arr2.length - arr1.length;}

    if (s1 === s2) continue;

    return s2 - s1;
  }
});
console.log(arr);

冒泡排序 – 工夫复杂度 n^2

题目形容: 实现一个冒泡排序

实现代码如下:

function bubbleSort(arr) {
  // 缓存数组长度
  const len = arr.length;
  // 外层循环用于管制从头到尾的比拟 + 替换到底有多少轮
  for (let i = 0; i < len; i++) {
    // 内层循环用于实现每一轮遍历过程中的反复比拟 + 替换
    for (let j = 0; j < len - 1; j++) {
      // 若相邻元素后面的数比前面的大
      if (arr[j] > arr[j + 1]) {
        // 替换两者
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
    }
  }
  // 返回数组
  return arr;
}
// console.log(bubbleSort([3, 6, 2, 4, 1]));

DNS 如何工作的

DNS 的作用就是通过域名查问到具体的 IP。DNS 协定提供的是一种主机名到 IP 地址的转换服务,就是咱们常说的域名零碎。是应用层协定,通常该协定运行在 UDP 协定之上,应用的是 53 端口号。

因为 IP 存在数字和英文的组合(IPv6),很不利于人类记忆,所以就呈现了域名。你能够把域名看成是某个 IP 的别名,DNS 就是去查问这个别名的真正名称是什么。

当你在浏览器中想拜访 www.google.com 时,会通过进行以下操作:

  • 本地客户端向服务器发动申请查问 IP 地址
  • 查看浏览器有没有该域名的 IP 缓存
  • 查看操作系统有没有该域名的 IP 缓存
  • 查看 Host 文件有没有该域名的解析配置
  • 如果这时候还没得话,会通过间接去 DNS 根服务器查问,这一步查问会找出负责 com 这个一级域名的服务器
  • 而后去该服务器查问 google.com 这个二级域名
  • 接下来查问 www.google.com 这个三级域名的地址
  • 返回给 DNS 客户端并缓存起来

咱们通过一张图来看看它的查问过程吧 👇

这张图很活泼的展现了 DNS 在本地 DNS 服务器是如何查问的,个别向本地 DNS 服务器发送申请是递归查问的

本地 DNS 服务器向其余域名服务器申请的过程是迭代查问的过程👇

递归查问和迭代查问

  • 递归查问指的是查问申请收回后,域名服务器代为向下一级域名服务器发出请求,最初向用户返回查问的最终后果。应用递归 查问,用户只须要收回一次查问申请。
  • 迭代查问指的是查问申请后,域名服务器返回单次查问的后果。下一级的查问由用户本人申请。应用迭代查问,用户须要收回 屡次的查问申请。

所以一般而言,本地服务器查问是递归查问 ,而 本地 DNS 服务器向其余域名服务器申请的过程是迭代查问的过程

DNS 缓存

缓存也很好了解,在一个申请中,当某个 DNS 服务器收到一个 DNS 答复后,它可能答复中的信息缓存在本地存储器中。返回的资源记录中的 TTL 代表了该条记录的缓存的工夫。

DNS 实现负载平衡

它是如何实现负载平衡的呢?首先咱们得分明 DNS 是能够用于在冗余的服务器上实现负载平衡。

起因: 这是因为个别的大型网站应用多台服务器提供服务,因而一个域名可能会对应 多个服务器地址。

举个例子来说👇

  • 当用户发动网站域名的 DNS 申请的时候,DNS 服务器返回这个域名所对应的服务器 IP 地址的汇合
  • 在每个答复中,会循环这些 IP 地址的程序,用户个别会抉择排在后面的地址发送申请。
  • 以此将用户的申请平衡的调配到各个不同的服务器上,这样来实现负载平衡。

DNS 为什么应用 UDP 协定作为传输层协定?

DNS 应用 UDP 协定作为传输层协定的次要起因是为了防止应用 TCP 协定时造成的连贯时延

  • 为了失去一个域名的 IP 地址,往往会向多个域名服务器查问,如果应用 TCP 协定,那么每次申请都会存在连贯时延,这样使 DNS 服务变得很慢。
  • 大多数的地址查问申请,都是浏览器申请页面时收回的,这样会造成网页的等待时间过长。

总结

  • DNS 域名零碎,是应用层协定,运行 UDP 协定之上,应用端口 43。
  • 查问过程,本地查问是递归查问,顺次通过浏览器缓存 —>> 本地 hosts 文件 —>> 本地 DNS 解析器 —>>本地 DNS 服务器 —>> 其余域名服务器申请。接下来的过程就是迭代过程。
  • 递归查问一般而言,发送一次申请就够,迭代过程须要用户发送屡次申请。

Loader 和 Plugin 有什么区别

Loader:直译为 ” 加载器 ”。Webpack 将所有文件视为模块,然而 webpack 原生是只能解析 js 文件,如果想将其余文件也打包的话,就会用到loader。所以 Loader 的作用是让 webpack 领有了加载和解析非 JavaScript 文件的能力。Plugin:直译为 ” 插件 ”。Plugin 能够扩大 webpack 的性能,让 webpack 具备更多的灵活性。在 Webpack 运行的生命周期中会播送出许多事件,Plugin 能够监听这些事件,在适合的机会通过 Webpack 提供的 API 扭转输入后果。

参考前端进阶面试题具体解答

DNS 残缺的查问过程

DNS 服务器解析域名的过程:

  • 首先会在 浏览器的缓存 中查找对应的 IP 地址,如果查找到间接返回,若找不到持续下一步
  • 将申请发送给 本地 DNS 服务器,在本地域名服务器缓存中查问,如果查找到,就间接将查找后果返回,若找不到持续下一步
  • 本地 DNS 服务器向 根域名服务器 发送申请,根域名服务器会返回一个所查问域的顶级域名服务器地址
  • 本地 DNS 服务器向 顶级域名服务器 发送申请,承受申请的服务器查问本人的缓存,如果有记录,就返回查问后果,如果没有就返回相干的下一级的权威域名服务器的地址
  • 本地 DNS 服务器向 权威域名服务器 发送申请,域名服务器返回对应的后果
  • 本地 DNS 服务器将返回后果保留在缓存中,便于下次应用
  • 本地 DNS 服务器将返回后果返回给浏览器

比方要查问 IP 地址,首先会在浏览器的缓存中查找是否有该域名的缓存,如果不存在就将申请发送到本地的 DNS 服务器中,本地 DNS 服务器会判断是否存在该域名的缓存,如果不存在,则向根域名服务器发送一个申请,根域名服务器返回负责 .com 的顶级域名服务器的 IP 地址的列表。而后本地 DNS 服务器再向其中一个负责 .com 的顶级域名服务器发送一个申请,负责 .com 的顶级域名服务器返回负责 .baidu 的权威域名服务器的 IP 地址列表。而后本地 DNS 服务器再向其中一个权威域名服务器发送一个申请,最初权威域名服务器返回一个对应的主机名的 IP 地址列表。

DNS 同时应用 TCP 和 UDP 协定?

DNS 占用 53 号端口,同时应用 TCP 和 UDP 协定。(1)在区域传输的时候应用 TCP 协定

  • 辅域名服务器会定时(个别 3 小时)向主域名服务器进行查问以便理解数据是否有变动。如有变动,会执行一次区域传送,进行数据同步。区域传送应用 TCP 而不是 UDP,因为数据同步传送的数据量比一个申请应答的数据量要多得多。
  • TCP 是一种牢靠连贯,保障了数据的准确性。

(2)在域名解析的时候应用 UDP 协定

  • 客户端向 DNS 服务器查问域名,个别返回的内容都不超过 512 字节,用 UDP 传输即可。不必通过三次握手,这样 DNS 服务器负载更低,响应更快。实践上说,客户端也能够指定向 DNS 服务器查问时用 TCP,但事实上,很多 DNS 服务器进行配置的时候,仅反对 UDP 查问包。

Ajax

它是一种异步通信的办法,通过间接由 js 脚本向服务器发动 http 通信,而后依据服务器返回的数据,更新网页的相应局部,而不必刷新整个页面的一种办法。

面试手写(原生):

//1:创立 Ajax 对象
var xhr = window.XMLHttpRequest?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');// 兼容 IE6 及以下版本
//2:配置 Ajax 申请地址
xhr.open('get','index.xml',true);
//3:发送申请
xhr.send(null); // 谨严写法
//4: 监听申请,承受响应
xhr.onreadysatechange=function(){if(xhr.readySate==4&&xhr.status==200 || xhr.status==304)
          console.log(xhr.responsetXML)
}

jQuery 写法

$.ajax({
  type:'post',
  url:'',
  async:ture,//async 异步  sync  同步
  data:data,// 针对 post 申请
  dataType:'jsonp',
  success:function (msg) { },
  error:function (error) {}})

promise 封装实现:

// promise 封装实现:function getJSON(url) {
  // 创立一个 promise 对象
  let promise = new Promise(function(resolve, reject) {let xhr = new XMLHttpRequest();

    // 新建一个 http 申请
    xhr.open("GET", url, true);

    // 设置状态的监听函数
    xhr.onreadystatechange = function() {if (this.readyState !== 4) return;

      // 当申请胜利或失败时,扭转 promise 的状态
      if (this.status === 200) {resolve(this.response);
      } else {reject(new Error(this.statusText));
      }
    };

    // 设置谬误监听函数
    xhr.onerror = function() {reject(new Error(this.statusText));
    };

    // 设置响应的数据类型
    xhr.responseType = "json";

    // 设置申请头信息
    xhr.setRequestHeader("Accept", "application/json");

    // 发送 http 申请
    xhr.send(null);
  });

  return promise;
}

TCP 的流量管制机制

一般来说,流量管制就是为了让发送方发送数据的速度不要太快,要让接管方来得及接管。TCP 采纳大小可变的 滑动窗口 进行流量管制,窗口大小的单位是字节。这里说的窗口大小其实就是每次传输的数据大小。

  • 当一个连贯建设时,连贯的每一端调配一个缓冲区来保留输出的数据,并将缓冲区的大小发送给另一端。
  • 当数据达到时,接管方发送确认,其中蕴含了本人残余的缓冲区大小。(残余的缓冲区空间的大小被称为窗口,指出窗口大小的告诉称为窗口通告。接管方在发送的每一确认中都含有一个窗口通告。)
  • 如果接管方应用程序读数据的速度可能与数据达到的速度一样快,接管方将在每一确认中发送一个正的窗口通告。
  • 如果发送方操作的速度快于接管方,接管到的数据最终将充斥接管方的缓冲区,导致接管方通告一个零窗口。发送方收到一个零窗口通告时,必须进行发送,直到接管方从新通告一个正的窗口。

说一下 JSON.stringify 有什么毛病?

1. 如果 obj 外面有工夫对象,则 JSON.stringify 后再 JSON.parse 的后果,工夫将只是字符串的模式,而不是对象的模式
2. 如果 obj 里有 RegExp(正则表达式的缩写)、Error 对象,则序列化的后果将只失去空对象;3、如果 obj 里有函数,undefined,则序列化的后果会把函数或 undefined 失落;4、如果 obj 里有 NaN、Infinity 和 -Infinity,则序列化的后果会变成 null
5、JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果 obj 中的对象是有构造函数生成的,则应用 JSON.parse(JSON.stringify(obj))深拷贝后,会抛弃对象的 constructor;6、如果对象中存在循环援用的状况也无奈正确实现深拷贝;

This

不同状况的调用,this指向别离如何。顺带能够提一下 es6 中箭头函数没有 this, arguments, super 等,这些只依赖蕴含箭头函数最靠近的函数

咱们先来看几个函数调用的场景

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

const obj = {
  a: 2,
  foo: foo
}
obj.foo()

const c = new foo()
  • 对于间接调用 foo 来说,不论 foo 函数被放在了什么中央,this 肯定是window
  • 对于 obj.foo() 来说,咱们只须要记住,谁调用了函数,谁就是 this,所以在这个场景下 foo 函数中的 this 就是 obj 对象
  • 对于 new 的形式来说,this 被永远绑定在了 c 下面,不会被任何形式扭转 this

说完了以上几种状况,其实很多代码中的 this 应该就没什么问题了,上面让咱们看看箭头函数中的 this

function a() {return () => {return () => {console.log(this)
    }
  }
}
console.log(a()()())
  • 首先箭头函数其实是没有 this 的,箭头函数中的 this 只取决包裹箭头函数的第一个一般函数的 this。在这个例子中,因为包裹箭头函数的第一个一般函数是 a,所以此时的 thiswindow。另外对箭头函数应用 bind这类函数是有效的。
  • 最初种状况也就是 bind 这些扭转上下文的 API 了,对于这些函数来说,this 取决于第一个参数,如果第一个参数为空,那么就是 window
  • 那么说到 bind,不晓得大家是否思考过,如果对一个函数进行屡次 bind,那么上下文会是什么呢?
let a = {}
let fn = function () { console.log(this) }
fn.bind().bind(a)() // => ?

如果你认为输入后果是 a,那么你就错了,其实咱们能够把上述代码转换成另一种模式

// fn.bind().bind(a) 等于
let fn2 = function fn1() {return function() {return fn.apply()
  }.apply(a)
}
fn2()

能够从上述代码中发现,不论咱们给函数 bind 几次,fn 中的 this 永远由第一次 bind 决定,所以后果永远是 window

let a = {name: 'poetries'}
function foo() {console.log(this.name)
}
foo.bind(a)() // => 'poetries'

以上就是 this 的规定了,然而可能会产生多个规定同时呈现的状况,这时候不同的规定之间会依据优先级最高的来决定 this 最终指向哪里。

首先,new 的形式优先级最高,接下来是 bind 这些函数,而后是 obj.foo() 这种调用形式,最初是 foo 这种调用形式,同时,箭头函数的 this 一旦被绑定,就不会再被任何形式所扭转。

函数执行扭转 this

  • 因为 JS 的设计原理: 在函数中,能够援用运行环境中的变量。因而就须要一个机制来让咱们能够在函数体外部获取以后的运行环境,这便是this

因而要明确 this 指向,其实就是要搞清楚 函数的运行环境,说人话就是,谁调用了函数。例如

  • obj.fn(),便是 obj 调用了函数,既函数中的 this === obj
  • fn(),这里能够看成 window.fn(),因而 this === window

但这种机制并不齐全能满足咱们的业务需要,因而提供了三种形式能够手动批改 this 的指向:

  • call: fn.call(target, 1, 2)
  • apply: fn.apply(target, [1, 2])
  • bind: fn.bind(target)(1,2)

深浅拷贝

浅拷贝:只思考对象类型。

function shallowCopy(obj) {if (typeof obj !== 'object') return

    let newObj = obj instanceof Array ? [] : {}
    for (let key in obj) {if (obj.hasOwnProperty(key)) {newObj[key] = obj[key]
        }
    }
    return newObj
}

简略版深拷贝:只思考一般对象属性,不思考内置对象和函数。

function deepClone(obj) {if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {if (obj.hasOwnProperty(key)) {newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
        }
    }
    return newObj;
}

简单版深克隆:基于简略版的根底上,还思考了内置对象比方 Date、RegExp 等对象和函数以及解决了循环援用的问题。

const isObject = (target) => (typeof target === "object" || typeof target === "function") && target !== null;

function deepClone(target, map = new WeakMap()) {if (map.get(target)) {return target;}
    // 获取以后值的构造函数:获取它的类型
    let constructor = target.constructor;
    // 检测以后对象 target 是否与正则、日期格局对象匹配
    if (/^(RegExp|Date)$/i.test(constructor.name)) {// 创立一个新的非凡对象 (正则类 / 日期类) 的实例
        return new constructor(target);  
    }
    if (isObject(target)) {map.set(target, true);  // 为循环援用的对象做标记
        const cloneTarget = Array.isArray(target) ? [] : {};
        for (let prop in target) {if (target.hasOwnProperty(prop)) {cloneTarget[prop] = deepClone(target[prop], map);
            }
        }
        return cloneTarget;
    } else {return target;}
}

革除浮动

  1. 在浮动元素前面增加 clear:both的空 div 元素
<div class="container">
    <div class="left"></div>
    <div class="right"></div>
    <div style="clear:both"></div>
</div>
  1. 给父元素增加 overflow:hidden 或者 auto 款式,触发BFC
<div class="container">
    <div class="left"></div>
    <div class="right"></div>
</div>
.container{
    width: 300px;
    background-color: #aaa;
    overflow:hidden;
    zoom:1;   /*IE6*/
}
  1. 应用伪元素,也是在元素开端增加一个点并带有 clear: both 属性的元素实现的。
<div class="container clearfix">
    <div class="left"></div>
    <div class="right"></div>
</div>
.clearfix{zoom: 1; /*IE6*/}
.clearfix:after{
    content: ".";
    height: 0;
    clear: both;
    display: block;
    visibility: hidden;
}

举荐应用第三种办法,不会在页面新增 div,文档构造更加清晰

节流

节流(throttle):触发高频事件,且 N 秒内只执行一次。这就好比公交车,10 分钟一趟,10 分钟内有多少人在公交站等我不论,10 分钟一到我就要发车走人!相似 qq 飞车的复位按钮。

核心思想 :应用 工夫戳或标记 来实现,立刻执行一次,而后每 N 秒执行一次。如果 N 秒内触发则间接返回。

利用:节流常利用于鼠标一直点击触发、监听滚动事件。

实现:

// 版本一:标记实现
function throttle(fn, wait){
    let flag = true;  // 设置一个标记
    return function(...args){if(!flag) return;
        flag = false;
        setTimeout(() => {fn.call(this, ...args);
            flag = true;
        }, wait);
    }
}

// 版本二:工夫戳实现
function throttle(fn, wait) {
    let pre = 0;
    return function(...args) {let now = new Date();
        if(now - pre < wait) return;
        pre = now;
        fn.call(this, ...args);
    }
}

函数柯里化

什么叫函数柯里化?其实就是将应用多个参数的函数转换成一系列应用一个参数的函数的技术。还不懂?来举个例子。

function add(a, b, c) {return a + b + c}
add(1, 2, 3)
let addCurry = curry(add)
addCurry(1)(2)(3)

当初就是要实现 curry 这个函数,使函数从一次调用传入多个参数变成屡次调用每次传一个参数。

function curry(fn) {let judge = (...args) => {if (args.length == fn.length) return fn(...args)
        return (...arg) => judge(...args, ...arg)
    }
    return judge
}

迭代查问与递归查问

实际上,DNS 解析是一个蕴含迭代查问和递归查问的过程。

  • 递归查问 指的是查问申请收回后,域名服务器代为向下一级域名服务器发出请求,最初向用户返回查问的最终后果。应用递归 查问,用户只须要收回一次查问申请。
  • 迭代查问 指的是查问申请后,域名服务器返回单次查问的后果。下一级的查问由用户本人申请。应用迭代查问,用户须要收回 屡次的查问申请。

个别咱们向本地 DNS 服务器发送申请的形式就是递归查问,因为咱们只须要收回一次申请,而后本地 DNS 服务器返回给我 们最终的申请后果。而本地 DNS 服务器向其余域名服务器申请的过程是迭代查问的过程,因为每一次域名服务器只返回单次 查问的后果,下一级的查问由本地 DNS 服务器本人进行。

正文完
 0