js的一些实用技巧【持续更新~】

1、数组

1.1、Array.from

Array.from:允许在JavaScript集合(如:数组、类数组对象、字符串、map、set等可迭代对象)上进行有效的转换,即将数组、类数组转为数组

Array.from(arrayLike[, mapFunction[, thisArg]])
  • arrayLike:必传参数,想要转换成数组的伪数组对象或可迭代对象。
  • mapFunction:可选参数,mapFunction(item,index){…} 是在集合中的每个项目上调用的函数。返回的值将插入到新集合中。
  • thisArg:可选参数,执行回调函数 mapFunction 时 this 对象。这个参数很少使用。
  1. Array.from() 第一个用途:将类数组对象转换成数组。
    平时会碰到的类数组对象有:函数中的 arguments 关键字,或者是一个 DOM 集合。
    在下面的示例中,对函数的参数求和:
function sumArguments() {
    
    
    return Array.from(arguments).reduce((sum, num) => sum + num);
}
 
sumArguments(1, 2, 3); // => 6
  1. 此外,Array.from() 的第一个参数可以是任意一个可迭代对象:
Array.from('Hey');                   // => ['H', 'e', 'y']
Array.from(new Set(['one', 'two'])); // => ['one', 'two']
 
const map = new Map();
map.set('one', 1)
map.set('two', 2);
Array.from(map); // => [['one', 1], ['two', 2]]
  1. 克隆数组
    在 JavaScript 中有很多克隆数组的方法。Array.from() 可以很容易的实现数组的浅拷贝。
const numbers = [3, 6, 9];
const numbersCopy = Array.from(numbers);

numbers === numbersCopy; // => false
function recursiveClone(val) {
    
    
    return Array.isArray(val) ? Array.from(val, recursiveClone) : val;
}
 
const numbers = [[0, 1, 2], ['one', 'two', 'three']];
const numbersClone = recursiveClone(numbers);
 
numbersClone; // => [[0, 1, 2], ['one', 'two', 'three']]
numbers[0] === numbersClone[0] // => false

recursiveClone() 能够对数组的深拷贝,通过判断 数组的 item 是否是一个数组,如果是数组,就继续调用 recursiveClone() 来实现了对数组的深拷贝。
4. 使用值填充数组
如果需要使用相同的值来初始化数组,那么 Array.from() 将是不错的选择。定义一个函数,创建一个填充相同默认值的数组:

const length = 3;
const init   = 0;
const result = Array.from({
    
     length }, () => init);
 
result; // => [0, 0, 0]

result 是一个新的数组,它的长度为3,数组的每一项都是0。调用 Array.from() 方法,传入一个类数组对象 { length } 和 返回初始化值的 mapFunction 函数。
但是,有一个替代方法 array.fill() 可以实现同样的功能。

const length = 3;
const init   = 0;
const result = Array(length).fill(init);
 
fillArray2(0, 3); // => [0, 0, 0]

fill() 使用初始值正确填充数组。
5. 使用对象填充数组
当初始化数组的每个项都应该是一个新对象时,Array.from() 是一个更好的解决方案:

const length = 3;
const resultA = Array.from({
    
     length }, () => ({
    
    }));
const resultB = Array(length).fill({
    
    });
 
resultA; // => [{}, {}, {}]
resultB; // => [{}, {}, {}]
 
resultA[0] === resultA[1]; // => false
resultB[0] === resultB[1]; // => true

由 Array.from 返回的 resultA 使用不同空对象实例进行初始化。之所以发生这种情况是因为每次调用时,mapFunction,即此处的 () => ({}) 都会返回一个新的对象。
然后,fill() 方法创建的 resultB 使用相同的空对象实例进行初始化。不会跳过空项。
6. 生成数字范围
可以使用 Array.from() 生成值范围。例如,下面的 range 函数生成一个数组,从0开始到 end - 1。

function range(end) {
    
    
    return Array.from({
    
     length: end }, (_, index) => index);
}
 
range(4); // => [0, 1, 2, 3]

在 range() 函数中,Array.from() 提供了类似数组的 {length:end} ,以及一个简单地返回当前索引的 map 函数 。这样就可以生成值范围。
7. 数组去重
由于 Array.from() 的入参是可迭代对象,因此可以利用其与 Set 结合来实现快速从数组中删除重复项。

function unique(array) {
    
    
  return Array.from(new Set(array));
}
 
unique([1, 1, 2, 3, 3]); // => [1, 2, 3]

首先,new Set(array) 创建了一个包含数组的集合,Set 集合会删除重复项。
因为 Set 集合是可迭代的,所以可以使用 Array.from() 将其转换为一个新的数组。这样就实现了数组去重。

1.2、Array.flat 数组扁平化

  1. 功能概述
  • flat() 函数提供了将一组数组项串联成一个全新的数组并在函数完成后返回新数组的能力。由于这个函数产生了一个全新的数组,所以一旦函数完成操作后,任何包含在原始数组中的现有的、完全独立的数组都不会被改变,在开始操作之前,不需要采取任何预防措施。
  • flat() 函数仅采用一个参数,该参数是可选的,唯一的参数是 depth 参数。如果原始数组包含一个或多个嵌套数组结构,则此参数决定函数将多少数组层压扁为单个层。由于该参数是可选的,所以它的默认值为 1,并且在函数完成时,只有单层数组将被平展到返回的全新数组中。
  1. 演示:
// 1、无参数
var array1 = [1, 2, [3, 4], [[5, 6]], [[[7, 8]]], [[[[9, 10]]]]];
var array2 = array1.flat();
// array2: [1, 2, 3, 4, [5, 6], [[7, 8]], [[[9, 10]]]]
// 2、有参数
var array1 = [1, 2, [3, 4], [[5, 6]], [[[7, 8]]], [[[[9, 10]]]]];
var array2 = array1.flat(2);
// array2: [1, 2, 3, 4, 5, 6, [7, 8], [[9, 10]]]
// 3、无限参数
var array1 = [1, 2, [3, 4], [[5, 6]], [[[7, 8]]], [[[[9, 10]]]]];
var array2 = array1.flat(Infinity);
// array2: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  1. 注意:
  • flat() 函数将删除原始数组中存在的所有空值
  • 在指定负深度参数值的情况下,将使用 0 作为替代。正如前面的示例所演示的那样,当指定深度参数值为 0 时,原始数组中没有数组是扁平的,而新数组中各个数组项和嵌套数组的组成与原始数组完全相同。
  • 不能去重。(可用set函数;排序的话用sort函数即Array.from(new Set(arr.flat(Infinity))).sort((a, b) => { return a - b }))

1.3、Array() 与 Array.of()

作用: 用于将一组值,转换为数组。

  1. Array方法没有参数、一个参数、三个参数时,返回结果都不一样。只有当参数个数不少于 2 个时,Array()才会返回由参数组成的新数组。参数个数只有一个时,实际上是指定数组的长度。
Array(); // []
Array(3); // [ , , ] 代表数组长度
Array(3,2,1) // [3,2,1]
  1. Array.of()方法总是返回参数值组成的数组。如果没有参数,就返回一个空数组。
Array.of(); // []
Array.of(3); // [3]
Array.of(3,2,1); // [3,2,1]

2、对象

2.1、Object.assign() 将可枚举属性从一个对象复制到另一个对象,浅拷贝。

2.2、Object.keys 返回自身可枚举属性,不包括原型链上的。

2.3、Object.entries() 方法返回对象的可枚举属性的键值对数组。

  • 其排列与使用 for…in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)。
  • 用法:Object.entries(obj)。obj - 要返回其可枚举的string-keyed 属性键和值对的对象。
  • 示例
const obj = {
    
     name: "Adam", age: 20, location: "Nepal" };
console.log(Object.entries(obj)); // [ [ 'name', 'Adam' ], [ 'age', 20 ], [ 'location', 'Nepal' ] ]

// Array-like objects
const obj1 = {
    
     0: "A", 1: "B", 2: "C" };
console.log(Object.entries(obj1)); // [ [ '0', 'A' ], [ '1', 'B' ], [ '2', 'C' ] ]

// random key ordering
const obj2 = {
    
     42: "a", 22: "b", 71: "c" };
// [ [ '22', 'b' ], [ '42', 'a' ], [ '71', 'c' ] ] -> arranged in numerical order of keys
console.log(Object.entries(obj2));

// string -> from ES2015+, non objects are coerced to object
const string = "code";
console.log(Object.entries(string)); // [ [ '0', 'c' ], [ '1', 'o' ], [ '2', 'd' ], [ '3', 'e' ] ]

2.4、Object.freeze()冻结对象

  • Object.freeze()方法可以冻结一个对象,冻结后的对象不能进行修改,不能对改对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性,可配置性,可写性,已经不能修改已有属性的值,该对象的原型也不能被修改。若没有进行深层次冻结,则对象的属性若是对象,则该对象的属性可以修改。
  • freeza返回和传入的参数相同的对象。
  • 被冻结的属性不能进行修改,对对象进行冻结一次,不会对深层次的对象进行冻结(可递归)。

3、方法

3.1、startsWith()

  • startsWith()方法用来判断当前字符串是否是以另外一个给定的子字符串“开头”的,根据判断结果返回 true 或 false。
  • str.startsWith(searchString [, position]);
    1、searchString:要搜索的子字符串。
    2、position:在str中搜索searchString的开始位置,默认值为0,也就是真正的字符串开头处。
  var str = "To be, or not to be, that is the question.";
  alert(str.startsWith("To be"));         // true
  alert(str.startsWith("not to be"));     // false
  alert(str.startsWith("not to be", 10)); // true

3.2、call() 方法

方法重用
使用 call() 方法,您可以编写能够在不同对象上使用的方法。
call() 方法是预定义的 JavaScript 方法。
它可以用来调用所有者对象作为参数的方法。
通过 call(),您能够使用属于另一个对象的方法。
在这里插入图片描述

扫描二维码关注公众号,回复: 15242376 查看本文章

3.3、防抖和节流的区别,以及手写实现

  • 防抖
    多次触发事件,事件处理函数只执行一次,并且是在触发操作结束时执行。也就是说,当一个事件被触发,准备执行事件函数前,会等待一定的时间,在这个等待时间内,如果没有再次被触发,那么就执行,如果又触发了,那就本次作废,重置等待时间,直到最终能执行。
    主要应用场景:搜索框搜索输入,用户最后一次输入完,再发送请求;手机号、邮箱验证输入检测
  • 节流
    事件触发后,规定时间内,事件处理函数不能再次被调用。也就是说在规定的时间内,函数只能被调用一次,且是最先被触发调用的那次。
    主要应用场景:高频点击、表单重复提交等。
/*** 防抖函数 n 秒后再执行该事件,若在 n 秒内被重复触发,则重新计时
   * @param func 要被防抖的函数
   * @param wait 规定的时间
   */
function debounce(func, wait) {
    
    
  let timeout;
  return function () {
    
    
    let context = this; // 保存this指向
    let args = arguments; // 拿到event对象

    clearTimeout(timeout);
    timeout = setTimeout(function () {
    
    
      func.apply(context, args)
    }, wait)
  }
}
/*** 节流函数 n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
   * @param fn 要被节流的函数
   * @param wait 规定的时间
   */
function throttled(fn, wait) {
    
    
  let timer = null;
  return function (...args) {
    
    
    if (!timer) {
    
    
      timer = setTimeout(() => {
    
    
        fn.apply(this, args);
        timer = null;
      }, wait);
    }
  }
}

4、正则

4.1、\w含义

JavaScript 中的 RegExp \w 元字符用于查找单词字符,即从 a 到 z、A 到 Z、0 到 9 的字符。它与 [a-zA-Z_0-9] 相同。

5、css样式

5.1、transform 属性(skew、 rotate、translate、scale)

  • 倾斜skew
    在这里插入图片描述
    每个图下方都有skew的参数。粗的红色的线分别是水平垂直方向上的投影,其长度与左边的未倾斜的边是相等的。而两个参数所代表的角度,就是图中黑色标记的角。从上面我们也可以看出来:skewx是代表与X轴倾斜的角度,方向是逆时针;skewY是代表与Y轴倾斜的角度,方向是顺时针。
    但是要注意的是,例如当用skew(60,60)的时候,角度的算法不是上面那样的呢,因为当两个参数的角度都大于45的时候,其实我们看到的是图形的反面,也就是长和宽交换了位置。我们可以看图片上的MyDiv确实是倒的。
    在这里插入图片描述
    此时黄色的才才是投影的矩形,可以和左边的原图进行比较。而原来的粗的红色的线是错的,错的,错的!!!多说几遍先。而角度则是黑色的标出来的!
    其他角度,大家也可以去推一推。
    需要说明的是skew的默认原点 transform-origin 是这个物件的中心点。这也是我在第二行有一条橘黄色的细线,是为了寻找中心点,然后获取投影。
    如果上面的你不能理解,就看我画的投影,首先知道角度是怎么标记的,然后记住这么一句话:不管它是一个方向倾斜,还是两个方向一起倾斜,最后的在X,Y轴上的投影长度是不变的,也就是参数都为 0 时候的长度。但是投影要注意旋转的角度。为了保持投影长度不变,所以就得拉长图形,也就是变形。所以如果你想通过动手实验的话,是得不出上面的结果,因为实际的不会变形。

5.2、white-space

white-space,英语词组意思为空余空间,在计算机术语中的意思是泛空格符。white-space属性指定元素内的空白怎样处理。

值:
1. normal:默认。空白会被浏览器忽略。
2. pre:空白会被浏览器保留。其行为方式类似于HTML中的pre标签。
3. nowrap:文本不会换行,文本会在同一行上继续,直到遇到br标签为止。
4. pre-wrap:保留空白符序列,但是正常地进行换行。
5. pre-line:合并空白符序列,但是保留换行符。
6. inherit:规定应该从父元素继承white-space属性的值。

5.3、word-wrap

word-wrap能设置或检索当当前行超过指定容器的边界时是否断开转行,是一个指令代码。word-wrap属性允许长的内容可以自动换行。

值:
1. normal:默认值。只在允许的断字点换行(浏览器保持默认处理)。
在这里插入图片描述 2. break-word:在长单词或URL地址内部进行换行。
在这里插入图片描述

5.4、word-break

word-break是一个计算机语法,用于设置或检索对象内文本的字内换行行为。尤其在出现多种语言时。
word-break属性指定非CJK脚本的断行规则。
提示:CJK脚本是中国,日本和韩国(“中日韩”)脚本。

值:

  1. normal:默认值。使用浏览器默认的换行规则。
  2. break-all:允许在单词内换行。
  3. keep-all:只能在半角空格或连字符处换行。
    在这里插入图片描述

6、HTML

6.1、script 标签中属性 async 和 defer 的区别

  • script 会阻碍 HTML 解析,只有下载好并执行完脚本才会继续解析 HTML。
  • async script:解析 HTML 的过程中会进行脚本的异步下载,下载成功后立马执行,因此有可能会阻断 HTML 的解析。多个脚本的执行顺序无法保证。
  • defer script:也是异步下载脚步,加载完成后,如果此时 HTML 还没有解析完,浏览器不会暂停解析去执行 JS 代码,而是等待 HTML 解析完毕再执行 JS 代码,如果存在多个 defer script 标签,浏览器(IE9及以下除外)会保证它们按照在 HTML 中出现的顺序执行,不会破坏 JS 脚本之间的依赖关系。

7、面试

7.1、“DTD”简介

  • “DTD”(Doctype definition)中文释义为“文档类型定义”,定义我们这个HTML文件使用的是哪一个版本的html规范。
  • 定义和用法:1、<!DOCTYPE> 声明必须位于 HTML5 文档中的第一行,也就是位于 标签之前。该标签告知浏览器文档所使用的HTML规范。2、在所有 HTML 文档中规定!DOCTYPE是非常重要的,这样浏览器就能了解预期的文档类型。3、HTML 4.01 中的!DOCTYPE需要对 DTD 进行引用,因为 HTML 4.01 基于标准通用标记语言。而HTML 5基于XML(标准通用标记语言的子集),因此不需要对 DTD 进行引用,但是需要!DOCTYPE来规范浏览器的行为(让浏览器按照它们应该的方式来运行)。
  • HTML4.01与HTML5之间的差异:在HTML4.01中规定了三种不同的 <!DOCTYPE> 声明,分别是:Strict、Transitional 和 Frameset;但是在 HTML5中仅规定了一种。
  • HTML规范是我们书写HTML要遵循的规范,规定了我们使用的标签,语法。
  • HTML、css、js规范的维护和制定的组织叫w3c。
  • HTML1.0版本-HTML5版本。
  • HTML4.0版本,将结构和样式进行了分离,常用HTML4.01版本。
  • XHTML1.0在4.01版本基础进行了严格的优化和扩展升级,严格规定标签必须小写,属性必须用双引号包裹,结束标签的关闭符号/必须写。
  • 两个规范下面分别包含了三个小规范:
  1. strict: 严格版,不能使用font等废弃标签,不能使用框架集,结构和样式分离。
  2. transition: 过渡版(通用版),可以使用font等废弃标签,不能使用框架集。
  3. frame: 框架集版,可以使用框架集。
  4. 严格程度:XHTML1.0strict>HTML4.01strict>XHTML1.0transition>HTML4.01transition。

7.2、如何判断一个对象是不是空对象

方法1Objeck.keys(obj).length === 0
方法2JSON.stringify(obj) === '{}'

7.3、[] == ![]结果是什么

==中,左右两边都需要转换为数字然后进行比较。[]转换为数字为0。![]首先是转换为布尔值,由于[]作为一个引用类型转换为布尔值为true,因此![]为false,进而在转换成数字,变为0。0等于0,结果为true。

7.4、如何判断 JS 的数据类型?

  1. typeof
    typeof可以区分除了Null类型以外的其他基本数据类型,以及从对象类型中识别出函数(function)。
    其返回值有:number、string、boolean、undefined、symbol、bigint、function、object。
    其中, typeof null返回 “object”
    如果要识别null,可直接使用===全等运算符来判断。
typeof 1 // 'number'
typeof '1' // 'string'
typeof true // 'boolean'
typeof undefined // 'undefined'
typeof Symbol() // 'symbol'
typeof null // 'object'
typeof [] // 'object'
typeof {
    
    } // 'object'
typeof console // 'object'
typeof console.log // 'function'
  1. instanceof
    instanceof一般是用来判断引用数据类型,但不能正确判断基本数据类型,根据在原型链中查找判断当前数据的原型对象是否存在返回布尔类型。
1 instanceof Number; // false
true instanceof Boolean; // false
'str' instanceof String; // false
[] instanceof Array; // true
function(){
    
    } instanceof Function; // true
{
    
    } instanceof Object; // true
let date = new Date();
date instance of Date; // true
  1. Object.prototype.toString
Object.prototype.toString({
    
    }) // "[object Object]"
Object.prototype.toString.call({
    
    }) // 同上结果,加上call也ok
Object.prototype.toString.call(1) // "[object Number]"
Object.prototype.toString.call('1') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(function () {
    
    }) // "[object Function]"
Object.prototype.toString.call(null) //"[object Null]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call(/123/g) //"[object RegExp]"
Object.prototype.toString.call(new Date()) //"[object Date]"
Object.prototype.toString.call([]) //"[object Array]"
Object.prototype.toString.call(document) //"[object HTMLDocument]"
Object.prototype.toString.call(window) //"[object Window]"
  1. Array.isArray
    Array.isArray(value)可以判断 value 是否为数组。
Array.isArray([]); // true
Array.isArray({
    
    }); // false
Array.isArray(1); // false
Array.isArray('string'); // false
Array.isArray(true); // false

7.5、如何遍历对象的属性?

  • 遍历自身可枚举的属性(可枚举、非继承属性):Object.keys() 方法,该方法会返回一个由给定对象的自身可枚举属性组成的数组。
  • 遍历自身的所有属性(可枚举、不可枚举、非继承属性):Object.getOwnPropertyNames()方法,该方法会返回一个由指定对象的所有自身属性组成的数组。
  • 遍历可枚举的自身属性和继承属性:for … in …

7.6、如何判断两个对象是否相等?

  1. Object.is(obj1, obj2),判断两个对象都引用地址是否一致,true 则一致,false 不一致。
  2. 判断两个对象内容是否一致,思路是遍历对象的所有键名和键值是否都一致。
  • 判断两个对象是否指向同一内存。
  • 使用 Object.getOwnPropertyNames 获取对象所有键名数组。
  • 判断两个对象的键名数组是否相等。
  • 遍历键名,判断键值是否都相等。
function isObjValueEqual(a, b) {
    
    
  // 判断两个对象是否指向同一内存,指向同一内存返回 true
  if (a === b) return true;
  // 获取两个对象的键名数组
  let aProps = Object.getOwnPropertyNames(a);
  let bProps = Object.getOwnPropertyNames(b);
  // 判断两键名数组长度是否一致,不一致返回 false
  if (aProps.length !== bProps.length) return false;
  // 遍历对象的键值
  for (let prop in a) {
    
    
    // 判断 a 的键名,在 b 中是否存在,不存在,直接返回 false
    if (b.hasOwnProperty(prop)) {
    
    
      // 判断 a 的键值是否为对象,是对象的话需要递归;
      // 不是对象,直接判断键值是否相等,不相等则返回 false
      if (typeof a[prop] === 'object') {
    
    
        if (!isObjValueEqual(a[prop], b[prop])) return false;
      } else if (a[prop] !== b[prop]){
    
    
        return false
      }
    } else {
    
    
      return false
    }
  }
  return true;
}

7.7、new 操作符的实现机制

  1. 首先创建了一个新的空对象。
  2. 设置原型,将对象的原型设置为函数的prototype对象。
  3. 让函数的this指向这个对象,执行构造函数的代码(为这个新对象添加属性)。
  4. 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
function myNew(context) {
    
    
  const obj = new Object();
  obj.__proto__ = context.prototype;
  const res = context.apply(obj, [...arguments].slice(1));
  return typeof res === "object" ? res : obj;
}

7.8、this 的理解

  • 概念:
    this是 JS 的一个关键字,它是函数运行时,自动生成的一个内部对象,只能在函数内部使用,随着函数使用场合的不同,this的值会发生变化,但有一个总的原则:this指的是调用函数的那个对象。
  • this的指向:
    1. 作为普通函数执行时,this指向window,但在严格模式下this指向undefined。
    2. 函数作为对象里的方法被调用时,this指向该对象。
    3. 当用new运算符调用构造函数时,this指向返回的这个对象。
    4. 箭头函数的this绑定看的是this所在函数定义在哪个对象下,就绑定哪个对象。如果存在嵌套,则this绑定到最近的一层对象上。
    5. call()、apply()、bind()是函数的三个方法,都可以显示的指定调用函数的this指向。

7.9、call、apply、bind的区别以及手写实现

  1. call
    call()可以传递两个参数,第一个参数是指定函数内部中this的指向,第二个参数是函数调用时需要传递的参数。改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次。
// 实现call方法
Function.prototype.myCall = function (context) {
    
    
  // 判断调用对象
  if (typeof this != "function") {
    
    
    throw new Error("type error");
  }
  // 首先获取参数
  let args = [...arguments].slice(1);
  let res = null;
  // 判断context是否传入,如果没有,就设置为window
  context = context || window;
  // 将被调用的方法置入context的属性
  // this 即为要调用的方法
  context.fn = this;
  // 执行要被调用的方法
  res = context.fn(...args);
  // 删除手动增加的属性方法
  delete context.fn;
  // 执行结果返回
  return res;
}
  1. apply
    apply()接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入。改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次。
// 实现apply方法
Function.prototype.myApply = function(context) {
    
    
  if (typeof this != "function") {
    
    
    throw new Error("type error");
  }
  let res = null;
  context = context || window;
  // 使用 symbol 来保证属性唯一
  // 也就是保证不会重写用户自己原来定义在context中的同名属性
  const fnSymbol = Symbol();
  context[fnSymbol] = this;
  // 执行被调用的方法
  if (arguments[1]) {
    
    
    res = context[fnSymbol](...arguments[1]);
  } else {
    
    
    res = context[fnSymbol]();
  }
  delete context[fnSymbol];
  return res;
}
  1. bind
    bind()方法的第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)。改变this指向后不会立即执行,而是返回一个永久改变this指向的函数
// 实现bind方法
Function.prototype.myBind = function (context) {
    
    
  if (typeof this != "function") {
    
    
    throw new Error("type error");
  }
  let args = [...arguments].slice(1);
  const fn = this;
  return function Fn() {
    
    
    return fn.apply(
      this instanceof Fn ? this : context,
      // 当前这个 arguments 是指 Fn 的参数
      args.concat(...arguments)
    );
  };
}
  • call()和bind()第二个参数是列表形式的;apply()第二个参数是数组形式。
  • call()和apply()是立即执行;bind()不会立即执行而是生成一个修改this之后的新函数。

8.0、JavaScript 中内存泄漏的几种情况

内存泄漏一般是指系统进程不再用到的内存,没有及时释放,造成内存资源浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

造成内存泄漏的原因有:

  • 全局变量
    在局部作用域中,函数执行完毕后,变量就没有存在的必要了,垃圾回收机制很快的做出判断并回收;但是对于全局变量,很难判断什么时候不用这些变量,无法正常回收。
    解决办法:

    1. 尽量少使用全局变量;
    2. 使用严格模式,在 js 文件头部或者函数的顶部加上use strict。
  • 闭包引起的内存泄露
    闭包可以读取函数内部的变量,然后让这些变量始终保存在内存中,如果在使用结束后没有将局部变量清除,就可能导致内存泄露。
    解决办法:将事件处理函数定义在外部,解除闭包。

  • 被遗忘的定时器
    定时器setInterval或者setTimeout不再需要使用时,且没有被清除,导致定时器的回调函数及其内部依赖的变量都不能被回收,就会造成内存泄漏。
    解决办法:当不需要定时器的时候,调用clearInterval或者clearTimeout手动清除。

  • 事件监听
    垃圾回收机制不好判断事件是否需要被解除,导致callback不能被释放,此时需要手动解除绑定。
    解决办法:及时使用removeEventListener移除事件监听。

  • 元素引用没有清理
    解决办法:移除元素后,手动设置元素的引用为null。

  • console
    传递给console.log的对象是不能被垃圾回收,可能会存在内存泄漏。
    解决办法:清除不必要的console。

8.1、EventLoop 事件循环

js 是单线程运行的,当遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列–事件队列(Task Queue)。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码…,如此反复,这样就形成了一个无限的循环,这个过程被称为事件循环(Event Loop)。
实际上,异步任务之间并不相同,它们的执行优先级也有区别。异步任务分两类:微任务(micro task)和宏任务(macro task)。

微任务包括: promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver。
宏任务包括: script 脚本的执行,setTimeout 、setInterval 和setImmediate 一类的定时事件,还有如 I/O 操作,UI 渲染等。

在一个事件循环中,异步事件返回结果后会被放到一个事件队列中。然而,根据这个异步事件的类型,这个事件实际上会被对应的宏任务队列或者微任务队列中去。并且在当前执行栈为空的时候,主线程会查看微任务队列是否有事件存在。如果不存在,那么再去宏任务队列中取出一个事件并把对应的回调加入当前执行栈;如果存在,则会依次执行队列中事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的一个事件,把对应的回调加入当前执行栈…如此反复,进入循环。

8.2、HTTP 状态码有哪些

  1. 状态码第一位数字决定了不同的响应状态:
    1xx表示请求已被接受,需要继续处理;
    2xx表示请求成功;
    3xx表示重定向;
    4xx表示客户端错误;
    5xx表示服务端错误。
  2. 常见的状态码:
    101:服务器根据客户端的请求切换协议,主要用于websocket或http2升级
    200:请求已成功,请求所希望的数据将随响应一起返回。
    201:请求成功并且服务器创建了新的资源。
    202:服务器已接受响应请求,但尚未处理。
    301:请求的网页已永久移动至新的位置。
    302:临时重定向/临时转移。服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
    304:本次获取到的内容是读取缓存中的数据,会每次去服务器校验。
    401:请求需要进行身份验证,尚未认证,没有登录网站。
    403:禁止访问,服务器拒绝请求。
    404:服务器没有找到相应资源。
    500:服务器遇到错误,无法完成对请求的处理。
    503:服务器无法使用。

8.3、TCP 的三次握手和四次挥手

  • 三次握手:
    1. 第一次握手:客户端给服务器发送一个SYN报文。
    2. 第二次握手:服务器收到SYN报文后,应答一个ACK报文,同时发出自己的SYN报文,即应答了一个SYN + ACK报文。
    3. 第三握手:客户端收到SYN + ACK报文后,回应一个ACk报文。服务器收到ACK报文之后,三次握手完毕。
  • 四次挥手:
    1. 第一次挥手:客户端认为没有数据需要继续发送,于是向服务器发送一个FIN报文,申请断开客户端到服务器端的连接,报文中同时会指定一个序列号seq(等于已传送数据的最后一个字节的序号加1)。此时,客户端进入FIN_WAIT_1状态。
    2. 第二次挥手:服务器接收到FIN报文后,向客户端发送一个确认报文ACK,表示已经接收到了客户端释放连接的请求,以后不会再接收客户端发送过来的数据。同时会把客户端报文中的序列号seq加1,作为ACK报文中的序列号值。此时,服务端处于CLOSE_WAIT状态。客户端接收到报文之后,进入FIN_WAIT_2状态,等待服务器发送连接释放报文(由于TCP连接是全双工的,此时客户端到服务区器的连接已释放,但是服务器仍可以发送数据给客户端)。
    3. 第三次挥手:服务器端发送完所有的数据,会向客户端发送一个FIN报文,申请断开服务器端到客户端的连接。此时,服务器进入LAST_ACK 状态。
    4. 第四次挥手:客户端接收到FIN报文之后,向服务器发送一个ACK报文作为应答,且把服务器报文中的序列号值加1作为ACK报文的序列号值。此时,客户端就进入TIME_WAIT状态。需要注意的是,该阶段会持续一段时间,以确保服务器收到了该ACK报文。这个时间是 2 * MSL(最长报文段时间),如果在这个时间内,没有收到服务器的重发请求,时间过后,客户端就进入CLOSED状态;如果收到了服务器的重发请求就需要重新发送确认报文。服务器只要一收到ACK报文,就会立即进入CLOSED状态。

8.4、GET 和 POST 的区别

  1. 参数位置:GET请求的参数是放在 url 中,POST请求放在请求体 body 中。
  2. 参数长度:GET请求在 url 中传递的参数有长度限制(主要是因为浏览器对 url 长度有限制),POST则没有。
  3. 参数的数据类型:GET只接受ASCII字符,而POST没有限制。
  4. 安全:POST比GET安全,因为数据在地址栏上不可见。但是从传输角度考虑,二者都是不安全的,因为HTTP是明文传输。
  5. 幂等性:GET是一个幂等的请求;POST不是。(幂等,指同⼀个请求⽅法执⾏多次和仅执⾏⼀次的效果完全相同)。
  6. 缓存:GET请求会被浏览器主动cache,而POST不会,除非手动设置。

8.5、从输入URL到看到页面的过程,发生了什么

  1. url解析:首先会判断输入的是一个合法 url还是关键词,并根据输入的内容进行相应的操作。
  2. 查找缓存:浏览器会判断所请求的资源是否在浏览器缓存中,以及是否失效。如果没有失效就直接使用;如果没有缓存或失效了,就继续下一步。
  3. DNS解析:此时需要获取url中域名对应的IP地址。浏览器会依次查看浏览器缓存、操作系统缓存中是否有ip地址,如果缓存中没有就会向本地域名服务器发起请求,获取ip地址。本地域名服务器也会先检查缓存,有则直接返回;如果也没有,则采用迭代查询方式,向上级域名服务器查询。先向根域名服务器发起请求,获取顶级域名服务器的地址;再向顶级域名服务器发起请求以获取权限域名服务器地址;然后向权限域名服务器发起请求并得到url中域名对应的IP地址。
  4. 建立TCP连接:根据ip地址,三次握手与服务器建立TCP连接。
  5. 发起请求:浏览器向服务器发起HTTP请求。
  6. 响应请求:服务器响应HTTP请求,将相应的HTML文件返回给浏览器。
  7. 关闭TCP连接:四次挥手关闭TCP连接。
  8. 渲染页面:浏览器解析HTML内容,并开始渲染。浏览器渲染过程如下:
    • 构建DOM树:词法分析然后解析成DOM树,DOM树是由DOM元素及属性节点组成,树的根是document对象。
    • 构建CSS规则树:生成CSS 规则树。
    • 构建渲染树:将DOM树和CSS规则树结合,构建出渲染树。
    • 布局:计算每个节点的位置。
    • 绘制:使用浏览器的UI接口进行绘制。

8.6、即时通讯的实现方式

主要有四种方式,它们分别是轮询、长轮询(comet)、长连接(SSE)、WebSocket。它们大体可以分为两类,一种是在HTTP基础上实现的,包括短轮询、comet和SSE;另一种不是在HTTP基础上实现是,即WebSocket。

  1. 轮询。
    短轮询的基本思路就是浏览器每隔一段时间向浏览器发送http请求,服务器在收到请求后,不论是否有数据更新,都直接进行响应。这种方式实现的即时通信,本质上还是浏览器发送请求,服务器接受请求的一个过程,通过让客户端不断的进行请求,使得客户端能够模拟实时地收到服务器端的数据的变化。
    优点:比较简单,易于理解,实现起来没有什么技术难点。
  2. 长轮询。
    当服务器收到客户端发来的请求后,服务器端不会直接进行响应,而是先将这个请求挂起,然后判断服务器端数据是否有更新。如果有更新,则进行响应;如果一直没有数据,则到达一定的时间限制(服务器端设置)才返回。
    优点:长轮询和短轮询比起来,明显减少了很多不必要的http请求次数,相比之下节约了资源。
    缺点:连接挂起也会导致资源的浪费。
  3. 长连接。
    SSE是HTML 5新增的功能,全称为Server-Sent Events。它可以允许服务推送数据到客户端。SSE在本质上就与之前的长轮询、短轮询不同,虽然都是基于http协议的,但是轮询需要客户端先发送请求。而SSE最大的特点就是不需要客户端发送请求,可以实现只要服务器端数据有更新,就可以马上发送到客户端。
    优点:不需要建立或保持大量的客户端发往服务器端的请求,节约了很多资源,提升应用性能;实现非常简单,并且不需要依赖其他插件。
  4. WebSocket。
    WebSocket是HTML 5定义的一个新协议,与传统的http协议不同,该协议可以实现服务器与客户端之间全双工通信。简单来说,首先需要在客户端和服务器端建立起一个连接,这部分需要http。连接一旦建立,客户端和服务器端就处于平等的地位,可以相互发送数据,不存在请求和响应的区别。
    优点:实现了双向通信。
    缺点:服务器端的逻辑非常复杂。

猜你喜欢

转载自blog.csdn.net/weixin_44767973/article/details/126307005