您的位置:首页 > 教育 > 培训 > 安阳信息网_代做作业网站_深圳网站设计制作_南宁seo规则

安阳信息网_代做作业网站_深圳网站设计制作_南宁seo规则

2025/6/9 8:18:37 来源:https://blog.csdn.net/weixin_38984030/article/details/141758470  浏览:    关键词:安阳信息网_代做作业网站_深圳网站设计制作_南宁seo规则
安阳信息网_代做作业网站_深圳网站设计制作_南宁seo规则

1、因为在 JS 的最初版本中,使用的是 32 位系统,为了性能考虑使用低位存储了变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。

typeof null // 'object'

2、最准确判断类型的是 Object.prototype.toString.call(null) === '[object Type]' 

注意type的第一个字母为大写

3、判断两个值是否相等

null可以用 null === null以及Object.is(null, null)

NaN需要用Object.is(NaN, NaN)

undefined,一般情况下使用undefined === undefined判断,但在一些古早的老版本的浏览器中undefined不是保留词,可能会存在undefined 变量被重新赋值。可以使用undefined === void 0,

void 0 总会安全地返回undefined

4、Boolean把 null,undefined,false,NaN,'', +/-0转化为false,其他值包括对象都转化为true

5、instanceof通常用于检查一个对象是否是某个构造函数的实例。它在判断某个对象是否属于特定类型时非常有用,但它主要适用于引用类型(对象)。对于基本类型(如字符串、数字、布尔值等),instanceof 不适用,因为它们不是对象。

instanceof 运算符的内部工作机制如下:

  1. 获取 constructorprototype 属性值,记为 prototypeObj
  2. 获取 object 的原型链(__proto__Object.getPrototypeOf(object))。
  3. 在原型链中从 object 开始逐级向上查找。
    • 如果找到了一个原型等于 prototypeObj,返回 true
    • 如果到达原型链的顶端(即 null),仍然没有找到,返回 false

当我们运行 foo instanceof Foo 时:

  • JavaScript 首先检查 foo.__proto__ 是否等于 Foo.prototype,是的话返回 true
  • 如果不是,继续检查 foo.__proto__.__proto__ 是否等于 Foo.prototype,直到找到为止。
  • 如果到达了原型链的顶端(null)仍然没有找到 Foo.prototype,则返回 false

6、原型链:在 JavaScript 中,每个对象都有一个内部属性([[Prototype]]),它指向其构造函数的原型对象(即 prototype)。这就形成了一条“原型链”。当我们访问对象的一个属性或方法时,如果对象本身没有该属性或方法,JavaScript 会沿着原型链向上查找,直到找到该属性或方法,或者到达原型链的顶端(null)为止。

7、箭头函数的this:

  • this 是静态的,取决于函数定义时的上下文: 箭头函数不会有自己的 this,它会捕获其所定义时所在上下文的 this
  • 不能通过 callapplybind 改变箭头函数的 this 对于箭头函数,这些方法对 this 的绑定没有效果。
  • 没有 arguments 对象: 箭头函数没有自己的 arguments 对象,但你可以使用 rest 参数 (...args) 来获取所有传入的参数。
  • 不能作为构造函数(没有 new 绑定): 你不能使用 new 关键字来调用箭头函数,否则会抛出错误。

8、call、apply、bind

bind与call、apply最大的不同:
 当我们把一个绑定函数用作构造函数时,bind 的行为会发生变化。具体来说,当一个绑定函数被用作构造函数调用时:

  1. 忽略绑定时传入的上下文:绑定函数会忽略你在 bind 时传入的 this 上下文,而是将 this 指向新创建的实例对象。
  2. 保持原函数的原型链:绑定函数创建的实例对象会继承原始函数(未绑定的函数)的原型链,这意味着实例对象可以访问原始函数的所有方法和属性。
// 手写call
Function.prototype.call2 = function(context, ...args) {context = context || windowcontext.fn = thisconst result = context.fn(args)delete context.fnreturn result
}
// 手写apply
Function.prototype.apply2 = function(context, args) {context.fn = thislet result// args为数组或为空if (args) result = context.fn(...args)else result = context.fn()delete context.fnreturn result
}
/** 手写bind
* bind实现有两个比较关键的点;
* 1、参数柯里化(即绑定时传入的参数和调用时传入的参数可以合并到原函数)
* 2、当把返回的函数作为构造函数时,
* 1)会忽略bind时传入的this上下文,而是将this指向新创建的实例对象;
* 2)保持原函数的原型链,既实例对象可以访问原始函数的所有方法和属性
**/
Function.prototype.bind2 = function(context, ...args) {let self = thisconst foundFun = function(...args1) {const isConstructor = this instanceOf foundFun // 判断当前是否作为构造函数被调用const bindArgs = isConstructor ? this : context // 如果是构造函数就指向构造函数调用时的thisself.apply(bindArgs, [...args, ...args1])}// 还需要保持原函数的原型链,让其可以访问原始函数的方法与属性foundFun.prototype = Object.create(self.prototype) // 使用Object.create是因为它会创建了一个新对象,保证foundFun和Object互相独立,新增属性等情况时不会互相干扰return foundFun
}

9、setTimeout可以传入多个参数

function greet(name, message) {
  console.log(name + ": " + message);
}

// 延迟 2 秒后执行函数,并传递参数 "Alice" 和 "Hello!"
setTimeout(greet, 2000, "Alice", "Hello!");

传递给回调函数的参数

  • 从第三个参数开始,可以传递任意数量的参数,这些参数会作为回调函数的参数传入。

10、深浅拷贝

浅拷贝的多种方式。Object.assign()、扩展运算符(...)、slice()。

浅拷贝只能解决第一层的问题,如果需要解决多层拷贝的问题,就需要用到深拷贝

深拷贝通常可以通过 JSON.parse(JSON.stringify(object)) 来解决。

但是该方法也是有局限性的:

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象

11、三种模块化处理的区别

  • commonjs是同步导入,适合服务端,ES Modules是异步导入,适合浏览器端
  • commonjs导出的时候是对值的拷贝,ES Modules导出的是对值的引用
  • commonjs在第一次加载时被执行,并缓存其导出结果。ES Modules在导入时不会立即执行,而是在需要时进行异步加载和执行

 随着 ES Modules (ESM) 成为 JavaScript 的标准模块系统,以及现代打包工具(如 Webpack)的使用,AMD 的使用场景有所减少。

12、如何避免频繁重排和重绘

  • 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)

  • 使用 translate 替代 top,避免使用 topleft 等定位属性,使用 transform 结合 translate 来改变位置,不会引发重排,只会引发合成(compositing)重绘。

  • 使用Document Fragment或隐藏元素修改:在更新多个 DOM 时,使用 DocumentFragment 或者将元素 display: none 之后再进行批量更新,完成后再显示,这样只会触发一次重排和重绘。

  • 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局

  • 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame

13、如何渲染几万条数据并不卡住界面

  • 虚拟列表
  • 懒加载
  • requestAnimationFrame

14、关于执行上下文

执行上下文定义了代码的执行环境,包括变量、函数和对象的可访问性。执行上下文可以帮助我们理解作用域、变量提升以及 this 的指向等概念。

执行上下文的类型可以分为:

  1. 全局上下文;在全局上下文中定义的变量和函数是全局可访问的
  2. 函数上下文;函数上下文中包含该函数的参数、局部变量和对外部变量的访问权限。
  3. Eval 上下文;使用 eval 语句时会创建一个特殊的执行上下文。谨慎使用eval,会带来性能和安全隐患
  • 性能问题。eval 会使 JavaScript 引擎无法进行优化,因为它需要在运行时解析和执行代码。这可能导致性能下降,尤其是在大规模使用时。
  • 安全隐患。使用 eval 可能导致安全问题,特别是在处理不可信的输入时。如果用户输入的字符串被直接传递给 eval,可能会导致代码注入攻击。

执行上下文由三个部分组成:

  • 变量环境;存储该上下文中定义的变量和函数声明;
  • 作用域链;指的是代码中可以访问变量和函数的区域。JavaScript 中主要有两种作用域:全局作用域和局部作用域。JavaScript 采用的是静态作用域,函数的作用域在函数定义的时候就决定了。
  • this 关键字的指向;

15、闭包

闭包:就是可以访问外部作用域变量的内部函数

注意事项

虽然外部函数已经执行完成,但是变量仍然存在于内存中,因为闭包保持着对它们的引用,被引用的变量直到闭包被销毁时才会被销毁。可能导致对象无法被垃圾回收机制回收,从而导致内存泄漏(内存泄漏是指程序在运行时未能正确释放不再需要的内存空间,导致这部分内存无法被重新利用。这通常会导致应用程序的内存使用不断增加,最终可能导致性能下降或崩溃。)

使用场景

  1. 数据封装与私有变量:使用闭包可以创建具有私有变量的对象,控制对数据的访问。

  2. 防止全局命名冲突:闭包可以用来封装代码,防止全局作用域被污染。

  3. 函数记忆:闭包可以用于生成带有特定环境的函数(函数工厂),这些函数可以记住其创建时的上下文。

比较常见的节流和防抖的函数就是利用了函数记忆这个特点

防抖函数

// 防抖一般用于防止按钮被多次点击,从而频繁触发点击时间。防抖的处理思想是,一段时间内,无论操作多少次,都只执行最后一次function debounce(fn, wait) {let timer;return function (...args) {if (timer) clearTimeout(timer)const context = thistimer = setTimeout(() => {fn.apply(context, args)}, wait)}
}

节流函数

// 节流的原理是一段时间内频繁触发某件事,只会执行一次,用于减少浏览器开销。常见的场景有 滚动事件:滚动页面时频繁触发 scroll 事件,节流可以确保在每隔一定时间内执行一次,避免性能问题// 最简单的实现方式是时间戳function throttle(fn, delay) {let lastTime = 0return function (...args) => {const now = Data.now()const context = thisif (now - lastTime >= delay) {lastTime = nowfn.apply(context, args)}}
}

看下以下的执行顺序

for (let i = 0; i < 5; i++) {setTimeout(function() {console.log(new Date, i);}, 1000);
}
for (var i = 0; i < 5; i++) {setTimeout(function() {console.log(new Date, i);}, 1000);
}

第一个输出0,1,2,3,4;第二个输出5,5,5,5,5

原因:

let是块级作用域,这意味着每次迭代时都会创建一个新的 i 变量实例,所以每一次循环的i其实都是一个新的变量。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。

var 声明的变量,作用域是整个包含它的函数。如果它在全局上下文中声明,则会成为全局变量;如果在某个函数内部,它的作用域就是该函数。意味着在整个循环中只有一个 i 变量实例。每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值

16、垃圾回收机制

浏览器的垃圾回收机制主要用于管理内存,确保在执行Javascript时不会再出现内存泄漏或资源浪费。垃圾回收机制的核心是通过识别那些对象不再被使用,将它从内存中清除。JS使用的是自动垃圾回收。主要的方式是标记清除。

标记清除通过“标记”和“清除”两个阶段来识别并清除不再需要的对象,核心思想是 从根对象开始,标记所有仍然可访问的对象,未被标记的对象则视为“垃圾”并进行清除。

缺点:
  • “暂停-世界”现象(Stop-the-world):标记-清除算法在执行时,必须暂停程序的执行,这意味着程序会在垃圾回收期间停止响应(虽然现代垃圾回收器通过分代回收、并发和增量标记来优化这种情况)。
  • 标记和清除的性能问题:每次垃圾回收时,都会遍历整个内存空间的对象,时间复杂度与堆中的对象数量成正比,随着对象数量的增加,性能可能下降。

现代垃圾回收器通过各种优化技术,如分代回收、增量回收和并发回收,来减少标记-清除算法带来的性能问题。

  1. 分代回收:将对象按生命周期分为“新生代”和“老生代”。新生代对象往往生命周期短,因此垃圾回收会频繁检查新生代中的对象。而老生代对象生命周期长,垃圾回收检查的频率较低。
  2. 增量回收:将一次完整的垃圾回收拆分成多个小步骤,每次只进行一部分回收工作,以减少“暂停-世界”的时间。
  3. 并发回收:在现代浏览器中,垃圾回收器可以利用多核 CPU,并发处理标记和清除的工作,进一步减少对主线程的影响。

例子:

假设我们有以下代码:

function outer() {let obj1 = { name: "object 1" };let obj2 = { name: "object 2" };function inner() {console.log(obj1);}return inner;
}const closure = outer();

在这个例子中:

  • obj1outer 函数的局部变量,它被 inner 函数引用。因此,尽管 outer 执行结束后,obj1 没有从根对象直接引用,但由于它被闭包引用,所以 obj1 仍然是可达的,不会被回收。
  • obj2 没有被 inner 函数引用,也没有其他地方引用它,因此 obj2outer 函数执行结束后,将在下一次垃圾回收时被回收。

标记-清除算法的回收流程将标记 obj1 为可达对象,而 obj2 则会被清除。

1)什么时候发生垃圾回收?

浏览器通常会在以下几个场景执行垃圾回收:

  • 内存占用达到某个阈值时。
  • 程序进入空闲阶段(例如 JavaScript 代码执行完毕后,等待用户输入或其他事件时)。
  • 显示的浏览器空闲时段(Idle Periods)。

2)内存泄漏的常见原因

尽管浏览器有垃圾回收机制,但仍可能发生内存泄漏,主要原因包括:

  1. 全局变量未释放:全局变量会一直存在于全局执行上下文,难以回收。
  2. 被遗忘的定时器或事件监听器:未清除的 setIntervalsetTimeout 或未移除的事件监听器,仍然引用着对象,导致对象无法被回收。
  3. 闭包:闭包会保持对外部变量的引用,如果这些变量不再需要,但依旧被引用,内存就无法释放。

17、this

函数的this完全取决于调用时的上下文决定。

箭头函数则比较特殊,是根据定义时的上下文。这意味着箭头函数不会创建自己的 this,它会捕获并继承外部作用域的 this 值。

下面是 Babel 转箭头函数产生的 ES5 代码,就能清楚地说明this的指向。箭头函数里面根本没有自己的this,而是引用外层的this

// ES6
function foo() {setTimeout(() => {console.log('id:', this.id);}, 100);
}// ES5
function foo() {var _this = this;setTimeout(function () {console.log('id:', _this.id);}, 100);
}

看下以下代码的输出:

var name = 'window';
var student = {name: '若川',doSth: function(){// var self = this;var arrowDoSth = () => {// console.log(self.name);console.log(this.name);}arrowDoSth();},arrowDoSth2: () => {console.log(this.name);}
}
student.doSth(); // '若川'
student.arrowDoSth2(); // 'window'

其实就是相当于箭头函数外的this是缓存的该箭头函数上层的普通函数this。如果没有普通函数,则是全局对象(浏览器中则是window)。 

使用箭头函数有几个需要注意的点:

  • 不能用作构造函数,无 new 关键字
  • 不具备 arguments:箭头函数没有自己的 arguments 对象,如果要用,可以用 rest 参数代替(即在箭头函数定义时(...arg))
  • 无法通过callapplybind绑定箭头函数的this(它自身没有this)
function Person(name) {this.name = name;
}const person1 = new Person('Alice');
const person2 = Person('Bob');console.log(person1.name);
console.log(person2);
console.log(name);

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com