番外03:前端面试八股文-javaScript

devtools/2025/2/13 18:08:08/

一:数据类型

1:JavaScript有哪些数据类型,它们的区别?

Undefined | Null | Boolean | Number | String | Object | Symbol | BigInt

Undefined:表示一个变量已经声明,但没有赋值(一个变量的值未定义)

null:表示"空" / ”无“(空值、空对象、清空某个变量)

2:数据类型检测的方法有哪些

(1)typeof

javascript">console.log(typeof 2);               // number
console.log(typeof true);            // boolean
console.log(typeof 'str');           // string
console.log(typeof []);              // object  
console.log(typeof function(){});    // function
console.log(typeof {});              // object
console.log(typeof undefined);       // undefined
console.log(typeof null);            // object

数组、对象、null都会判定为Object 

(2)instanceof

javascript">console.log(2 instanceof Number);                    // false
console.log(true instanceof Boolean);                // false 
console.log('str' instanceof String);                // false console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object);                   // true

instanceof只能正确判断引用数据类型,不能判断基本数据类型

(3)constructor

javascript">console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true

(4)Object.prototype.toString.call()

javascript">var a = Object.prototype.toString;console.log(a.call(2));
console.log(a.call(true));
console.log(a.call('str'));
console.log(a.call([]));
console.log(a.call(function(){}));
console.log(a.call({}));
console.log(a.call(undefined));
console.log(a.call(null));

3:判断数组的方式有哪些

(1)通过Object.prototype.toString.call()做判断

javascript">Object.prototype.toString.call(obj).slice(8,-1) === 'Array';

(2)通过原型链做判断

javascript">obj.__proto__ === Array.prototype;

(3)通过ES6的Array.isArray()做判断

javascript">Array.isArrray(obj);

(4)通过instanceof做判断

javascript">obj instanceof Array

(5)通过Array.prototype.isPrototypeOf()

javascript">Array.prototype.isPrototypeOf(obj)

4:null和undefined的区别

相同点:

        undefined和null都是基本数据类型 🔜 都只有一个值:undefined和null

不同点:

        undefined代表的是未定义 🔜 变量声明了但没有定义时会返回undefined

        null赋值给一些可能会返回对象的变量,作为初始化

        typeof undefined = undefined

        typeof null = Object

        null == undefined 🔜 true

        null === undefined 🔜 false

5:intanceof操作符的实现原理及实现

instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。

javascript">function myInstanceof(left, right) {// 获取对象的原型let proto = Object.getPrototypeOf(left)// 获取构造函数的 prototype 对象let prototype = right.prototype; // 判断构造函数的 prototype 对象是否在对象的原型链上while (true) {if (!proto) return false;if (proto === prototype) return true;// 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型proto = Object.getPrototypeOf(proto);}
}

6:如何获取安全的undefined值

因为 undefined 是一个标识符,所以可以被当作变量来使用和赋值,但是这样会影响 undefined 的正常判断。表达式 void ___ 没有返回值,因此返回结果是 undefined。void 并不改变表达式的结果,只是让表达式不返回值。因此可以用 void 0 来获得 undefined。

javascript">let safeUndefined = void 0;

7:typeof NaN的结果是什么

NaN:not a number(不是一个数字),一个警戒值,用于指出数字类型中的错误情况。

即:执行数学运算没有成功,这是失败后返回的结果

8:isNaN和Number.isNaN的区别

isNaN:

  • isNaN 会先尝试将参数转换为数值,再判断是否为 NaN
  • 如果参数不是数值类型,它会先进行 隐式类型转换,可能导致意外的 true 结果
javascript">console.log(isNaN(NaN));        // true ✅
console.log(isNaN(123));        // false ✅
console.log(isNaN("123"));      // false ❌(字符串 "123" 会被转换成数字 123)
console.log(isNaN("hello"));    // true ✅("hello" 转换为数字后是 NaN)
console.log(isNaN(undefined));  // true ✅(undefined 先转换成 NaN)
console.log(isNaN(null));       // false ❌(null 先转换成 0)
console.log(isNaN(true));       // false ❌(true 先转换成 1)

Number.isNaN:

  • Number.isNaN 不会进行类型转换,只在 参数本身就是 NaN 时才返回 true
  • 更安全,更推荐使用!
javascript">console.log(Number.isNaN(NaN));        // true ✅
console.log(Number.isNaN(123));        // false ✅
console.log(Number.isNaN("123"));      // false ✅(不会转换字符串)
console.log(Number.isNaN("hello"));    // false ✅(不会转换字符串)
console.log(Number.isNaN(undefined));  // false ✅
console.log(Number.isNaN(null));       // false ✅
console.log(Number.isNaN(true));       // false ✅

9:==操作符的强制类型转换规则

宽松相等操作符会进行类型转换 🔜 比较不同类型的值 🔜 会将其转换为相同的类型再进行比较。

10:|| 和 &&操作符的返回值?

|| 和 &&首先会对第一个操作数执行条件判断 🔜 不是布尔值就先强制转换为布尔类型 🔜 再执行条件判断

  • 对于 || 来说,如果条件判断结果为 true 就返回第一个操作数的值,如果为 false 就返回第二个操作数的值。
  • && 则相反,如果条件判断结果为 true 就返回第二个操作数的值,如果为 false 就返回第一个操作数的值

11:Object.is()与比较操作符"==="、"=="的区别?

使用==进行相等判断时 🔜 如果两边的类型不一致 🔜 进行强制类型转化后再进行比较

使用===进行相等判断时 🔜 两边数据类型不一致 🔜 不会进行强制类型转换,直接返回false

使用Object.is进行相等判断 🔜 一般情况下和===的判断相同 🔜 处理了特殊情况 🔜 -0和+0不再相等 🔜 两个NaN是相等的。

12:+ 操作符什么时候用于字符串拼接

如果 + 的其中一个操作数是字符串(或者通过以上步骤最终得到字符串),则执行字符串拼接,否则执行数字加法

二:ES6

1:let、const、var的区别

(1)块级作用域:

let和const具有块级作用域,var不存在块级作用域,块级作用域解决了ES5的两个问题:

        内层变量可能覆盖外层变量

        用来计数的循环变量泄露为全局变量

(2)变量提升

var存在变量提升 🔜let和const不存在变量提升 🔜 变量只能在声明之后使用

(3)各全局添加属性

var声明的变量为全局变量 🔜 并且会将该变量添加为全局对象的属性 🔜 let和const不会

(4) 重复声明

var声明变量时,可以重复声明变量 🔜 后声明的同名变量会覆盖之前声明的变量

const和let不允许重复声明变量

(5)暂时性死区

在使用let、const命令声明变量之前,该变量都是不可用的(暂时性死区)

(6)初始值设置

变量声明时,var和let可以不用设置初始值。

const声明变量必须设置初始值

(7)指针指向

let 创建的变量可以更改指针指向

const声明的变量不允许改变指针指向

2:const对象的属性可以修改吗

const不能改变对象的引用,但对象的属性可以修改

javascript">const obj = { name: "Alice", age: 25 };obj.age = 26; // ✅ 允许修改属性
obj.city = "New York"; // ✅ 允许添加新属性console.log(obj); // { name: "Alice", age: 26, city: "New York" }

如果让对象的属性也不可修改 🔜 使用Object.freeze()或深度冻结

3:箭头函数与普通函数的区别

(1)箭头函数比普通函数更加简洁

  • 如果没有参数,就直接写一个空括号即可
  • 如果只有一个参数,可以省去参数的括号
  • 如果有多个参数,用逗号分割
  • 如果函数体的返回值只有一句,可以省略大括号
  • 如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个void关键字。最常见的就是调用一个函数:

(2)箭头函数没有自己的this

箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。

(3)箭头函数继承来的this指向永不改变

javascript">getName 是一个箭头函数,它的 this 不是 obj,而是定义时的外层作用域(通常是 window 或 global)。
在浏览器环境下,window.name 默认是 ""(空字符串),但 this.name 是 undefined,所以打印 undefined。
const obj = {name: "Alice",getName: () => {console.log(this.name);}
};obj.getName(); // undefined

(4)call()、apply()、bind()等方法不能改变箭头函数中this的指向

javascript">var id = 'Global';
let fun1 = () => {console.log(this.id)
};
fun1();                     // 'Global'
fun1.call({id: 'Obj'});     // 'Global'
fun1.apply({id: 'Obj'});    // 'Global'
fun1.bind({id: 'Obj'})();   // 'Global'

(5)箭头函数不能作为构造函数使用

(6)箭头函数没有自己的arguments

javascript">const arrowFn = () => {console.log(arguments);
};arrowFn(1, 2, 3); // ❌ 报错:arguments is not defined

箭头函数没有自己的arguments,直接访问会导致ReferenceError

箭头函数继承外层函数的arguments

javascript">function outerFunction() {const arrowFn = () => {console.log(arguments);};arrowFn();
}outerFunction(1, 2, 3); // 输出:Arguments(3) [1, 2, 3]

arrowFn 继承了 outerFunctionarguments,所以能正确访问 [1, 2, 3]

(7)箭头函数没有prototype

(8)箭头函数不能用作Generator函数,不能使用yeild关键字

3:扩展运算符及其使用场景

(1)对象扩展运算符

对象的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。

javascript">let bar = { a: 1, b: 2 };
let baz = { ...bar }; // { a: 1, b: 2 }

上述方法实际上等价于

javascript">let bar = { a: 1, b: 2 };
let baz = Object.assign({}, bar); // { a: 1, b: 2 }

 (2)数组扩展运算符

        赋值数组  

javascript">const arr1 = [1, 2];
const arr2 = [...arr1];

        合并数组

javascript">const arr1 = ['two', 'three'];
const arr2 = ['one', ...arr1, 'four', 'five'];
// ["one", "two", "three", "four", "five"]

        将字符串转化为真正的数组

javascript">[...'hello']    // [ "h", "e", "l", "l", "o" ]

3:ES6中模板语法与字符串处理

(1)模板语法 ${}

  • 在模板字符串中,空格、缩进、换行都会被保留
  • 模板字符串完全支持“运算”式的表达式,可以在${}里完成一些计算

(2)字符串处理

        存在性判定:includes、startWith、endsWith

        自动重复:可以使用 repeat 方法来使同一个字符串输出多次(被连续复制多次)

javascript">const sourceCode = 'repeat for 3 times;'
const repeated = sourceCode.repeat(3) 
console.log(repeated) // repeat for 3 times;repeat for 3 times;repeat for 3 times;

三:JavaScript基础

1:new操作符的实现原理

(1)首先创建了一个新的空对象

(2)设置原型,将对象的原型设置为函数的 prototype 对象。

(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)

(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。

具体实现:

javascript">function objectFactory() {let newObject = null;let constructor = Array.prototype.shift.call(arguments);let result = null;// 判断参数是否是一个函数if (typeof constructor !== "function") {console.error("type error");return;}// 新建一个空对象,对象的原型为构造函数的 prototype 对象newObject = Object.create(constructor.prototype);// 将 this 指向新建对象,并执行函数result = constructor.apply(newObject, arguments);// 判断返回对象let flag = result && (typeof result === "object" || typeof result === "function");// 判断返回结果return flag ? result : newObject;
}
// 使用方法
objectFactory(构造函数, 初始化参数);

2:map和Object的区别

(1)Map允许所有数据类型作为键

javascript">const obj = {};
obj[1] = "Number Key";   // 实际上变成了 obj["1"]
obj["name"] = "String Key";
obj[true] = "Boolean Key"; // 变成了 obj["true"]
console.log(obj);

Object的键会被强制转换为字符串

(2)Map维护插入顺序,Object不保证

Map遍历时保持插入顺序

javascript">const map = new Map();
map.set("a", 1);
map.set("b", 2);
map.set("c", 3);
console.log([...map.keys()]); // ['a', 'b', 'c']

Object遍历的键顺序可能不同,特别是数值键会优先排序

javascript">const obj = {};
obj.b = 2;
obj.a = 1;
obj.c = 3;
console.log(Object.keys(obj)); // 可能 ['a', 'b', 'c'],但在某些情况下可能不同

(3)Map可以直接获取大小,而Object需要重新计算

Map直接提供.size

javascript">const map = new Map();
map.set("a", 1);
map.set("b", 2);
console.log(map.size); // 2 ✅

Object 需要 Object.keys(obj).length 来计算大小

javascript">const obj = { a: 1, b: 2 };
console.log(Object.keys(obj).length); // 2 ❌ 需要手动计算

(4)Map和Object的遍历方式

Map支持for...of...直接获取键值对,也可以用.forEach()

javascript">const map = new Map();
map.set("a", 1);
map.set("b", 2);for (let [key, value] of map) {console.log(key, value);
}
// 输出:
// a 1
// b 2

Object需要for...in或Object.keys(obj).forEach()来遍历

javascript">const obj = { a: 1, b: 2 };
for (let key in obj) {console.log(key, obj[key]);
}
// 输出:
// a 1
// b 2

(5)性能考量

Map适合高频插入,删除数据,效率比Object更高

Object使用delete删除属性会影响性能,因为它影响js引擎优化机制

3:对于json的理解

JSON(JavaScript Object Notation,JavaScript 对象表示法)是一种轻量级的数据交换格式 🔜 主要用于在服务器和客户端之间传输数据

它基于 JavaScript 的对象语法,但独立于编程语言,可以被多种语言解析(如 Python、Java、C#)。

JSON的特点:

        轻量:结构简单,数据量小,适合网络传输

        易读易写:格式清晰、可读性强

        语言无关:几乎所有编程语言都支持JSON

        支持复杂数据结构:可以表示数组、对象、字符串、数值、布尔值等

4:数组有哪些原生方法

  • 数组和字符串的转换方法:toString()、toLocalString()、join() 其中 join() 方法可以指定转换为字符串时的分隔符。
  • 数组尾部操作的方法 pop() 和 push(),push 方法可以传入多个参数。
  • 数组首部操作的方法 shift() 和 unshift() 重排序的方法 reverse() 和 sort(),sort() 方法可以传入一个函数来进行比较,传入前后两个值,如果返回值为正数,则交换两个参数的位置。
  • 数组连接的方法 concat() ,返回的是拼接好的数组,不影响原数组。
  • 数组截取办法 slice(),用于截取数组中的一部分返回,不影响原数组。
  • 数组插入方法 splice(),影响原数组查找特定项的索引的方法,indexOf() 和 lastIndexOf() 迭代方法 every()、some()、filter()、map() 和 forEach() 方法
  • 数组归并方法 reduce() 和 reduceRight() 方法

5:常见的位运算符有哪些?其计算规则是什么?

6:什么是DOM和BOM?

(1)DOM

DOM指的是文档对象模型 🔜 把文档当作一个对象 🔜 对象主要定义了处理网页内容的方法和接口。

(2)BOM

BOM指的是浏览器对象模型 🔜 把浏览器当做一个对象来对待 🔜 这个对象主要定义了与浏览器进行交互的方法和接口。

BOM的核心是window 🔜 window对象具有双重角色 🔜 通过js访问浏览器窗口的一个接口,又是一个Global(全局)对象

7:对类数组对象的理解,如何转化为数组

一个拥有length属性和若干索引属性的对象:类数组对象 🔜 不能调用数组的方法 🔜 常见类数组对象有arguments和DOM方法的返回结果

(1)通过call调用数组的slice方法来实现转换

Array.prototype.slice.call(arrayLike);

(2)通过call调用数组的splice方法来实现转换

Array.prototype.splice.call(arrayLike, 0);

(3)通过apply调用数组的concat方法来实现转换

Array.prototype.concat.apply([], arrayLike);

(4)通过Array.from方法来实现转换

Array.from(arrayLike);

8:escape、encodeURI、encodeURIComponent 的区别

encodeURI:对整个URI进行转义 🔜 将URI的非法字符转换为合法字符 🔜 对URI中有特殊意义的字符不会进行转义

encodeURIComponent:对URI的组成部分进行转义 🔜 一些特殊字符也会进行转义

应用场景:

对URL进行编码 🔜 确保URL中的特殊字符(如空格、斜杆、问号等)不会被误解或破坏URL的结构。

9:对于AJAX的理解

10:JavaScript为什么要进行变量提升,它导致了什么问题

原因:

  • 解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间
  • 声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行

问题:

var tmp = new Date();function fn(){console.log(tmp);if(false){var tmp = 'hello world';}
}fn();  // undefined

11:什么是尾调用,尾调用有什么好处

尾调用:函数的最后一步调用另一个函数。

代码执行基于执行栈 🔜 一个函数调用另一个函数,会保留当前执行的上下文 🔜 再新建另外一个执行上下文加入栈中。

尾调用是函数最后一步 🔜 可以不必再保留当前执行的上下文,从而节省了内存。

12:ES6模块和CommonJS模块有什么异同

(1)语法差异

ES6模块使用import和export来导入和导出模块

// 导出
export const foo = 42;
export function bar() { ... }// 导入
import { foo, bar } from './module.js';

CommonJS模块使用require和module.exports来导入和导出模块

// 导出
module.exports = { foo: 42, bar: function() { ... } };// 导入
const { foo, bar } = require('./module');

(2)加载方式

ES6模块是静态加载的 🔜 意味着模块在编译阶段就确定了依赖关系

CommonJS模块是动态加载 🔜 意味着每次调用require时,都会执行模块代码

13:常见的DOM操作有哪些

(1)DOM节点的获取

getElementById // 按照 id 查询
getElementsByTagName // 按照标签名查询
getElementsByClassName // 按照类名查询
querySelectorAll // 按照 css 选择器查询// 按照 id 查询
var imooc = document.getElementById('imooc') // 查询到 id 为 imooc 的元素
// 按照标签名查询
var pList = document.getElementsByTagName('p')  // 查询到标签为 p 的集合
console.log(divList.length)
console.log(divList[0])
// 按照类名查询
var moocList = document.getElementsByClassName('mooc') // 查询到类名为 mooc 的集合
// 按照 css 选择器查询
var pList = document.querySelectorAll('.mooc') // 查询到类名为 mooc 的集合

(2)DOM节点的创建

创建一个新节点,并把它添加到指定节点的后面

14:use strict是什么意思?使用其的区别是什么

use strict是一种ECMAscript5添加的(严格模式)运行模式 🔜 这种模式使得Javascript在更严格的条件下运行。

15:如何判断一个对象是否属于某个类

(1)使用instanceof运算符来判断构造函数的prototype属性是否出现在对象原型链中的任何位置

javascript">class MyClass {constructor() {}
}const obj = new MyClass();console.log(obj instanceof MyClass);  // true
console.log(obj instanceof Object);  // true,因为所有类都是 Object 的子类

 (2)通过对象的constructor属性来判断

javascript">class MyClass {constructor() {}
}const obj = new MyClass();console.log(obj.constructor === MyClass);  // true

16:for...in和for..of的区别

(1)遍历对象的不同

for...in遍历的对象是可枚举属性(包括继承的属性),其会遍历对象的所有键名

for...of遍历的是可迭代对象的值

(2)遍历的数据类型

for...in:适用于遍历对象的属性名

for...of:适用于遍历可迭代对象

(3)性能差异

for...in通常用于对象的遍历,其性能会稍微低一些

for...of专门用来遍历可迭代对象,其对性能进行了优化

17:数组的遍历方法有哪些

方法是否改变原数组特点
forEach()数组方法,不改变原数组,没有返回值
map()数组方法,不改变原数组,有返回值
filter()数组方法,过滤数组,返回包含符合条件的元素的数组,可链式调用
for...of()遍历具有Iterator迭代器对象的属性
every()和some()some()只要有一个是true,便会返回true;every()只要有一个是false,便会返回false
find()和findIndex()数组方法,find()返回的是第一个符合条件的值,findIndex()返回的是第一个符合条件的索引值
reduce()和reduceRight()数组方法,reduce()对数组正序操作;reduceRight()对数组逆序操作

四:原型与原型链

1:对原型、原型链的理解

(1)原型

使用构造函数新建一个对象后 🔜 对象内部包含一个指针,指针指向构造函数prototype属性对应的值 🔜 ES5中这个指针被成为对象的原型。

(2)原型链

访问一个对象属性时,对象内部不存在这个属性 🔜 去它的原型对象里找这个属性 🔜 这个原型对象又会有自己的原型 🔜 这样一直找下去,就是原型链的概念

2:原型修改和重写

(1)原型修改

原型修改指的是直接更改一个对象的原型 🔜 通常是通过修改prototype属性或者使用Object.setPrototypeOf()方法。

        1)修改实例对象的原型
javascript">// 使用 __proto__ 修改原型
const obj = {};
obj.__proto__.sayHello = function() {console.log("Hello from modified prototype!");
};obj.sayHello();  // 输出: "Hello from modified prototype!"
javascript">// 使用 Object.setPrototypeOf 修改原型
const animal = {speak() {console.log("Animal speaks");}
};const dog = {};
Object.setPrototypeOf(dog, animal);
dog.speak();  // 输出: "Animal speaks"
        2)修改构造函数的原型
javascript">function Person(name) {this.name = name;
}// 添加原型方法
Person.prototype.sayHi = function() {console.log(`Hi, I'm ${this.name}`);
};const john = new Person('John');
john.sayHi();  // 输出: "Hi, I'm John"// 修改原型
Person.prototype.sayHi = function() {console.log(`Hello, I'm ${this.name}`);
};john.sayHi();  // 输出: "Hello, I'm John"

(2)原型重写

原型重写则是指完全替换对象的原型,而不是修改原型上的某个属性

        1)重写构造函数的原型
javascript">function Animal() {this.type = "animal";
}Animal.prototype = {speak() {console.log("Animal speaks");}
};const dog = new Animal();
dog.speak();  // 输出: "Animal speaks"// 重写原型
Animal.prototype = {speak() {console.log("Dog barks");}
};const dog2 = new Animal();
dog2.speak();  // 输出: "Dog barks"
        2)重写对象的原型

可以通过直接将一个新的对象赋给实例的 __proto__ 来重写实例的原型:

javascript">const dog = {speak() {console.log("Bark");}
};// 重写实例的原型
dog.__proto__ = {speak() {console.log("Meow");}
};dog.speak();  // 输出: "Meow"

(3)原型修改和原型重写

原型修改:原有原型的基础上修改或添加新的属性和方法 🔜 通常保留原型链上的其他属性和方法

原型重写:指用一个新的对象完全替换原型,通常会导致丢失原型链上原来的方法和属性

3:原型链的终点是什么?如何打印出原型链的终点?

原型链的终点是Object.prototype._proto_,而Object.prototype._proto_ === null //true,所以原型链的终点是null。

五:执行上下文/作用域链/闭包

1:对闭包的理解

闭包就是一个函数能够访问其外部函数的变量,即使外部函数已经执行完毕并返回

(1)闭包的例子

        1)基本的闭包
javascript">function outer() {let outerVar = "I'm outside!";function inner() {console.log(outerVar); // 内部函数访问外部函数的变量}return inner; // 返回内部函数
}const closureFunc = outer();  // outer 执行并返回 inner
closureFunc();  // 输出: "I'm outside!"
        2)通过闭包实现数据封装

        闭包常用于数据封装和创建私有变量

javascript">function createCounter() {let count = 0;  // `count` 是一个私有变量return {increment: function() {count++;console.log(count);},decrement: function() {count--;console.log(count);},getCount: function() {return count;}};
}const counter = createCounter();
counter.increment();  // 输出: 1
counter.increment();  // 输出: 2
counter.decrement();  // 输出: 1
console.log(counter.getCount());  // 输出: 1

(2)闭包的优势和弊端

        优势:

        数据封装和私有化:闭包使得某些数据只能通过特定的函数来访问,避免了外部直接修改。

        延迟计算:闭包可以实现延迟执行的功能、像缓存、惰性加载等

        维持状态:闭包使得函数能够记住执行时的状态,适用于某些需要状态持久化的场景

        弊端:

        内存占用:闭包会让外部函数的局部变量一直保存在内存中,直到闭包不再被使用。这可能会导致内存泄露。

        调试难度:过度使用闭包可能导致代码逻辑复杂。

2:对作用域的理解

(1)全局作用域和函数作用域

        1)全局作用域

        最外层函数和最外层函数外面定义的变量拥有全局作用域

        所有未定义直接赋值的变量自动声明为全局作用域

        所有window对象的属性拥有全局作用域

        全局作用域有很大的弊端,过多的全局作用域会污染命名空间

        2)函数作用域

        声明在函数内部的变量,一般只有固定的代码片段可以访问

        作用域是分层的,内层作用域可以访问外层作用域,反之不行

(2)块级作用域

        ES6新增的let和const指令可以声明块级作用域

        let和const声明的变量不会有变量提升,也不可以重复声明

        循环中比较适合绑定块级作用域 🔜 这样就可以把声明的计数器变量限制在循环内部

六:this/call/apply/bind

1:对this对象的理解

this是执行上下文中的一个属性,其指向最后一次调用这个方法的对象。

(1)普通函数调用

当函数以普通方式调用时,this指向全局对象

javascript">function show() {console.log(this);  // 在浏览器中指向 window,非严格模式下
}show();

(2)对象方法调用(隐式绑定)

当函数作为对象的方法被调用时,this指向该对象

javascript">const person = {name: 'John',greet: function() {console.log(this.name);}
};person.greet();  // 输出 'John'

(3)构造函数调用(显示绑定)

使用new关键字调用函数时,this会指向新创建的空对象

javascript">function Person(name) {this.name = name;
}const john = new Person('John');
console.log(john.name);  // 输出 'John'

(4)call()和apply()调用(显示绑定)

call()和apply()方法允许你显式地指定this的值,其作用是改变函数的上下文

        call():接受一个指定的this值和参数列表

        apply():与call()类似,但接受一个数组作为参数

javascript">function greet() {console.log(this.name);
}const person = { name: 'John' };greet.call(person);  // 输出 'John'
greet.apply(person);  // 输出 'John'

(5)bind()方法(显示绑定)

bind()方法会返回一个新的函数,允许你指定该函数的this值 🔜 返回的函数在调用时会使用指定的this.

javascript">function greet() {console.log(this.name);
}const person = { name: 'John' };
const greetPerson = greet.bind(person);greetPerson();  // 输出 'John'

2:call()和apply()的区别

call()和apply()都是javaScript中的函数内置方法,其作用相似:都可以用来改变函数内部的this指向,并且可以显示地传递参数。

(1)call()方法

call()方法接受若干个单独的参数,其会把这些参数传递给调用的函数。

javascript">function greet(message) {console.log(`${message}, ${this.name}`);
}const person = { name: 'John' };greet.call(person, 'Hello');  // 输出: "Hello, John"

(2)apply()方法

apply()方法和call()类似,唯一的区别是它接受一个包含所有参数的数组或类数组对象,而不是单独的参数

javascript">function greet(message) {console.log(`${message}, ${this.name}`);
}const person = { name: 'John' };greet.apply(person, ['Hello']);  // 输出: "Hello, John"

七:垃圾回收和内存泄露

1:浏览器的垃圾回收机制

(1)垃圾回收的概念

垃圾回收:JavaScript代码运行时,需要分配内存空间来储存变量和值 🔜 当变量不在参与运行时,就需要系统收回被占用的空间。

回收机制

  • Javascript 具有自动垃圾回收机制,会定期对那些不再使用的变量、对象所占用的内存进行释放,原理就是找到不再使用的变量,然后释放掉其占用的内存。
  • JavaScript中存在两种变量:局部变量和全局变量。全局变量的生命周期会持续要页面卸载;而局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束,在这个过程中,局部变量会在堆或栈中存储它们的值,当函数执行结束后,这些局部变量不再被使用,它们所占有的空间就会被释放。
  • 不过,当局部变量被外部函数使用时,其中一种情况就是闭包,在函数执行结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使用,所以不会回收。

(2)减少垃圾回收

        1)对数组进行优化

        在清空一个数组时,最简单的方法就是给其赋值为[ ],但是与此同时会创建一个新的空对象,可以将数组的长度设置为0,以此来达到清空数组的目的。

        2)对object进行优化

        对象尽量复用,对于不再使用的对象,就将其设置为null,尽快被回收

        3)对函数进行优化

        在循环中的函数表达式,如果可以复用,尽量放在函数的外面

2:哪些情况会导致内存泄露

        (1)意外的全局变量:使用未声明的变量 🔜 意外创建一个全局变量 🔜 这个变量一直留在内存中无法被回收。

        (2)被遗忘的计时器或回调函数:设置了setInterval定时器,忘记取消它

        (3)获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收.

        (4)闭包


http://www.ppmy.cn/devtools/158557.html

相关文章

单机伪分布HBase配置

目录 1. 引言2. 配置单机伪分布HBase2.1 下载并解压HBase2.2 配置环境变量2.3 配置单机伪分布HBase2.3.1 hbase-env.sh2.3.2 hbase-site.xml2.3.3 验证HBase 参考 1. 引言 前面提到我更换了系统盘,但是在更换系统之前我在原先的Hadoop镜像中配置了HBase,…

如何在 Java 应用中实现数据库的主从复制(读写分离)?请简要描述架构和关键代码实现?

在Java应用中实现数据库主从复制(读写分离) 一、架构描述 (一)整体架构 主库(Master) 负责处理所有的写操作(INSERT、UPDATE、DELETE等)。它是数据的源头,所有的数据变…

SWIFT 后训练 Deepseek-VL2 参数

SWIFT 后训练 Deepseek-VL2 参数 flyfish [INFO:swift] args: TrainArguments( _n_gpu-1, acc_steps1, acc_strategytoken, accelerator_config{dispatch_batches: False}, adafactorFalse, adalora_beta10.85, adalora_beta20.85, adalora_deltaT1, adalora_init_r12, adalo…

在vivado中对数据进行延时,时序对齐问题上的理清

在verilog的ISP处理流程中,在完成第一个模块的过程中,我经常感到困惑,到底是延时了多少个时钟?今日对这几个进行分类理解。 目录 1.输入信号激励源描述 1.1将数据延时[9]个clk 1.2将vtdc与hzdc延时[9]个clk(等价于单bit的数据…

【Elasticsearch】文本分析Text analysis概述

文本分析概述 文本分析使 Elasticsearch 能够执行全文搜索,搜索结果会返回所有相关的结果,而不仅仅是完全匹配的结果。 如果你搜索“Quick fox jumps”,你可能希望找到包含“A quick brown fox jumps over the lazy dog”的文档&#xff0c…

洛谷算法1-3 暴力枚举

目录 1 P2241统计方形 2 三连击 3 选数 4 P1088 [NOIP2004 普及组] 火星人 5 P3799 小 Y 拼木棒 排列组合 6 P2392 kkksc03考前临时抱佛脚 7 P2036 [COCI2008-2009 #2] PERKET 1 P2241统计方形 思路: 本题中,矩阵数量正方形数量长方形数量&#xff0…

uniApp 实现下拉框自定义标签 label 和值 value

文章目录 问题分析 问题 在 uniApp 中 参考至 https://blog.csdn.net/qq_34645412/article/details/140442672 分析 <view class"contents"><uni-formsref"baseForm":model"formData"labelWidth"70px":rules"rules&…

拯救者Y9000P双系统ubuntu22.04安装4070显卡驱动

拯救者Y9000P双系统ubuntu22.04安装4070显卡驱动 1. 前情&#xff1a; 1TB的硬盘&#xff0c;分了120G作ubuntu22.04。/boot: 300MB, / : 40GB, /home: 75G, 其余作swap area。 2. 一开始按这个教程&#xff1a;对我无效 https://blog.csdn.net/Eric_xkk/article/details/1…