Skip to content
Go back

前端相关的各类知识汇总

Updated:  at  05:46 PM

文章目录

JavaScript&Typescript 相关

判断是否是数组

const a = [1,1,1]
Array.isArray(a)
a instanceof Array
a.__proto__ === Array.prototype
a.constructor === Array
Object.prototype.toString.call(a) === '[object, Array]'

js精度丢失问题及解决办法

javaScript存储方式是双精度浮点数,在二进制转换时有精度丢失的问题。

对于一个整数,可以很轻易转化成十进制或者二进制。但是对于一个浮点数来说,因为小数点的存在,小数点的位置不是固定的。

在javascript语言中,0.1 和 0.2 都会被转成二进制后无限循环(精度问题就是在这里的转换出现的), 在计算机的二进制表示里却是无穷的,由于存储位数限制因此存在“舍去”,精度丢失就发生了。

使用Math.js/Big.js之类的第三方库来解决相应问题。

反转链表

给出一个单链表的头节点,将该链表反转后,返回反转后的链表。

// 迭代法
function reverseList(head) {
    let prev = null;
    let curr = head;
    while (curr !== null) {
        const next = curr.next; // 保存下一节点
        curr.next = prev; // 反转指针
        prev = curr; // 移动prev
        curr = next; // 移动curr
    }
    return prev; // 返回新的头节点
}

// 递归法
function reverseList2(head) {
  if (head === null || head.next === null) {
    return head;
  }
  const newHead = reverseList2(head.next); // 递归反转后续链表
  head.next.next = head; // 反转指针
  head.next = null; // 断开原指针
  return newHead; // 返回新的头节点
}

js数据类型

实现 trim 方法

熟悉下正则表达符号:

表达式描述
\d匹配一个数字字符。等价于 [0-9]
\w匹配任何单词字符,包括下划线。等价于 [0-9a-zA-Z_]
\s匹配任何空白字符,包括空格、制表符、换页符等。
.匹配几乎所有字符。
^匹配开头,在多行匹配中匹配行开头。
$匹配结尾,在多行匹配中匹配行结尾。
\b匹配一个单词边界,即字与空格间的位置。
(?=p)匹配p前面的位置。
(?!p)匹配不是p前面的位置。
// 删除左右两端空格
String.prototype.trim =  function (){
  return this.replace(/(^\s*)|(\s*$)/g, '');
}
// 删除左边空格
String.prototype.trim =  function (){
  return this.replace(/(^\s*)/g, '');
}
// 删除右边空格
String.prototype.trim =  function (){
  return this.replace(/(\s*$)/g, '');
}

判断一个数是否是 NaN 类型?

function isNaNFunc (value){
  return value !== value;
}
// Or
function isNaNFunc (value){
  return Number.isNaN(value);
}

Number.isNaN(value) 与 isNaN(value) 区别?

Number.isNaN('blabla'); // false
isNaN('blabla'); // true
Number.isNaN(NaN); // true
null == null // true
null === null // true
undefined == undefined // true
undefined === undefined // true
NaN == NaN // false
NaN === NaN // false
typeof NaN // "number"
typeof null // "object"
typeof undefined // "undefined"
// NaN 代表一个范围
// null、undefined 代表一个确切的值

编写防抖函数

// 函数在最后一次调用
function debounce(func, timeout = 300){
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => { func.apply(this, args); }, timeout);
  };
}

// 函数在第一次调用
function debounce_leading(func, timeout = 300){
  let timer;
  return (...args) => {
    if (!timer) {
      func.apply(this, args);
    }
    clearTimeout(timer);
    timer = setTimeout(() => {
      timer = undefined;
    }, timeout);
  };
}

function saveInput(){
  console.log('Saving data');
}
const processChange = debounce(() => saveInput());

将一个数划分成逗号相隔的数?

function splitNum(str){
  const res = str.split('.');
 const pre = res[0].split("").reverse().reduce((prev, next, index)=> {
    return ((index % 3) ? next : (next + ',')) + prev
  })
  const last = res[1] || [];
  return last.length === 0? pre: pre + '.' + last
}

如何查找当前作用域的上级作用域

看当前函数是在哪个作用域下定义的,那么它的上级作用域就是谁。 和函数在哪里执行的没有任何关系

如何明确this指向?

this代表的是当前行为执行的主体;js中的context代表的是当前行为执行的环境(区域)。

  1. 函数执行首先看函数名前是否有“.”,有的话,“.”前面是谁this就是谁; 没有的话,this就是window
  2. 自执行函数中的this永远是 window
  3. 给元素的某一个事件绑定方法,当事件触发的时候,执行对应的方法,方法中的this是当前的元素
  4. 在构造函数模式中。类中(函数体中)出现的this.xxx = xxx 中的this 指的是当前类的实例

箭头函数

对于箭头函数的this总结如下:

实现bind方法



fn.call(obj, 2, 3); //6
fn.apply(obj, [2, 3]); //6
// bind的特点:
// 可以修改函数this指向。
// bind返回一个绑定了this的新函数boundFcuntion
// 支持函数柯里化,我们在返回bound函数时已传递了部分参数2,在调用时bound补全了剩余参数。
// boundFunction的this无法再被修改,使用call、apply也不行。
Function.prototype.myBind = function (target) {
  const self = this;
  const p = Array.prototype.slice.call(arguments, 1);
  return (...arg) => {
    self.apply(target ? target : self, Array.prototype.concat.apply(p, arg));
  };
};

Function.prototype.myBind = function (target) {
    target.fn = this;
    return (...args)=> target.fn(...args)
}

事件委托、事件流、事件冒泡

  1. 事件委托其实本质就是利用了事件捕获和事件冒泡。
<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
</ul>
<script>
  const ul = document.querySelector('ul');
  // 事件绑定到其公共的祖先元素ul上
  ul.addEventListener('click', function (event){
      let target = event.target
      // 获取到被点击节点的祖先节点,直到其父节点为ul
      while (target && target.parentNode !== this){
          target = target.parentNode;
      }
      if(target.tagName === 'LI' && target.parentNode === this){
          alert(`${target.innerText}被点击了`);
      }
  });
  let newLi = document.createElement("li");
  newLi.innerText = '5';
  ul.appendChild(newLi);
</script>
  1. 事件委托的优点:
  1. JavaScrip 中事件流模型分为 事件捕获、事件目标、事件冒泡 三个阶段。
<html>
    <body>
        <div class="parent">
              <div class="target">
              </div>
        </div>
    </body>
</html>

DOM0级事件绑定 el.onclick=function(){};
DOM2级事件绑定 标准浏览器:el.addEventListener(‘click’,function(){}, false)

<!-- 如果对同一个元素同事绑定捕获事件和冒泡事件 -->
document.addEventListener('click', ()=>{console.log('document 捕获')}, true)
document.addEventListener('click', ()=>{console.log('document 冒泡')}, false)

通过 event.target 获取当前事件的目标对象 target 是触发事件的最具体的元素,currenttarget是绑定事件的元素(在函数中一般等于this) stopImmediatePropagation和stopPropagation方法:

数组操作

/** 会改变原数组 **/
array.shift()       // 删除第一个元素
array.unshift()     // 开头追加一个元素
array.pop()         // 删除最后一个元素
array.push()        // 向最后添加一个元素
array.fill(value,start,end) // 将数组从start到end-1的位置的值都变更为value
array.reverse()     // 颠倒数组中元素的顺序
array.splice()      // 向数组中添加或删除项目,返回被删除的项目,改变了原数组
array.copyWithin()  // 在数组内部,将指定位置的成员复制到数组中的其他位置,返回复制后的新数组

/** 不改变原数组 **/
array.concat()      // 连接两个或多个数组,返回新数组
array.join()        // 将数组元素通过指定分隔符拼接成字符串,返回字符串,
array.slice()       // 截取数组中的一部分,返回截取的部分数组,
array.flat()        // 将二维或多维数据扁平化
array.map()         // 遍历数组然后返回
array.flatMap()     // 先执行map操作再执行flat操作,只能展开一层数组,返回新数组,
array.sort()        // 按规则排序,返回排序后的新数组
array.reduce()      // 迭代数组

对闭包的理解

闭包的形成条件存在不同作用域的变量引用
闭包的作用可以用于定义局部作用域的持久化变量这些变量可以用来做缓存或者计算的中间量等
闭包的弊端:持久化变量不会被正常释放持续占用内存空间很容易造成内存浪费要注意释放

什么是函数式编程

一种编程范式,利用函数把运算过程封装起来,通过组合各种函数来计算结果

函数式编程有两个基本特点:

函数编程的好处 var CEOs = companies.map(c => c.CEO); 函数式编程的一个明显的好处就是这种声明式的代码,对于无副作用的纯函数,我们完全可以不考虑函数内部是如何实现的,专注于编写业务代码。

函数式编程特性

高阶函数:

函数柯里化:

字符串大小写反转

function reverseStr(str){
  return str.replace(/([a-z])|([A-Z])/g, function($1){
    if(/[a-z]/.test($1)){
        return $1.toUpperCase()
    }
    return $1.toLowerCase()
})
}

一堆数字字符串组成最大数是多少[50, 2, 5, 9] => 95502 (字典序+贪心)

function getMaxNumber(arr){
  return arr.sort().reduce((acc= '', cur)=> Math.max(+`${acc}${cur}`, +`${cur}${acc}`))
}

Map/Set、WeakMap

WeakMap与Map的区别:

如何取消一个请求

const controller = new AbortController();
 
axios.get('/foo/bar', {
  signal: controller.signal
}).then(function(response) {
  //...
});

// 取消请求
controller.abort();

如何设计封装一个组件

封装的目的是为了重用,提高代码效率和代码质量
低耦合,单一职责, 可拓展性,可维护性,易读性等

js文件异步加载

渲染引擎遇到 script 标签会停下来,等到执行完脚本,继续向下渲染 

- defer 是“渲染完再执行”,async 是“下载完就执行”
- defer 如果有多个脚本,会按照在页面中出现的顺序加载,多个async 脚本不能保证加载顺序 

实现call方法和apply方法

function fn(num){
    console.log(num)
    console.log(this)
}

Function.prototype.myCall = function(target, p){
    const self = this;
    target.fn = self;
    target.fn(p);
    delete target.fn
}

Function.prototype.myApply = function(target, p){
    const self = this;
    target.fn = self;
    target.fn(...p);
    delete target.fn
}

fn.myCall({a:1},5)

浏览器的渲染机制

解析html代码 -> 构建DOM树 -> 构建css树 -> 构建布局 -> 完成渲染

关于内存释放和作用域销毁的研究

全局作用域(只有当页面关闭的时候,全局作用域才会销毁)

私有作用域(只有函数执行才会产生私有的作用域)

对象的深拷贝和浅拷贝

深拷贝

js类的属性和特点

  1. 每个函数数据类型(普通函数、类)都天生自带一个属性:prototype(原型),并且这个属性是一个对象数据类型的值。
  2. 并且在prototype上浏览器天生给他加了一个属性constructor(构造函数),属性值是当前函数(类)本身
  3. 每一个对象数据类型(普通的对象、实例、prototype…)也天生自带一个属性:proto,属性值是当前实例所属类的原型(prototype)

手写Promise.all 和 Promise.race

  1. 接收一个可迭代对象
  2. 传入的数据中可以是普通数据,也可以是Promise对象
  3. 可迭代对象的promise是并行执行的
  4. 保持输入数组的顺序和输出数组的顺序一致
  5. 传入数组中只要有一个reject,立即返回reject
  6. 所有数据resolve之后返回结果
Promise.all = (iter) => {
  return new Promise((resolve, reject) => {
    const targets = Array.from(iter);
    const result = [];
    if(targets.length === 0) {
      return Promise.resolve(arr)
    }
    for (let i = 0; i < targets.length; i++) {
      Promise.resolve(targets[i]).then(
        (res) => {
          result[i] = res;
          if (i === targets.length - 1) {
            resolve(result);
          }
        },
        (err) => reject(err)
      );
    }
  });
};

Promise.race = (arr) => {
  return new Promise((res,rej) => {
    if(arr.length === 0 ) {
      return Promise.resolve(arr)
    }
    for(let i = 0; i < arr.length; i++){
      arr[i].then(resolve => {
        res(resolve)  //某一promise完成后直接返回其值
      }).catch(e => {
        rej(e)  //如果有错误则直接结束循环,并返回错误
      })
    }
  })
}

settimeout实现interval(注意和普通的要无差别体验)

function customSetInterval(callback, delay, ...args) {
  let isCleared = false;

  function execute() {
    if (!isCleared) {
      callback(...args);
      scheduleNext();
    }
  }

  function scheduleNext() {
    setTimeout(execute, delay);
  }

  // 立即调度第一次执行
  scheduleNext();

  // 返回清除函数
  return function customClearInterval() {
    isCleared = true;
  };
}

笔试题

// 使用let替代var(块级作用域)
for(let i = 1; i <= 3; i++){ // 使用let声明i(块级作用域)
    setTimeout(function(){
        console.log(i); // 每个定时器捕获独立的i值
    }, 1000);
}

// 
for(var i = 0; i<3;i++){
    ((i)=>setTimeout(function(){
        console.log(i)
    },1000))(i)
}

// 立即执行函数(IIFE)闭包
for(var i = 1; i <= 3; i++){
    (function(j){ // 立即执行函数创建闭包
        setTimeout(function(){
            console.log(j); // 闭包捕获参数j
        }, 1000);
    })(i); // 将当前i值传递给闭包
}

// 箭头函数与参数解构(ES6)
for(var i = 1; i <= 3; i++){
    setTimeout((j => () => console.log(j))(i), 1000);
}

// 使用setTimeout的第三个参数
for(var i = 1; i <= 3; i++){
    setTimeout(function(j){
        console.log(j); // 第三个参数作为回调函数的参数
    }, 1000, i); // 传递当前i值
}

console.log(i)

拖拽控制元素宽度

MouseDown = (e) => {
		// 鼠标的X轴坐标
		let clientX = e.clientX;
		// 拖动块距离屏幕左侧的偏移量
		let offsetLeft = dragBtn.offsetLeft;

		// 鼠标移动
		document.onmousemove = (e) => {
			let curDist = offsetLeft + (e.clientX - clientX), // 拖动块的移动距离
				maxDist = 700; // 拖动块的最大移动距离
			// 边界值处理
			if (curDist < 243) {
				// 设置值(保证拖动块一直在拖动盒子的相对位置)
				curDist < 32 && (curDist = 32);
				this.setMinValue(curDist);
				return false;
			}
			curDist > maxDist && (curDist = maxDist);
			this.setMaxValue(curDist);
			return false;
		};
		// 鼠标松开
		document.onmouseup = (e) => {
			let curDist = offsetLeft + (e.clientX - clientX); // 拖动块的移动距离
			if (curDist < 243) {
				this.setMinValue(32);
			}
			document.onmousemove = null;
			document.onmouseup = null;
			// 释放鼠标 ie兼容
			// @ts-ignore
			dragBtn?.releaseCapture && dragBtn.releaseCapture();
		};
		// 捕获鼠标 ie兼容
		// @ts-ignore
		dragBtn.setCapture && dragBtn.setCapture();
		return false;
	};


Previous Post
Typescript
Next Post
Vue-Router的简易实现