【面试题】2024前端面试真题之JS篇(1)

  1. NodeList 等 DOM 集合类型
接收可迭代对象的原生语言特性包括
  1. for-of 循环
  2. 数组解构
  3. 扩展操作符
  4. Array.from()
  5. 创建Set
  6. 创建Map
  7. Promise.all()接收由Promise组成的可迭代对象
  8. Promise.race()接收由Promise组成的可迭代对象
  9. yield*操作符,在生成器中使用
迭代器协议

迭代器是一种一次性使用的对象,用于迭代与其关联的可迭代对象

迭代器 API 使用 next()方法在可迭代对象中遍历数据,每次成功调用 next(),都会返回一个 IteratorResult 对象,其中包含迭代器返回的下一个值。

next()方法返回的迭代器对象 IteratorResult 包含两个属性

  1. done
    • 一个布尔值,表示是否还可以再次调用 next()取得下一个值
  2. value
    • 包含可迭代对象的下一个值

每个迭代器都表示对可迭代对象的一次性有序遍历

手写一个迭代器
function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length
        ? { value: array[nextIndex++], done: false }
        : { value: undefined, done: true };
    },
  };
}

代码测试

var it = makeIterator(["a", "b"]);

it.next(); // { value: "a", done: false }
it.next(); // { value: "b", done: false }
it.next(); // { value: undefined, done: true }


设计模式的分类

总体来说设计模式分为三大类:(C5S7B11)

  1. 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
  2. 结构型模式,共七种:适配器模式装饰器模式代理模式、外观模式、桥接模式、组合模式、享元模式。
  3. 行为型模式,共十一种:策略模式、模板方法模式、观察者模式/发布订阅模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

手写单例模式(创建模式)

let CreateSingleton = (function(){
    let instance;
    return function(name) {
        if (instance) {
            return instance;
        }
        this.name = name;
        return instance = this;
    }
})();
CreateSingleton.prototype.getName = function() {
    console.log(this.name);
}


代码测试

let Winner = new CreateSingleton('Winner');
let Looser = new CreateSingleton('Looser');

console.log(Winner === Looser); // true
console.log(Winner.getName());  // 'Winner'
console.log(Looser.getName());  // 'Winner'


手写观察者模式(行为模式)

// 定义observe
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);


const observable = obj => new Proxy(obj, {
  set(target, key, value, receiver) {
    const result = Reflect.set(target, key, value, receiver);
    // notify
    queuedObservers.forEach(observer => observer());
    return result;
  }
});


代码测试


obj = observable({
  name:'789'
})

observe(function test(){
  console.log('触发了')
})

obj.name ="前端柒八九"
// 触发了
// 前端柒八九

手写发布订阅 (行为模式)

class Observer {
  caches = {}; // 事件中心
  
  // eventName事件名-独一无二, fn订阅后执行的自定义行为
  on (eventName, fn){ 
    this.caches[eventName] = this.caches[eventName] || [];
    this.caches[eventName].push(fn);
  }
  
  // 发布 => 将订阅的事件进行统一执行
  emit (eventName, data) { 
    if (this.caches[eventName]) {
      this.caches[eventName]
      .forEach(fn => fn(data));
    }
  }
  // 取消订阅 => 若fn不传, 直接取消该事件所有订阅信息
  off (eventName, fn) { 
    if (this.caches[eventName]) {
      const newCaches = fn 
        ? this.caches[eventName].filter(e => e !== fn) 
        : [];
      this.caches[eventName] = newCaches;
    }
  }

}

代码测试

ob = new Observer();

l1 = (data) => console.log(`l1_${data}`)
l2 = (data) => console.log(`l2_${data}`)

ob.on('event1',l1)
ob.on('event1',l2)

//发布订阅
ob.emit('event1',789) 
// l1_789
// l2_789

// 取消,订阅l1
ob.off('event1',l1)

ob.emit('event1',567)
//l2_567

观察者模式 VS 发布订阅模式

  1. 从表面上看:
    • 观察者模式里,只有两个角色 —— 观察者 + 被观察者
    • 而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的 —— {经纪人|Broker}
  2. 往更深层次讲:
    • 观察者和被观察者,是松耦合的关系
    • 发布者和订阅者,则完全不存在耦合
  3. 从使用层面上讲:
    • 观察者模式,多用于单个应用内部
    • 发布订阅模式,则更多的是一种{跨应用的模式|cross-application pattern} ,比如我们常用的消息中间件

WebGL和canvas的关系

  • Canvas就是画布,只要浏览器支持,可以在canvas上获取2D上下文3D上下文,其中3D上下文一般就是WebGL,当然WebGL也能用于2D绘制,并且WebGL提供硬件渲染加速,性能更好。
  • 但是 WEBGL 的支持性caniuse还不是特别好,所以在不支持 WebGL 的情况下,只能使用 Canvas 2D api,注意这里的降级不是降到 Canvas,它只是一个画布元素,而是降级使用 浏览器提供的 Canvas 2D Api,这就是很多库的兜底策略,如 Three.jsPIXI 等

CommonJS和ES6 Module的区别

  1. CommonJS 是同步加载模块,ES6是异步加载模块
    • CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。
    • 浏览器加载 ES6 模块是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本
  2. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
    • CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值
    • ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值
  3. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

是否可以在浏览器端使用 CommonJS

CommonJS不适用于浏览器环境


声明变量的方式(2 + 4 )

  • ES5
    1. var命令
    2. function命令
  • ES6
    1. let
    2. const
    3. import
    4. class

函数的声明

  1. function 命令
    • function fn(s) {}
  2. 函数表达式
    • var fn = function(s) {}
  3. Function 构造函数
    • new Function('x','y','return x + y' )

Object/Map/WeakMap的区别

ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

也就是说,

  • Object 结构提供了字符串—值的对应,
  • Map 结构提供了值—值的对应,是一种更完善的 Hash 结构实现。

WeakMap结构与Map结构类似,也是用于生成键值对的集合。

WeakMapMap的区别有两点。

  • 首先,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
  • 其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。

总之,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏

WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用


JS 深浅复制

JS在语言层面仅支持浅复制,深复制需要手动实现

浅复制(3个)

  1. 扩展运算符
  2. Object.assign()
  3. Object.getOwnPropertyDescriptors()+Object.defineProperties()
扩展运算符(…)复制对象和数组
const copyOfObject = {...originalObject};
const copyOfArray = [...originalArray];

扩展运算符不足和特性。

不足&特性
不能复制普通对象的prototype属性
不能复制内置对象特殊属性(internal slots)
只复制对象的本身的属性(非继承)
只复制对象的可枚举属性(enumerable)
复制的数据属性都是可写的(writable)和可配置的(configurable)
Object.assign()

Object.assign()的工作方式和扩展运算符类似。

const copy1 = {...original};
const copy2 = Object.assign({}, original);

Object.assign()并非完全和扩展运算符等同,他们之间存在一些细微的差别。

  • 扩展运算符在副本中直接定义新的属性
  • Object.assign()通过赋值的方式来处理副本中对应属性
Object.getOwnPropertyDescriptors()Object.defineProperties()

JavaScript允许我们通过属性描述符来创建属性。

function copyAllOwnProperties(original) {
  return Object.defineProperties(
    {}, Object.getOwnPropertyDescriptors(original));
}

  1. 能够复制所有自有属性
  2. 能够复制非枚举属性

深复制

通过嵌套扩展运算符实现深复制
const original = {name: '789', work: {address: 'BeiJing'}};
const copy = {name: original.name, work: {...original.work}};

original.work !== copy.work // 指向不同的引用地址

使用JSON实现数据的深复制

先将普通对象,

  1. 先转换为JSON串(stringify)
  2. 然后再解析(parse)该串
function jsonDeepCopy(original) {
  return JSON.parse(JSON.stringify(original));
}


而通过这种方式有一个很明显的缺点就是:

只能处理JSON所能识别的keyvalue。对于不支持的类型,会被直接忽略掉。

手动实现

递归函数实现深复制

实现逻辑就是(FHT

  1. 利用 for-in对对象的属性进行遍历(自身属性+继承属性)
  2. source.hasOwnProperty(i)判断是否是非继承可枚举属性
  3. typeof source[i] === 'object'判断值的类型,如果是对象,递归处理
function clone(source) {
    let target = {};
    for(let i in source) {
        if (source.hasOwnProperty(i)) {
            if (typeof source[i] === 'object') {
                target[i] = clone(source[i]); // 递归处理
            } else {
                target[i] = source[i];
            }
        }
    }

    return target;
}



闭包

函数即对象

在JS中,一切皆对象。那从语言的设计层面来讲,函数是一种特殊的对象

函数和对象一样可以拥有属性和值

function foo(){
    var test = 1
    return test;
}
foo.myName = 1
foo.obj = { x: 1 }
foo.fun = function(){
  return 0;
}

根据对象的数据特性:foo 函数拥有myName/obj/fun 的属性

但是函数和普通对象不同的是,函数可以被调用

V8内部来看看函数是如何实现可调用特性

在 V8 内部,会为函数对象添加了两个隐藏属性

  • name 属性:属性的值就是函数名称
  • code 属性:表示函数代码,以字符串的形式存储在内存

code 属性

当执行到,一个函数调用语句时,V8 便会从函数对象中取出 code 属性值(也就是函数代码),然后再解释执行这段函数代码。

在解释执行函数代码的时候,又会生成该函数对应的执行上下文,并被推入到调用栈里


闭包

在 JS 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量。
当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了。但是内部函数引用外部函数的变量依然保存在内存中,就把这些变量的集合称为闭包。

function test() {
    var myName = "fn_outer"
    let age = 78;
    var innerObj = {
        getName:function(){
            console.log(age);
            return myName
        },
        setName:function(newName){
            myName = newName
        }
    }
    return innerObj
}
var t = test();
console.log(t.getName());//fn_outer 
t.setName("global")
console.log(t.getName())//global

  • 根据词法作用域的规则,内部函数 getName 和 setName 总是可以访问它们的外部函数 test 中的变量
    • 在执行test时,调用栈的情况 
  • test 函数执行完成之后,其执行上下文从栈顶弹出
    • 但是由于返回innerObj对象中的 setName 和 getName 方法中使用了 test 函数内部的变量 myName 和 age 所以这两个变量依然保存在内存中(Closure (test)
  • 当执行到t.setName方法的时,调用栈如下:
  • 利用debugger来查看对应的作用链和调用栈信息

通过上面分析,然后参考作用域的概念和使用方式,我们可以做一个简单的结论

闭包和词法环境的强相关

而JS的作用域由词法环境决定,并且作用域是静态的。

所以,我们可以得出一个结论:

闭包在每次创建函数时创建(闭包在JS编译阶段被创建)


闭包是如何产生的?

产生闭包的核心两步:

  1. 预扫描内部函数
  2. 内部函数引用的外部变量保存到堆中
function test() {
    var myName = "fn_outer"
    let age = 78;
    var innerObj = {
        getName:function(){
            console.log(age);
            return myName
        },
        setName:function(newName){
            myName = newName
        }
    }
    return innerObj
}
var t = test();

当 V8 执行到 test 函数时

  • 首先会编译,并创建一个空执行上下文。

    • 编译过程中,遇到内部函数 setName, V8还要对内部函数做一次快速的词法扫描(预扫描) 发现该内部函数引用了 外部函数(test)中的 myName 变量
    • 由于是内部函数引用了外部函数的变量,所以 V8 判断这是一个闭包
    • 于是在堆空间创建换一个closure(test)的对象 (这是一个内部对象,JavaScript 是无法访问的),用来保存 myName 变量
  • 当 test 函数执行结束之后,返回的 getName 和 setName 方法都引用clourse(test)对象。

    • 即使 test 函数退出了,clourse(test)依然被其内部的 getName 和 setName 方法引用。
  • 所以在下次调用t.setName或者t.getName时,在进行变量查找时候,根据作用域链来查找。


Event Loop

{事件循环|Event Loop}

事件循环是一个不停的从 宏任务队列/微任务队列中取出对应任务的循环函数。
在一定条件下,你可以将其类比成一个永不停歇的永动机。 它从宏/微任务队列取出任务并将其推送调用栈中被执行。

事件循环包含了四个重要的步骤:

  1. 执行Script:以同步的方式执行script里面的代码,直到调用栈为空才停下来。
    • 其实,在该阶段,JS还会进行一些预编译等操作。(例如,变量提升等)。
  2. 执行一个宏任务:从宏任务队列中挑选最老的任务并将其推入到调用栈中运行,直到调用栈为空
  3. 执行所有微任务:从微任务队列中挑选最老的任务并将其推入到调用栈中运行,直到调用栈为空。
    • 但是,但是,但是(转折来了),继续从微任务队列中挑选最老的任务并执行。直到微任务队列为空
  4. UI渲染:渲染UI,然后,跳到第二步,继续从宏任务队列中挑选任务执行。(这步只适用浏览器环境,不适用Node环境)

事件循环的单次迭代过程被称为tick

{宏任务队列|Task Queue}

也可以称为{回调队列| Callback queue}。

调用栈是用于跟踪正在被执行函数的机制,而宏任务队列是用于跟踪将要被执行函数的机制。

事件循环不知疲倦的运行着,并且按照一定的规则从宏任务队列中不停的取出任务对象。

宏任务队列是一个FIFO(先进先出)的队列结构。结构中存储的宏任务会被事件循环探查到。并且,这些任务是同步阻塞的。当一个任务被执行,其他任务是被挂起的(按顺序排队)。

{微任务队列|Microtask Queue}

微任务队列也是一个FIFO(先进先出)的队列结构。并且,结构中存储的微任务也会被事件循环探查到。微任务队列和宏任务队列很像。作为ES6的一部分,它被添加到JS的执行模型中,以处理Promise回调

微任务和宏任务也很像。它也是一个同步阻塞代码,运行时也会霸占调用栈。像宏任务一样,在运行期间,也会触发新的微任务,并且将新任务提交到微任务队列中,按照队列排队顺序,将任务进行合理安置。

  • 宏任务是在循环中被执行,并且UI渲染穿插在宏任务中。
  • 微任务是在一个宏任务完成之后,在UI渲染之前被触发。

微任务队列是ES6新增的专门用于处理Promise调用的数据结构。它和宏任务队列很像,它们最大的不同就是微任务队列是专门处理微任务的相关处理逻辑的。


{垃圾回收机制|Garbage Collecation}

垃圾回收算法

  1. 通过 GC Root 标记空间中活动对象非活动对象
    • V8 采用的{可访问性| reachability}算法,来判断堆中的对象是否是活动对象
    • 这个算法是将一些 GC Root 作为初始存活的对象的集合
    • 从 GC Roots 对象出发,遍历 GC Root 中的所有对象
    • 通过 GC Roots 遍历到的对象,认为该对象是{可访问的| reachable},也称可访问的对象为活动对象
    • 通过 GC Roots 没有遍历到的对象,是{不可访问的| unreachable},不可访问的对象为非活动对象
    • 浏览器环境中,GC Root 包括1.全局的 window 对象,2.文档 DOM 树,由可以通过遍历文档到达的所有原生 DOM 节点组成,3.存放栈上变量
  2. 回收非活动对象所占据的内存
  3. 内存整理
    • 频繁回收对象后,内存中就会存在大量不连续空间
    • 这些不连续的内存空间称为内存碎片

代际假说

代际假说是垃圾回收领域中一个重要的术语

两个特点

  1. 第一个是大部分对象都是朝生夕死的
  • 大部分对象在内存中存活的时间很短
  • 比如函数内部声明的变量,或者块级作用域中的变量
  1. 第二个是不死的对象,会活得更久
  • 比如全局的 windowDOMWeb API 等对象

堆空间

在 V8 中,会把分为

  1. 新生代
    • 存放的是生存时间短的对象
    • 新生代通常只支持 1~8M 的容量
    • {副垃圾回收器| Minor GC} (Scavenger)
    • 负责新生代的垃圾回收
  2. 老生代
    • 存放生存时间久的对象
    • {主垃圾回收器| Major GC}
    • 负责老生代的垃圾回收


{副垃圾回收器| Minor GC}

新生代中的垃圾数据用 Scavenge 算法来处理。

所谓 Scavenge 算法,把新生代空间对半划分为两个区域:

  • 一半是对象区域 (from-space)
  • 一半是空闲区域 (to-space)

当对象区域快被写满时,就需要执行一次垃圾清理操作,

  1. 首先要对对象区域中的垃圾做标记,

  2. 标记完成之后,就进入垃圾清理阶段,

    • 把这些存活的对象复制到空闲区域中,把这些对象有序地排列起来
  3. 完成复制后,对象区域与空闲区域进行角色翻转

副垃圾回收器采用对象晋升策略移动那些经过两次垃圾回收依然还存活的对象到老生代中


{主垃圾回收器| Major GC}

负责老生代中的垃圾回收,除了新生代中晋升的对象,大的对象会直接被分配到老生代里。

老生代中的对象有两个特点

  1. 对象占用空间大
  2. 对象存活时间长
{标记 - 清除|Mark-Sweep}算法
  1. 标记过程阶段
    • 从一组根元素开始,递归遍历这组根元素
    • 这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据
  2. 垃圾的清除过程
    • 主垃圾回收器会直接将标记为垃圾的数据清理掉
{标记 - 整理|Mark-Compact}
  1. 标记可回收对象
  2. 垃圾清除
    • 不是直接对可回收对象进行清理
    • 而是让所有存活的对象都向一端移动
    • 直接清理掉这一端之外的内存

内存问题

内存泄漏 (Memory leak)

不再需要 (没有作用) 的内存数据依然被其他对象引用着。

污染全局(window)
function foo() {
    //创建一个临时的temp_array
    temp_array = new Array(200000)
   /**
    * 使用temp_array
    */
}

函数体内的对象没有被 varletconst 这些关键字声明。

V8 就会使用 this.temp_array 替换 temp_array

在浏览器,默认情况下,this 是指向 window 对象的

闭包
function foo(){  
    var temp_object = new Object()
    temp_object.x = 1
    temp_object.y = 2
    temp_object.array = new Array(200000)
    /**
    *   使用temp_object
    */
    return function(){
        console.log(temp_object.x);
    }
}

闭包会引用父级函数中定义的变量。

如果引用了不被需要的变量,那么也会造成内存泄漏。

detached 节点
let detachedTree;
function create() {
  var ul = document.createElement('ul');
  for (var i = 0; i < 100; i++) {
    var li = document.createElement('li');
    ul.appendChild(li);
  }
  detachedTree = ul;
 }
 
create() 

只有同时满足 DOM 树和 JavaScript 代码都不引用某个 DOM 节点,该节点才会被作为垃圾进行回收。

“detached ”节点:如果某个节点已从 DOM 树移除,但 JavaScript 仍然引用它


作用域的产生

作用域被分为3大类

  1. 声明式作用域
  • 函数作用域
  • module作用域
  1. 对象作用域
  2. 全局作用域

声明式作用域

声明式ER可以通过 var/const/let/class/module/import/function生成。

常说的ES6块级作用域和函数作用域属于同一大类(声明式作用域)。

根据实现层级,还有一个更准确的结论:

ES6块级作用域是函数作用域的子集

全局作用域

全局作用域是最外面的作用域,它没有外部作用域。即全局环境的OuterEnvnull

全局ER使用两个ER来管理其变量:

  1. 对象ER
    • 将变量存储在全局对象
    • 顶层作用域下,var 和 function 声明的变量被绑定在对象ER里(在浏览器环境下, window 指向全局对象)
  2. 声明式ER
    • 使用内部对象来存储变量
    • 顶层作用域下,const/let/class声明的变量被绑定在声明ER

当声明式ER和对象ER有共同的变量,声明式优先级高


this指向

{执行上下文 |Execution context} 中包含了

  1. {变量环境 |Viriable Environment}
  2. {词法环境 |Lexical Environment}
  3. {外部环境 |outer}
  4. this

this 是和执行上下文绑定的,也就是说每个执行上下文中都有一个 this

执行上下文主要分为三种

  1. 全局执行上下文
  2. 函数执行上下文
  3. eval 执行上下文
全局执行上下文

全局执行上下文中的 this 是指向 window 对象的

这也是 this 和作用域链的唯一交点

  • 作用域链的最底端包含了 window 对象
  • 全局执行上下文中的 this 也是指向 window 对象
函数执行上下文

默认情况下调用一个函数,其执行上下文中的 this 也是指向 window 对象的

设置函数执行上下文中的 this 值
通过函数的 call/bind/apply 方法设置
let bar = {
  myName : " 北宸 ",
  test1 : 1
}
function foo(){
  this.myName = " 南蓁 "
}
foo.call(bar)
console.log(bar) // 南蓁
console.log(myName) // myName is not defined

通过对象调用方法设置
var myObj = {
  name : " 北宸", 
  showThis: function(){
    console.log(this)
  }
}
myObj.showThis()

使用对象来调用其内部的一个方法,该方法的 this 是指向对象本身

可以认为 JavaScript 引擎在执行myObject.showThis()时,将其转化为了:myObj.showThis.call(myObj)

把 showThis 赋给一个全局对象,然后再调用该对象

var myObj = {
  name : " 北宸 ",
  showThis: function(){
    this.name = " 南蓁 "
    console.log(this)
  }
}
var foo = myObj.showThis
foo()

this 又指向了全局 window 对象

  • 在全局环境中调用一个函数,函数内部的 this 指向的是全局变量 window
  • 通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向对象本身
通过构造函数中设置
function CreateObj(){
  this.name = " 北宸南蓁 "
}
var myObj = new CreateObj()

此时,this指向实例对象


this 的设计缺陷以及应对方案

嵌套函数中的 内部函数this 不会从外层函数中继承
var myObj = {
  name : " 北宸南蓁 ", 
  showThis: function(){
    console.log(this)
    function inner(){console.log(this)}
    inner()
  }
}
myObj.showThis()

  • 函数 inner 中的 this 指向的是全局 window 对象
  • 函数 showThis 中的 this 指向的是 myObj 对象
解决方案

把 this 体系转换为了作用域的体系

var myObj = {
  name : " 北宸 ", 
  showThis: function(){
    console.log(this)
    var self = this
    function inner(){
      self.name = " 南蓁 "
    }
    inner()
  }
}
myObj.showThis()
console.log(myObj.name)
console.log(window.name)

在 showThis 函数中声明一个变量 self 用来保存 this,然后在 inner 函数中使用 self

使用 ES6 中的箭头函数
var myObj = {
  name : " 北宸 ", 
  showThis: function(){
    console.log(this)
    var inner = ()=>{
      this.name = " 南蓁 "
      console.log(this)
    }
    inner()
  }
}
myObj.showThis()
console.log(myObj.name)
console.log(window.name)

ES6 中的箭头函数并不会创建其自身的执行上下文,所以箭头函数中的 this 取决于它的外部函数

普通函数中的 this 默认指向全局对象 window

通过设置 JavaScript 的“严格模式”来解决

在严格模式下,默认执行一个函数,其函数的执行上下文中的 this 值是 undefined

TCP协议

  • TCP 和 UDP 的区别?
  • TCP 三次握手的过程?
  • 为什么是三次而不是两次、四次?
  • 三次握手过程中可以携带数据么?
  • 说说 TCP 四次挥手的过程
  • 为什么是四次挥手而不是三次?
  • 半连接队列和 SYN Flood 攻击的关系
  • 如何应对 SYN Flood 攻击?
  • 介绍一下 TCP 报文头部的字段
  • TCP 快速打开的原理(TFO)
  • 说说TCP报文中时间戳的作用?
  • TCP 的超时重传时间是如何计算的?
  • TCP 的流量控制
  • TCP 的拥塞控制
  • 说说 Nagle 算法和延迟确认?
  • 如何理解 TCP 的 keep-alive?

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

浏览器篇
  • 浏览器缓存?
  • 说一说浏览器的本地存储?各自优劣如何?
  • 说一说从输入URL到页面呈现发生了什么?
  • 谈谈你对重绘和回流的理解
  • XSS攻击
  • CSRF攻击
  • HTTPS为什么让数据传输更安全?
  • 实现事件的防抖和节流?
  • 实现图片懒加载?
var myObj = {
  name : " 北宸 ", 
  showThis: function(){
    console.log(this)
    var self = this
    function inner(){
      self.name = " 南蓁 "
    }
    inner()
  }
}
myObj.showThis()
console.log(myObj.name)
console.log(window.name)

在 showThis 函数中声明一个变量 self 用来保存 this,然后在 inner 函数中使用 self

使用 ES6 中的箭头函数
var myObj = {
  name : " 北宸 ", 
  showThis: function(){
    console.log(this)
    var inner = ()=>{
      this.name = " 南蓁 "
      console.log(this)
    }
    inner()
  }
}
myObj.showThis()
console.log(myObj.name)
console.log(window.name)

ES6 中的箭头函数并不会创建其自身的执行上下文,所以箭头函数中的 this 取决于它的外部函数

普通函数中的 this 默认指向全局对象 window

通过设置 JavaScript 的“严格模式”来解决

在严格模式下,默认执行一个函数,其函数的执行上下文中的 this 值是 undefined

TCP协议

  • TCP 和 UDP 的区别?
  • TCP 三次握手的过程?
  • 为什么是三次而不是两次、四次?
  • 三次握手过程中可以携带数据么?
  • 说说 TCP 四次挥手的过程
  • 为什么是四次挥手而不是三次?
  • 半连接队列和 SYN Flood 攻击的关系
  • 如何应对 SYN Flood 攻击?
  • 介绍一下 TCP 报文头部的字段
  • TCP 快速打开的原理(TFO)
  • 说说TCP报文中时间戳的作用?
  • TCP 的超时重传时间是如何计算的?
  • TCP 的流量控制
  • TCP 的拥塞控制
  • 说说 Nagle 算法和延迟确认?
  • 如何理解 TCP 的 keep-alive?

[外链图片转存中…(img-gELip3vG-1714173111531)]

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

浏览器篇
  • 浏览器缓存?
  • 说一说浏览器的本地存储?各自优劣如何?
  • 说一说从输入URL到页面呈现发生了什么?
  • 谈谈你对重绘和回流的理解
  • XSS攻击
  • CSRF攻击
  • HTTPS为什么让数据传输更安全?
  • 实现事件的防抖和节流?
  • 实现图片懒加载?

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值