JavaScript 核心

一、数据类型

  • 基本类型 VS 引用类型
    基本类型:
    String/Number/Boolean/Null/Undefined/Symbol/BigInt
    存在栈中,值传递,赋值是深拷贝,互不影响
    引用类型:
    Object(包含对象 {}、数组 []、函数 function、Date、RegExp)
    栈存地址、堆存数据,引用传递,修改会相互影响,赋值是浅拷贝

堆VS栈

栈:存放基本类型、函数执行上下文,空间小、自动回收

堆:存放引用类型,空间大、垃圾回收
强类型语言VS弱类型语言

强类型语言禁止隐式类型转换,类型严格、变量类型固定、更健壮,如Java、C、TypeScript;弱类型语言允许自动隐式转换,类型随时可变、易出 BUG,如JS 。
解释性语言VS编译型语言

编译型语言:整体编译、生成可执行文件.exe、运行快、平台绑定、编译期报错。C

解释型语言:逐行翻译、跨平台、开发灵活、运行慢、运行时报错。JS / TS
包装类型

包装类型就是 JS 为基本类型(String/Number/Boolean)临时创建的对象,使其能调用属性和方法,用完即销毁;因此基本类型无法添加属性,而包装对象可以。
内置对象(自带、不用自己定义就能直接用的对象,分为三类:标准内置对象、包装对象、全局对象。)

基础:Object Array Function

包装:String Number Boolean Symbol BigInt

工具:Math JSON Date RegExp

高级:Promise Map Set WeakMap WeakSet Error
检测方式
typeof:检测基本类型。null 会误判为 object,数组、对象、正则等都返回 object,无法区分
instanceof:基于原型链,检测引用类型

(原理:instanceof 用于判断某个对象是否属于某个构造函数的实例,递归查找实例的 _proto _ 原型链,判断是否能找到构造函数的 prototype。

实现:循环获取实例的隐式原型,与构造函数显式原型对比,找到返回 true,到原型链顶端返回 false)
Object.prototype.toString.call ():最精准,可判断所有类型,返回[object Type]
constructor :容易被修改原型,不稳定
Array.isArray():专门判断数组

  • undefined VS null

    1.undefined 表示变量已声明但未赋值;null 表示赋值为空,代表 "空对象"。

    2.typeof undefined 为 undefined,typeof null 为 object(原因是 JS 最初实现中会用类型标签标识类型,null 在内存中表示为空指针(二进制全 0),而对象的类型标签恰好也是 000,导致 typeof 误判,这是一个历史遗留 bug);

    3.转为数字时 undefined 是 NaN,null 是 0

    4.安全获取 undefined 推荐使用 void 0,因为 undefined 不是关键字,可能被局部变量覆盖

  • 隐式类型转换

    运算:+ 遇到字符串转拼接,否则转数字; - * / %一律转数字

    比较:== 优先转数字;

    条件判断 :if /while 转布尔;

    对象先 valueOf 再 toString 转原始值。

  • BigInt

    因为Number 类型数值有精度上限,无法安全表示和运算超大整数,因此提出 BigInt 提案,用于表示任意精度的大整数。

  • NaN

    typeof NaN 的结果是 number,NaN 是 JavaScript 中唯一一个不等于自身的值

    isNaN()先把值转为Number,再判断是不是 NaN

    Number.isNaN() 先判断是不是Number,再判断是不是 NaN,更准确

  • Object.is() 与===、== 的区别?

    1.== 会隐式转换,比较值

    2.=== 比较类型+值,但对 NaN、±0 判断怪异

    NaN === NaN → false

    +0 === -0 → true

    3.Object.is 同 ===,但修复了 两个怪异点

    Object.is(NaN, NaN) → true

    Object.is(+0, -0) → false

  • || 和 && 的返回值

    || 返回第一个真值,全假返回最后一个;&& 返回第一个假值,全真返回最后一个。两者都返回操作数本身,而非布尔值。

  • 精度缺失

    原因:JavaScript 采用 IEEE 双精度浮点数标准,0.1 和 0.2 在二进制中是无限循环小数,存储时会被截断,产生精度丢失,导致相加不等于 0.3。

    解决方法:

    1.转成整数运算后再转回小数(先乘倍数变成整数,运算后再除回去)

    2.使用 toFixed 四舍五入

  • 模板字符串

用反引号,支持直接换行、${} 嵌入变量 / 表达式

新增方法

includes(str):是否包含指定字符串

startsWith(str):是否以指定字符串开头

endsWith(str):是否以指定字符串结尾

repeat(n):重复 n 次

padStart(len, str):前面填充

padEnd(len, str):后面填充

trimStart() / trimEnd():去除开头 / 结尾空格

trim():去除首尾空格

js 复制代码
const str = "hello world";
str.includes("world");  // true
str.startsWith("hello"); // true
str.endsWith("d");      // true
"*".repeat(3); // "***"
"123".padStart(5, "0"); // "00123"
"123".padEnd(5, "0");   // "12300"
  • 数组

1.增删改(会改变原数组)

push:末尾添加 → 返回新长度

pop:末尾删除 → 返回被删元素

unshift:开头添加 → 返回新长度

shift:开头删除 → 返回被删元素

splice(start, count, ...items):删除 / 插入 / 替换

slice(start, end):截取

sort:排序

reverse:反转

fill:填充

concat:拼接多个数组

join:转字符串

2.遍历(不改变原数组)

for...in:遍历索引 key

for...of:遍历元素值

forEach:只遍历,不能链式,无返回

map:遍历并返回一个新数组,可以链式调用,常用于数据加工

filter:过滤,返回符合条件的新数组

find:找第一个符合条件元素,找不到返回 undefined

findIndex:找第一个符合条件索引

findLast / findLastIndex:从后找

reduce :累计 arr.reduce((prev,cur)=> prev+cur, 0)

some:有一个满足就 true

every:全部满足才 true

indexOf:从前找索引

includes:判断是否包含某一项,返回布尔值

keys / values / entries:for (const [i,val] of arr.entries()){}

3.静态

Array.isArray:判断是否数组

Array.from:类数组 → 真数组

  • for...in VS for...of
    for...in 遍历键名/ 索引(key/index),能遍历对象,会遍历原型链,适合对象;
    for...of 遍历值(value),不能遍历普通对象,不遍历原型链,适合数组 / Set / Map / 类数组 / 字符串(可迭代对象)。

普通对象不可迭代,不能直接用 for...of;

借助 Object.keys / values / entries 转为数组,即可遍历;

for (const [key, value] of Object.entries(obj)) {}

  • Map VS WeakMap VS Object
特性 Object Map WeakMap
键的类型 字符串 或 Symbol 任意类型 对象
键的顺序 部分有序 按插入顺序遍历 --
长度 Object.keys(obj).length map.size 无size
原型
遍历 不可直接迭代,需先获取键 原生可迭代(for...of),支持 forEach 不可遍历
性能 频繁增删性能一般 频繁增删性能更优 --
引用方式 强引用,内存泄漏风险较高 弱引用,可 GC 回收,内存泄漏风险极低

Map 键类型任意、有序、可直接遍历、高频操作性能更好,强引用,容易内存泄漏,适合动态、键为复杂类型,且需要频繁增删 / 维护顺序的键值对场景。

Object 键仅限String 或 Symbol、有原型属性、可直接 JSON.stringify,适合存储静态的结构化数据

WeakMap 键只能是对象,是弱引用,会被垃圾回收,不可遍历、无 size,主要用于避免内存泄漏

JSON 是一种轻量级、纯文本的数据交换格式,是纯字符串,键必须双引号

常见用途:前后端接口数据传输;配置文件;本地存储 localStorage;深拷贝(JSON.parse(JSON.stringify(obj)))

二、深拷贝 vs 浅拷贝
浅拷贝

定义:只拷贝第一层属性,引用类型共享地址

实现:Object.assign 和扩展运算符都是浅拷贝。

const newObj = {...obj}语法简洁,只能用于对象字面量;

Object.assign(target, obj1, obj2)会修改第一个目标对象,并支持一次性合并多个源对象。
深拷贝

定义:完全拷贝所有层级,互不影响

实现:JSON.parse(JSON.stringify(obj))、递归

扩展运算符

1.数组 / 对象浅拷贝与合并

2.函数传参(将数组直接作为函数的多个参数传入)如求max、min

3.将类数组转为真数组(Array.from()/...)

const nodeList = document.querySelectorAll('div')

const nodeArr = [...nodeList]

4.解构,快速获取剩余元素

  • 解构
    解构赋值 = 快速从数组 / 对象中提取值,赋值给变量
    数组解构按位置顺序取值,对象解构按属性名匹配
    都支持默认值、嵌套、剩余参数
    //默认值
    const [a, b = 20] = [10] // a = 10, b = 20
    //嵌套
    const { user: { name } } = { user: { name: 'zs' } } // name = 'zs'
    //rest 参数( ...变量名)
    用于获取函数的多余参数,将其整合为一个数组,必须放在参数末尾,参数个数不确定时使用
特性 arguments rest 参数
类型 类数组对象(不是真数组) 真正的数组(可直接用 map/filter)
获取 获取函数所有传入的参数 获取形参之外的剩余参数
写法 无需声明 需要通过 ... 显式声明

类数组对象

定义:可以通过下标访问、有 length 属性,但不能直接调用数组方法。常见如 arguments、DOM 集合。

转换:Array.from(arrayLike) / [...arrayLike]

原因:arguments 是设计时,只需要索引和 length,所以是轻量级类数组对象;遍历可用 for 循环、for...of、转数组

三、原型链

每个函数有 prototype ,用于挂载公共属性方法,节省内存

每个对象都有 _proto _ ,指向构造函数的 prototype

对象访问属性会通过 proto 逐层向上查找,这条链式结构就是原型链,原型链最终指向 Object.prototype,终点为 null,JS 借助原型与原型链实现继承和方法复用。

原型修改:在原 prototype 上追加 / 修改方法,不破坏原型关系,新旧实例通用,constructor 正常;

原型重写:直接覆盖整个 prototype 为新对象,旧实例失效、constructor 丢失、原原型方法清空,需手动挂载 constructor

开发中优先使用原型追加修改

  • 原型链指向:

    1.实例 _proto _ ->自身构造函数的 prototype

    2.所有引用类型的原型->Object.prototype

    Person.prototype._proto _ → Object.prototype

    Function.prototype._proto _ → Object.prototype

    3.Object.prototype._proto _ = null

    4.构造函数的 _proto _ -> Function.prototype

    Function._proto _ → Function.prototype

    Object.proto → Function.prototype

  • 获得对象非原型链上的属性

    Object.keys (obj) 返回可枚举的属性名数组

    需包含不可枚举属性用 Object.getOwnPropertyNames (obj)

    若还需包含 Symbol 键用 Reflect.ownKeys (obj)。

  • new

    实现原理:

    创建空对象、关联原型(将实例的 proto 指向构造函数的 prototype)、将构造函数的 this 指向实例,执行函数并返回新对象或返回函数返回的对象。

返回值规则:

构造函数无返回值 → 返回新对象

构造函数返回原始值 → 返回新对象

构造函数返回对象 → 返回该对象(覆盖新对象)

  • 箭头函数VS普通函数
    箭头函数没有 prototype、没有自己的 this,继承外层 this,不能修改 this,也没有内部构造方法,不能被 new 实例化,否则会抛出类型错误。
    普通函数 this 动态绑定,指向调用它的对象,用 call/apply/bind 可以改变 this。可做构造函数,new 生成实例,有 prototype。

四、变量提升

原因:变量提升是为了解决函数相互调用和 JS 编译执行阶段的设计,会导致变量未赋值就可访问并得到 undefined,容易出错。

解决:var 有提升,默认 undefined、无块级作用域、可重复、挂全局;let 有块级作用域、存在暂时性死区、不重复;const 同 let,但必须初始化赋值且栈内容不可改(基础类型不能修改,引用类型可以修改内部属性,但不能改指向);函数提升只提升声明

尾调用就是函数最后一步是返回另一个函数的调用结果,后面不能再有其他操作;

尾递归是特殊尾调用。引擎可做尾调用优化,复用栈帧,无论递归多少次,调用栈深度始终为 1,不会溢出。
CommonJS:require/module.exports、同步加载,运行时解析,先执行完模块代码,再拿到导出结果、浅拷贝、不支持树摇;

ESM:import/export、异步加载,编译时解析,适合前端。引用绑定,外部导入的值自动更新、支持静态分析和 Tree-Shaking,打包可删除未使用代码,优化体积。

同:都实现模块化、作用域隔离、代码复用;
use strict 是严格模式;限制未声明变量、修正 this 指向this === undefined、禁止怪异语法、强制代码规范,减少隐性 bug,提升代码安全性与引擎优化能力

五、 闭包

内层函数 + 外层函数变量= 闭包

函数嵌套,内层函数引用外层变量且被外部引用,外层执行完毕变量不销毁

作用:实现变量私有化;外部可访问内部变量;缓存、柯里化、防抖、节流

缺点:内存泄漏(局部变量仍存在),需手动释放引用

六、作用域 & 作用域链

作用域:变量可访问范围。分为全局(页面关闭时销毁)、函数、块级作用域(GC)

作用域链:变量的查找规则。先在当前作用域查找;找不到就逐级向外层父作用域查找;一直查到全局作用域。这种层层向上查找变量的链式结构,就是作用域链,在函数定义时就已确定

var 函数作用域;

let/const 有块级作用域

执行上下文是 JS 引擎为代码运行创建的抽象环境,代表代码当前正在执行的环境,包含了 This 指向、变量存储的变量对象以及访问上级的作用域链。它分为创建(确定 This、构建 VO、建立作用域链)和执行(代码运行)两个阶段。会创建全局、函数执行上下文。一个程序只有 一个 全局上下文,页面关闭时销毁。可以有 无数个函数执行上下文,但同一时间只有一个在运行,函数执行完毕后,上下文销毁,内部变量被垃圾回收。

多个执行上下文通过 调用栈 (Call Stack) 来管理,遵循后进先出 (LIFO) 原则。
this 由函数调用方式决定,而非定义位置;

1.箭头函数无自身 this,继承外层作用域的 this

2.new 绑定:this 指向实例对象

3.call/apply/bind 硬绑定:

fn.call(thisArg, 参数1, 参数2)、fn.apply(thisArg, [参数数组])

同:立即执行,this 绑定为传入对象,返回结果和新函数

异:传参形式不同(call 接收零散参数,apply 接收数组参数)

fn.bind(obj):不执行,返回新函数,永久绑定 this,不会被修改

4.谁调用,指向谁

5.函数:

非严格模式:独立函数直接调用,this → window / 全局对象

严格模式:独立调用,this → undefined

七、 防抖、节流

防抖:触发后 n 秒内不再触发才执行/多次触发只执行最后一次(搜索框输入)等你不动了,我再执行

节流:固定时间内只执行一次/多次触发只执行一次(滚动、拖拽)不管点多快,我按固定节奏来

js 复制代码
// 防抖 :设置定时器,频繁触发就清空重计时;
function debounce(fn, delay) {
  let timer = null;
  return function(...args) {
    // 每次触发都清空定时器,重新计时
    if (timer) clearTimeout(timer);//有则清除
    timer = setTimeout(() => { //没有就添加
      fn.apply(this, args);
      timer = null; //使用完清除
    }, delay);
  };
}
//Lodash 实现
npm i lodash
const debounceSearch = _.debounce(函数, 延迟毫秒, [配置])
//常用配置
//leading: true 立即执行,之后等待间隔
//trailing: true 默认,结束后延迟执行
// 取消
debounceSearch.cancel();

// 节流:记录时间戳,间隔到了才允许执行。
function throttle(fn, interval) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      fn.apply(this, args);
      lastTime = now;
    }
  };
}
//Lodash 实现
const throttleScroll = _.throttle(函数, 300);
// 绑定滚动事件
window.addEventListener('scroll', throttleScroll);

React 中必须用 useRef 存防抖 / 节流函数,函数组件每次渲染都会重新创建函数实例,不加 useRef 防抖节流会失效

js 复制代码
//搜索框防抖
import { useState, useRef } from 'react';
import _ from 'lodash';
export default function SearchInput() {
  const [keyword, setKeyword] = useState('');

  // useRef 缓存防抖函数实例
  const debounceFn = useRef(
    _.debounce((val) => {
      // 发起接口请求
      console.log('请求搜索:', val);
    }, 500)
  ).current;

  const handleChange = (e) => {
    const val = e.target.value;
    setKeyword(val);
    debounceFn(val);
  };
  return <input value={keyword} onChange={handleChange} placeholder="输入搜索" />;
}
}
js 复制代码
滚动节流
import { useEffect, useRef } from 'react';
import _ from 'lodash';
export default function ScrollBox() {
  const throttleFn = useRef(
    _.throttle(() => {
      console.log('滚动位置:', window.scrollY);
    }, 200)
  ).current;
  useEffect(() => {
    window.addEventListener('scroll', throttleFn);
    return () => {
      // 组件卸载取消监听 + 取消节流
      window.removeEventListener('scroll', throttleFn);
      throttleFn.cancel();
    };
  }, []);
  return <div style={{height: '200vh'}}>滚动页面测试</div>;
}

八、垃圾回收机制

1.主要回收算法

引用计数:每个对象维护一个引用计数器,计数器为 0 立即回收。互相引用,内存泄漏

标记清除:从根节点标记可达对象,清除不可达,现代浏览器主流

2.分代回收

新生代:存放短期使用、生命周期短的对象,空间小、回收频繁、速度快,算法:复制回收,采用两个等大空间From / To,存活对象复制转移,清空原空间

老生代:长期存活、常驻对象,空间大、回收频率低,算法:标记清除 + 标记压缩,避免内存碎片

3.内存泄漏

意外全局变量(未声明变量挂载 window)

闭包滥用:内层长期引用外层变量,无法释放

定时器 / 事件监听 未销毁

DOM 元素脱离文档,仍被 JS 引用

循环引用(旧引用计数缺陷,现代标记清除已解决)

全局缓存无限累加

第三方组件未销毁

4.如何避免内存泄漏

合理使用 let/const,禁用隐式全局

定时器、监听事件页面销毁时手动清除

闭包合理使用,不用的引用手动置空 obj = null

DOM 操作后及时解绑引用

九、转换

普通字符串 ↔ 数组 split() / join()

对象/数组 ↔ JSON字符串 JSON.stringify/ JSON.parse

对象 -> 数组 keys()/values()/entries()

相关推荐
之歆1 小时前
DAY_23 JavaScript 函数进阶:作用域 · 提升 · 匿名函数 · IIFE · 回调 · 递归 · Object 对象建模(下)
开发语言·javascript·ecmascript
哆哆啦001 小时前
CSS 选择器优先级计算规则
前端·javascript·css3
千寻girling1 小时前
周日那天参加的力扣周赛... —— 10号
java·javascript·c++·python·算法·leetcode·职场和发展
zhoumeina991 小时前
设计器模版底图,一直渲染错误,是因为第一张图变形后内存中图片数据被改了,其他尺码一直错误
java·前端·javascript
ZC跨境爬虫2 小时前
跟着 MDN 学 HTML day_42:(DOMTokenList 接口详解)
前端·javascript·ui·html·ecmascript·音视频
前端 贾公子2 小时前
响应式系统基础:基于依赖追踪的响应式系统的本质(下)
前端·javascript·vue.js
yqcoder2 小时前
突破性能瓶颈:深入理解 JavaScript TypedArray
java·开发语言·javascript
yqcoder2 小时前
JS 中的“空”之双雄:null vs undefined
开发语言·前端·javascript
计算机安禾2 小时前
【c++面向对象编程】第8篇:const成员与mutable:常对象与常函数
开发语言·javascript·c++