一 数据类型介绍及值类型和引用类型的理解(8.2)
- 基本数据类型:number,string,Boolean,null,undefined,symbol
- 引用数据类型:object,Array,function
- 值类型是指基本数据类型,每个变量都有自己独立的值。
- 引用类型是指除了基本类型以外的类型,变量存储的是一个指向实际对象的引用,对一个变量修改会影响到另一个变量
二 数据类型的判断(8.3)
- typeof:判断基本类型,返回数据类型,但是无法判断null和数组
- instanceof:用于检查对象是否属于某个特定的类。可以判断数组
- Object.prototype.toString:获取一个对象内部的属性
- Array.isArray:用于判断一个对象是否为数组类型
// 用法 typeof value; value instanceof Constructor; Object.prototype.toString.call(value); Array.isArray(value);
三 实现深拷贝(8.4)
// 1.使用JSON序列化和反序列化
JSON.parse(JSON.stringify(obj))
// 2.使用递归复制
const deepCopy = (obj) => {
if (typeof obj !== 'object' || obj === null) {
return obj
}
let copy = Array.isArray(obj) ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopy[obj(key)]
}
}
return copy
}
四 根据0.1+0.2 != 0.3,讲讲IEEE754 ,如何让其相等?(8.5)
- IEEE754是一种浮点数表示准则,它定义浮点数的存储和运算规则。然而,由于浮点数的二进制表示存在精确限制,导致某些十进制数无法精确表示,从而引发了类似0.1+0.2 != 0.3的场景
- 解决办法:
- 使用整数运算
- 使用toFixed()方法
- 使用Math.round()方法
五 原型和原型链(8.7)补8.6
- 原型:
- 在JS中,每一个对象都有一个原型(prototype)属性,它指向另一个对象或者为null
- 原型是一个普通对象,它包含了共享的属性和方法
- 当我们访问一个对象的属性或方法时,如果对象本身没有该属性和方法时,JS引擎会沿着原型链往上查找,直到找到该属性或方法
- 原型链:
- 原型链是由对象的原型组成的链式结构
- 作用:
- 实现对象之间的继承
- 实现属性和方法的共享
六 作用域和作用域链(8.7)
- 作用域
- 作用域是指变量和函数的可访问范围
- JS中有全局作用域和局部作用域(函数作用域和块级作用域)
- 全局作用域定义的变量和函数可以在整个代码中访问
- 局部作用域中定义的变量和函数只能在其所在的作用域内部访问
- 作用域链
- 是由多个作用域组成的链式结构
- 作用域的顶端是全局作用域
- 作用:
- 控制变量和函数的可见性和可访问性:可以避免命名冲突和变量污染
- 实现变量的封装和保护:通过作用域链,避免外部作用域对变量的直接访问和修改
七 执行上下文(8.8)
- 在JS中,执行上下文是指代码执行时的环境,包含了变量,函数和对象等信息。每当JS代码执行时,都会创建一个执行上下文,并且这些执行上下文会行程一个执行上下文栈
- 执行上下文包含三个重要组成部分
- 变量对象:它存储了在上下文中定义的变量,函数声明和函数参数。在全局上下文中,变量对象被称为全局对象,在函数上下文中,被称为活动对象
- 作用域链:
- this值:指向当前执行上下文所属对象。在全局下,执行全局对象。在函数下,this的值取决于函数的调用方式
- 生命周期
- 创建阶段:在创建阶段,JS引擎会创建变量对象,建立作用域和确定this值,同时,函数声明会被提到当前作用域的顶部
- 执行阶段:在执行阶段,JS引擎会按照代码的顺序执行,将变量,函数等操作添加到执行栈中执行
- 销毁阶段:在执行完成后,执行上下文会被销毁,释放内存空间
八 闭包(8.9)
- 闭包是指函数以及其在创建时能够访问的词法环境的组合,简单来说,闭包是由函数和其相关的引用环境组成的包裹
- 特点:可以访问其外部函数的变量和参数,即使外部函数以及执行完毕
- 优势:
- 封装私有变量
- 记忆状态
- 实现模块化
- 缺点:
- 内存占用
- 性能影响
- 变量共享和意外修改
- 难以理解和调试
- 简单的例子
function outerFunction(){ let outerVariable = 'Hello'; function innerFunction() { console.log(outerVariable); } return innerFunction; } let closure = outerFunction(); closure();
九 new的实现(8.10)
- 实现步骤:
- 创建一个空对象,作为新对象的实例
- 将新对象的原型指向构造函数的原型对象,已便新对象可以访问构造函数原型上的属性和方法
- 将构造函数的this指向新对象
- 执行构造函数中的代码,初始化新对象的属性和方法
- 如果构造函数没有显式返回一个对象,则返回新对象
- 简单例子
function myNew(constructor, ...arge) { // 创建一个空对象,作为新对象的实例 const obj = {} // 将新对象的原型指向构造函数的原型对象 Object.setPrototypeOf(obj, constructor.prototype) // 将构造函数的this指向新对象,并进行构造函数 const result = constructor.apply(obj, arge) // 如果有构造函数有显式返回一个对象,则返回该对象,否则返回新对象 return typeof result === 'object' && result !== null ? result : obj } // 实例构造函数 function Person(name, age) { this.name = name this.age = age } // 使用myNew创建实例对象 const person = myNew(Person, 'John', 26) console.log(person.name); // 输出: John console.log(person.age); // 输出: 25 console.log(person instanceof Person); // 输出: true
十 call,apply,bind的实现即应用场景(8.11)
- 都用于改变函数执行上下文(即函数内部this值)的方法
- call
- 语法:function.call(thisArg, arg1, arg2, ...)
- 作用:立即调用函数,并将指定的 this 值和参数传递给函数。可以将任意对象作为函数的上下文,并立即执行函数
- 应用场景:常用于借用其他对象的方法,或者在函数内部指定特定的上下文执行
- apply
- 语法:function.apply(thisArg, [argsArray])
- 作用:立即调用函数,并将指定的 this 值和参数数组传递给函数,可以将任意对象作为函数的上下文,并立即执行函数
- 应用场景:常用于借用其他对象的方法,或者在函数内部指定特定的上下文执行,与call 不同的是,apply 接受的参数是数组而不是逐个参数
- bind
- 语法:function.bind(thisArg, arg1, arg2, ...)
- 作用:创建一个新的函数,将指定的 this 值和参数绑定到原函数上,返回的新函数可以在稍后的时候调用
- 应用场景:常用于场景一个绑定了特定上下文的函数,可以在稍后的时候调用或者用于事件处理函数,定时器回调等需要保持特定上下文的场景
十一 异步(8.12)
- 在js中异步是一种处理代码执行顺序的机制。当代码执行到异步操作时,它会被放入事件循环队列中,然后继续执行后续的代码,而不会等待异步操作完成
- 常见的场景
- 定时器:使用setTimeout或setInterval可以在一定时间间隔后执行回调函数,而不会阻塞后续代码的执行
- AJAX请求:通过XMLHttpRequest或现代的fetchAPI发起网路请求时,可以使用回调函数或Promise对象来处理异步响应
- 事件处理:当用户与页面进行交互时,例如点击事件,滚动页面等,可以通过事件监听器来处理异步操作
- 异步函数:ES6引入了asyns和await语法,可以更方便地处理异步操作,通过在函数前面加上asyns关键字,函数内部使用await关键字来等待异步操作完成
十二 浏览器回收机制(8.14)补8.13
- 标记清除(Mark and Sweep)
- 当变量不在被引用时,垃圾回收器会将其标记为可回收
- 垃圾回收器会从根对象开始遍历,标记所有与根对象相关联的对象
- 未被标记的对象将被视为垃圾,将被清除并释放内存
- 引用计数(Reference Counting)
- 垃圾回收器会为每个对象维护一个引用计数器
- 当对象被引用时,引用计数器加一,当对象引用被释放时,引用计数器减一
- 当引用计数器为零时,垃圾回收器会将对象标记为可回收的,并释放相关内存
- 内存回收的时机
- 垃圾回收器会根据一定策略和算法来确定何时执行内存回收
- 常见的策略包括标记-清除,标记-整理,分代回收等
- 垃圾回收器会在空闲时刻或达到一定的条件时触发内存回收
- 避免内存泄漏
- 内存泄漏是指不再使用的对象仍然被保留在内存中,无法被垃圾回收器释放
- 避免内存泄漏的方法包括及时释放不再使用的对象,避免循环使用,合理使用闭包等
十三 防抖(8.14)
- 是一种优化技术,用于限制某个函数在短时间内频繁触发的情况,以减少不必要的计算和网路请求
- 原理:在函数被触发后,设置一个定时器,在指定时间间隔内如果函数再次被触发,则清除之前的定时器并重新设置新的定时器,知道指定的时间间隔内没有再次触发函数,才真正执行函数
- 常用场景
- 输入框搜索
- 窗口调整
- 按钮点击
- 简单的实例
function debounce(func, delay) { let timer return function() { const context = this const args = arguments clearTimeout(timer) timer = setTimeout(function() { func.apply(context, arge) }, delay) } } function search() { // 执行搜索操作 } const debounceSearch = debounce(search, 300) input.addEventListener('input', debounceSearch)
十四 节流(8.15)
-
是一种限制函数执行频率的技术。它可以确保在一定时间间隔内,函数只会被执行一次,无论触发频率多高
-
常用场景
-
监听滚动事件
-
处理输入框事件
-
防止按钮重复点击
-
-
简单实例
function throttle(func, delay) { let lastTime = 0 return function() { const currentTime = Date.now() if (currentTime - lastTime >= delay) { func.apply(this, arguments) lastTime = currentTime } } } const throttledScrollHandler = throttle(function () { // 处理滚动事件的逻辑 }, 200) window.addEventListener('scroll', throttledScrollHandler)
十五 继承(8.16)
- 继承是一种通过创建一个新的对象来扩展现有对象的能力,
- 实现方式
- 原型链继承:通过将子类的原型对象指向父类的实例来实现继承。
function Parent() { this.name = 'Parent' } Parent.prototype.sayHello = function() { console.log('Hello, ' + this.name); } function Child() { this.name = 'Child' } Child.prototype = new Parent() let child = new Child() child.sayHello(); // 输出:Hello, Child
- 构造函数继承:通过子类的构造函数中调用父类的构造函数来实现继承
function Parent() { this.name = 'Parent'; } function Child() { Parent.call(this) this.name = 'Child' } let child = new Child() console.log(child.name) // 输出:Child
- 组合继承:结合原型链继承和构造函数继承的方式,即可以继承父类的属性和方法,又可以拥有自己的属性
function Parent() { this.name = 'Parent' } Parent.prototype.sayHello = funciton() { console.log('Hello, ' + this.name) } function Child() { Parent.call(this) this.name = 'Child' } Child.prototype = new Parent() Child.prototype.constructor = Child var child = new Child() child.sayHello() // 输出:Hello, Child
- ES6类继承:ES6引入了class和extends关键字,提供了更简洁的语法来实现继承
class Parent { constructor() { this.name = 'Parent'; } sayHello() { console.log('Hello, ' + this.name) } } class Child extends Parent { constructor() { super() this.name = 'Child' } } let child = new Child() child.sayHello() // 输出:Hello, Child
- 原型链继承:通过将子类的原型对象指向父类的实例来实现继承。
十六 promise 实现(8.17)
- 是一种用于处理异步操作的对象。它可以用来从处理异步操作的成功和失败,并返回响应的结果
- 基本实现
const promise = new Promise((resolve, reject) => { // 异步操作 // 如果操作成功,调用 resolve 并传递结果 // 如果操作失败,调用 reject 并传递错误信息 }) promise.then((result) => { // 处理操作成功的结果 }).catch((error) => { // 处理操作失败的错误信息 })
十七 Proxy(8.18)
- 是一种用于创建代理对象的特殊对象。代理对象可以拦截并自定义目标对象的操作,包括属性访问,赋值,函数调用等。proxy提供一种机制,可以在目标对象上进行拦截和修改操作,从而实现对目标对象的动态控制和增强
- 基本用法
const proxy = new Proxy(target, handler);
- 可以定义多个拦截操作
- get(target, property, receiver):拦截对目标对象属性和读取操作
- set(target, property, value, receiver):拦截对目标对象属性和赋值操作
- apply(target, thisArg, argumentsList):拦截对目标对象的函数调用
- has(target, property):拦截in操作符
- deleteProperty(target, property):拦截delete操作符
- getOwnPropertyDescriptor(target, property):拦截Object.getOwnPropertyDescriptor()方法
- defineProperty(target, property, descriptor):拦截Object.defineProperty()方法
- 优点
- 可以对目标对象的操作进行拦截和修改,实现对目标对象的动态控制和增强
- 可以用于实现数据绑定,数据校验,日志记录功能
- 缺点:
- 兼容性较差,不支持IE浏览器
- 由于proxy可以拦截目标对象的所有操作,使用不当可能导致性能问题或安全问题
十八 generator 实现(8.21)
-
生成器(Generator)是一种特殊的函数,可以通过function*语法来定义。Generator函数可以通过yield关键字来暂停执行,并返回一个值给调用者。调用者可以通过生成器的next()方法来恢复生成器的执行,并获取生成器返回的值
-
简单实例
function* myGenerator() { yield: 1; yield: 2; yield: 3; } // 创建生成器对象 const generator = myGenerator() // 调用生成器的next()方法来获取生成器返回的值 console.log(generator.next()); // { value: 1, done: false } console.log(generator.next()); // { value: 2, done: false } console.log(generator.next()); // { value: 3, done: false } console.log(generator.next()); // { value: undefined, done: true }
十九 事件模型(8.22)
- 三个阶段
- 捕获阶段:事件从最外层的祖先元素向目标元素进行传播
- 目标阶段:事件到达目标元素的阶段
- 冒泡阶段:事件从目标元素开始向最外层的祖先元素进行传播的阶段
- 冒泡事件:当你使用事件冒泡时,子元素先触发,祖先元素后触发
- 捕获事件:当你使用事件捕获时,父元素先触发,子元素后触发
- 阻止冒泡: stopPropagation(),IE设置cancelBubble = true
- 阻止捕获:IE设置widow.event.returnValue = false,其他preeventDefault()