• 【知识总结】金九银十offer拿到手软的前端面试题——Javascript篇(二)


    作用域和作用域链

    • 简单来说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。作用域是一套规则,在当前作用域及子作用域中根据标识符名称进行变量查找。
    • 作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的。

    new操作符具体都干了什么

    • 创建一个空对象
    • 继承了该函数的原型
    • 属性和方法被加入到了this引用的对象中
    • 新创建的对象由this所引用,并且最后隐式的返回this

    严格模式的限制

    • 变量必须声明后再使用
    • 函数的参数不能有同名属性
    • 不能使用with语句
    • 不能对只读属性赋值
    • 不能使用前缀0表示八进制数
    • 不能删除不可删除的属性
    • 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
    • eval不会在它的外层作用域引入变量
    • eval和arguments不能被重新赋值
    • arguments不会自动反映函数参数的变化
    • 不能使用arguments.callee
    • 不能使用arguments.caller
    • 禁止this指向全局对象
    • 不能使用fn.caller和fn.arguments获取函数调用的堆栈
    • 增加了保留字(比如protected、static和interface)

    es6新增特性

    • let、const
    • 字符串、数组、对象的方法扩展
    • symbol、set、map新的数据类型和数据结构
    • proxy代理拦截
    • 异步解决方案:promise、generate、async、await
    • class类
    • module模块

    箭头函数和普通函数的区别

    • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
    • 不可以当作构造函数,也就是说不可以使用new命令,否则会报错
    • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替
    • 不可以使用yield命令,因此箭头函数不能用作Generator函数

    异步编程的实现方式

    1)回调函数

    优点:简单、容易理解

    缺点:不利于维护,代码耦合度高

    2)事件监听(采用事件驱动模式,取决于某个事件是否发生)

    优点:容易理解,可以绑定多个事件,每个事件可以指定多个回调函数

    缺点:事件驱动型,流程不够清晰

    3)发布、订阅(观察者模式)

    优点:可以利用then方法,进行链式写法

    缺点:编写和理解,相对比较难

    4)Generator函数

    优点:函数体内外的数据交换、错误处理机制

    缺点:流程管理不方便

    5)async函数

    优点:内置执行器、更好的语义、更广的适用性、返回的promise、结构清晰

    缺点:错误处理机制

    事件循环 & 宏任务、微任务

    宏任务:macrotask也叫task

    • script
    • setTimeout
    • setInterval
    • setImediate
    • I/O
    • UI rendering
    • 回调函数

    微任务:microtask 也称为 jobs

    • process.nextTick(Node)
    • promise
    • Object.observe
    • Mutationbserve(浏览器)

    事件循环:event loop

    流程:在一次事件循环中,会先执行js线程的主任务,然后会去查找是否有微任务,如果有接执行,没有就去查找宏任务进行执行。

    一个事件循环的执行步骤:

    1. 执行同步代码,这属于宏任务
    2. 执行所有微任务
    3. 必要的话渲染UI
    4. 然后开始下一轮event loop,执行宏任务中的异步代码

    在一个事件循环中,微任务会比宏任务先执行,因此即使new promise()立即执行的代码比setTimeout(()=>{},0)的后面,也会提前执行。

    setTimeout(() => console.log(1), 0);
    
    new Promise((resolve) => resolve(''), () => {})
        .then(() => {
            console.log(2);
            setTimeout(() => console.log(4), 0);
            new Promise((resolve) => resolve(''), () => {}).then(() => console.log(3))
        });
    
    // 结果:2 3 1 4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Javascript垃圾回收

    1)标记删除(mark and sweep)

    • 最常见方式,当变量进入执行环境时,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开时标记为“离开环境”
    • 垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及环境中变量所引用的变量,在这些完成之后仍存在标记的就是要删除的变量了

    2)引用计数(reference counting)

    • 在低版本IE中经常出现内存泄漏,很多时候就是因为其采用引用计数方式进行垃圾回收
    • 其策略是跟踪记录每个值被使用的次数
    • 当声明了一个变量并将一个引用类型赋值给该变量的时候这个值的引用次数就+1
    • 如果该变量的值变成另一个值,则引用次数-1
    • 当这个值的引用次数变0的时候,说明没有变量在使用,这个值就没法访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理引用次数为0的值占用空间

    深浅拷贝

    深拷贝:JSON.parse(JSON.stringify(object))

    缺点:

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

    浅拷贝:只拷贝第一层

    • Object.assign
    • 扩展运算符(…)

    进程和线程

    本质上来说,进程和线程都是CPU工作时间片上的描述

    • 进程描述了CPU在运行指令及加载和保存上下文所需时间,放在应用上来说代表一个程序
    • 线程是进程中的更小单位,描述了执行一段指令所需的时间

    举例:拿浏览器来说,当打开一个tab页时,其实就创造了一个进程,在进程中可有多个线程,比如渲染线程、js引擎线程、http请求线程等。当你发起请求时,其实就是创建了一个线程,当请求结束后,线程可能被销毁

    单线程的好处:可以节省内存,节约上下文切换时间,没有锁的问题

    事件委托

    将事件绑定到父节点上,因为事件冒泡的缘故可获取到实际点击的叶子节点,以减少dom事件绑定的次数

    document.body.addEventListener('click', e => console.log(e));
    /*
    {
        currentTarget: null, // 当前绑定事件的节点,那为什么是null呢?看下面解释
        target: h1#title.title // 点击最下层的叶子结点
    }
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    为何currentTargetnull

    • console在log一个对象的机制造成的。log没有包含对象的所有属性,它只包含了这个对象的引用。当你点击时,它才给你找到那个对象的属性。
    • 之所以null,当调用console.log(e),currentTarget是有值的,但过后这个值就被重置为null了。所以当你展开事件对象,看到的就是null。

    typescript

    1)ts 和 js 详细对比
    在这里插入图片描述
    面向对象:

    • 三大特性:多态、封装、继承
    • 五大原则:单一职责原则、开放封闭原则、里氏替换原则、依赖倒置原则、接口分离原则、高内聚低耦合

    2)interface 和 type 的区别

    interface接口

    定义:对值所具有的结构进行类型检查

    作用:为类型命名和第三方代码定义数据类型规范

    type类型别名

    作用:给类型起名字

    type 可以而 interface 不行

    • type可以声明基本类型别名,联合类型,元组等类型。类和interface不行(只能描述对象和函数)
    • type语句中还可以使用typeof获取实例的类型进行赋值

    interface 可以而 type 不行

    • interface能声明合并

    interface 和 type 都可以

    • 两者都可以用来描述对象或函数类型,只是语法不一样
    // 接口
    interface Point {
      x: number;
      y: number;
    }
    interface SetPoint {
      (x: number, y: number): void;
    }
    // 类型别名
    type Point = {
      x: number;
      y: number;
    };
    type SetPoint = (x: number, y: number) => void;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 两者都可以扩展,但是语法不一样

    ⚠️最后如果不清楚什么时候用interface/type,能用 interface 实现,就用 interface , 如果不能就用 type。

    3)元组

    ts数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。

    js数组本身包含了元组特性,支持多类型数组

    let tom: [string, number] = ['Tom', 25];
    
    • 1

    4)枚举

    枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。

    • 枚举成员会被赋值从0开始递增的数字,同时也会对枚举出来值到枚举名进行反向映射
    • 可以给枚举对象手动赋值
    enum Days {Sun, Mon = 1, Tue, Wed, Thu, Fri, Sat};
    console.log(Days["Sun"] === 0); // true
    console.log(Days[0] === "Sun"); // true
    // 上面的代码实际会被编译为:Days[Days["Sun"] = 0] = "Sun";
    
    console.log(Days["Sun"] === 7); // true 手动赋值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    泛型

    泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

    // 可以指定默认类型:T = string
    function createArray<T = string>(length: number, value: T): Array<T> {
        let result: T[] = [];
        for (let i = 0; i < length; i++) {
            result[i] = value;
        }
        return result;
    }
    
    createArray<string>(3, 'x'); // ['x', 'x', 'x']
    createArray(3, 'x'); // 也可以自动推断类型
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    多个类型参数定义泛型的时候,可以借助元组一次定义多个类型参数:

    function swap<T, U>(tuple: [T, U]): [U, T] {
        return [tuple[1], tuple[0]];
    }
    
    swap([7, 'seven']); // ['seven', 7] 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    泛型约束在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法。

    这种情况可以对泛型进行约束,只允许这个函数传入那些包含 length 属性的变量。这就是泛型约束:

    interface Lengthwise {
        length: number;
    }
    function loggingIdentity<T extends Lengthwise>(arg: T): T {
        console.log(arg.length);
        return arg;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    枚举

    enum statusCode{
        ok=0,
        error=1,
        pedding=2
    }
    statusCode[0] === 'ok'
    statusCode['ok'] === 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    vscode在windows环境不能使用终端安装依赖
    Transaction - 记一次 Spring 事务联合 Redis 挂了引发的生产事故
    QT学习_07_样式表的初步学习
    [项目管理-23]:中西方沟通方式的差别:含蓄VS直接
    什么是泛型?Java基础之泛型详细知识点总结
    NC20471 [ZJOI2007]棋盘制作
    聊聊HuggingFace Transformer
    【BOOST C++ 13 并行编程】(2) 同步线程
    Pycharm 远程连接服务器(ssh)运行深度学习代码 | 详细步骤
    大模型的实践应用7-阿里的多版本通义千问Qwen大模型的快速应用与部署
  • 原文地址:https://blog.csdn.net/weixin_42224055/article/details/126240808