目录
JavaScript 基础
- 数据类型:JavaScript 的数据类型分为基本数据类型和引用数据类型。基本数据类型包含 number(数字)、string(字符串)、boolean(布尔值)、null(空值)、undefined(未定义)。而像 array(数组)、function(函数)等则属于引用数据类型。在内存存储方面,基本类型是按值存储在栈中,引用类型则是按地址存储在堆中。
- 类型判断方法:
- typeof:可判断变量是否为 undefined、数值、字符串、布尔值等类型,但对于 null 和 object,以及 object 与 array 的判断存在局限性。例如 typeof null 返回 'object',无法准确区分普通对象和数组。
- instanceof:用于判断对象是否为某个构造函数的实例,能够判断对象的具体类型。如 [] instanceof Array 会返回 true。
- Object.prototype.toString.call():可精准判断基本数据类型和引用数据类型,确切到某个具体类型。例如 Object.prototype.toString.call (1) 返回 '[object Number]'。
- constructor:通过对象的 constructor 属性可判断其构造函数类型,但当原型链被修改时,结果可能不准确。
- Array.isArray():专门用于判断是否为数组,返回布尔值。
- ===:可判断变量是否为 undefined、null,同时比较值和类型是否完全相等。
- 数组判断:除了使用上述提到的 instanceof、constructor、Object.prototype.toString.call ()、Array.isArray () 方法外,还可以通过判断对象是否具有数组特有的方法,如 push、pop 等,但这种方法不够严谨,因为对象也可能被赋予这些同名方法。
- 运行机制:JavaScript 基于事件循环的单线程模型运行。所有同步任务都在主线程上依次执行,而异步任务会被放入事件队列中。当主线程上的同步任务执行完毕后,事件循环机制会从事件队列中取出异步任务,放入主线程中执行。这种机制确保了 JavaScript 在同一时间只能执行一个任务,避免了多线程编程中的复杂问题,但也限制了一些需要大量计算的任务在主线程中的执行,否则可能导致页面卡顿。
- 特殊值:null 表示空对象指针,在定义变量但预期会赋值为对象,且初始阶段想表明该变量还未指向有效对象时,可赋值为 null。undefined 表示变量已声明但未被赋值,或者访问对象不存在的属性、函数没有返回值时会返回 undefined。二者在概念和使用场景上有明显区别。
- 比较运算符:
- ==:在比较时,会进行类型转换,只比较值是否相等。例如 '1' == 1 会返回 true,因为它将字符串 '1' 转换为数字 1 后进行比较。
- ===:不仅比较值,还会比较类型。'1' === 1 会返回 false,因为一个是字符串类型,一个是数字类型。
- 布尔值为 false 的情况:有 undefined、null、NaN(非数字值,且 NaN 与任何值比较都不相等,包括它自身)、false、0(数字零)、0n(BigInt 类型的零)、空串('')。在条件判断中,这些值会被当作 false 处理。
- this 指向:
- 函数直接调用:在非严格模式下,this 指向全局对象 window;在严格模式下,this 指向 undefined。
- 作为对象方法调用:this 指向调用该方法的对象。例如,let obj = {name: 'test', say: function () { console.log (this.name); } }; obj.say (); 这里的 this 指向 obj。
- 构造函数调用:this 指向新创建的实例对象。
- 箭头函数:箭头函数本身没有自己的 this,它的 this 指向定义时所在的作用域中的 this。
- Call、apply、bind:这三个方法都可以改变函数内部 this 的指向。Call 方法第一个参数为要绑定的 this 值,后面参数依次传入函数执行所需参数;apply 方法第一个参数同样为要绑定的 this 值,第二个参数是一个包含函数执行所需参数的数组;bind 方法返回一个新函数,新函数的 this 被绑定为传入的第一个参数,后续调用新函数时再传入实际参数。
- 构造函数与 new:当使用 new 操作符调用构造函数时,会经历以下过程:
- 创建一个新的空对象。
- 将新对象的__proto__属性指向构造函数的 prototype 属性,从而连接到原型链。
- 将构造函数的 this 指向新创建的对象。
- 执行构造函数中的代码,对新对象进行属性和方法的赋值。
- 最后返回这个新的实例对象。
- arguments:在函数内部(箭头函数除外),存在一个名为 arguments 的伪数组,它存储了函数调用时传入的所有实参。虽然它可以通过下标访问元素,但它并不是真正的数组,不具备数组的一些方法,如 map、filter 等。
- 循环控制关键词:
- return:用于结束函数的执行,并返回一个值。如果函数没有显式使用 return 语句,那么会返回 undefined。
- continue:用于结束本次循环,直接进入下一次循环。例如在 for 循环中,执行到 continue 时,会跳过本次循环中 continue 后面的代码,直接开始下一次循环的条件判断。
- break:用于跳出当前所在的最近一层循环,可用于 for、while、do - while 等循环语句中。
- 数组方法:
- 增:push 方法可在数组末尾添加一个或多个元素,并返回新数组的长度;unshift 方法则在数组开头添加元素,同样返回新数组的长度。
- 删:pop 方法用于删除数组的最后一个元素,并返回被删除的元素;shift 方法删除数组的第一个元素,返回被删除的元素。
- 改:splice 方法可用于删除、替换或插入数组元素。例如 arr.splice (1, 2, 'a', 'b') 表示从索引 1 开始,删除 2 个元素,并插入 'a' 和 'b'。
- 查:indexOf 方法返回指定元素在数组中第一次出现的索引,如果不存在则返回 -1;lastIndexOf 方法返回指定元素在数组中最后一次出现的索引。
- 转换:join 方法将数组元素连接成一个字符串,可指定连接符;toString 方法将数组转换为字符串,元素之间用逗号分隔。
- 遍历:forEach 方法用于遍历数组,对每个元素执行传入的回调函数,但没有返回值;map 方法也遍历数组,对每个元素执行回调函数,并返回一个新数组,新数组的元素是回调函数的返回值。
- 过滤:filter 方法用于过滤数组,返回一个新数组,新数组中的元素是通过回调函数测试的元素。
- 事件处理:
- 阻止事件冒泡:在事件处理函数中使用 e.stopPropagation (),可以阻止事件继续向上冒泡传播。例如,当一个子元素和父元素都绑定了点击事件,在子元素的点击事件处理函数中调用 e.stopPropagation (),那么父元素的点击事件就不会被触发。
- 阻止默认事件:使用 e.preventDefault () 可以阻止事件的默认行为。比如,对于一个链接元素的点击事件,默认行为是跳转到链接指向的页面,调用 e.preventDefault () 后,就不会发生跳转。
- return false:在使用 on 方式绑定事件时,在事件处理函数中返回 false,既能阻止事件冒泡,又能阻止默认事件。但在使用 addEventListener 方式绑定事件时,return false 没有这种效果。
- innerHTML 与 innerText:
- innerHTML:用于获取或设置元素的 HTML 内容,会解析标签。例如,div.innerHTML = '<p>新内容</p>',会将一个新的段落元素添加到 div 中。
- innerText:用于获取或设置元素的文本内容,不会解析标签。例如,div.innerText = '<p>新内容</p>',div 中显示的就是'<p>新内容</p>',而不是一个段落。
- 事件代理:利用事件冒泡的特性,将事件处理程序添加到上级元素上,这样当子元素触发事件时,由于事件冒泡,上级元素的事件处理程序会被调用。优点是可以减少事件注册的数量,节省内存消耗,同时在动态添加或删除子元素时,无需重新绑定事件,简化了 DOM 更新操作。但对于一些不冒泡的事件,如 blur、focus 等,无法使用事件代理。而且如果层级过多,事件冒泡的过程可能会受到影响,并且可能导致浏览器频繁调用事件处理函数,影响性能。
- 本地存储:
- sessionStorage:数据仅在当前会话(浏览器标签页)中有效,关闭标签页后数据会被清除。同一页面的不同脚本可以共享 sessionStorage 中的数据。
- localStorage:数据会长久保存在本地,除非手动清除。多个页面之间只要同源,就可以共享 localStorage 中的数据。
- cookie:除了可以存储数据外,还可以在客户端和服务器之间传递,用于身份验证、记录用户偏好等。但 cookie 的存储容量有限,并且会随着每次 HTTP 请求发送到服务器,增加了请求的大小。
- 数组去重:
- 新旧数组遍历对比:创建一个新数组,遍历原数组,检查新数组中是否已存在当前元素,不存在则添加到新数组中。
- Set:利用 Set 数据结构的特性,它会自动过滤掉重复的值。可以将数组转换为 Set,再转换回数组实现去重,如 [...new Set (arr)]。
- filter 与 indexOf 结合:使用 filter 方法,在回调函数中通过 indexOf 判断当前元素在原数组中第一次出现的索引是否等于当前索引,如果相等则说明是唯一的,保留该元素。
- includes:遍历数组,利用 includes 方法判断新数组中是否已包含当前元素,未包含则添加到新数组。
- 利用对象属性:创建一个空对象,遍历数组,将数组元素作为对象的属性名,由于对象属性名不能重复,从而实现去重。
- 事件三要素:
- 事件源:即触发事件的对象,例如按钮、链接、文本框等 DOM 元素。
- 事件类型:如 click(点击事件)、mouseover(鼠标悬停事件)、keydown(键盘按下事件)等。
- 事件处理程序:是一段 JavaScript 代码,用于响应事件的发生,定义当事件发生时要执行的操作。
- DOM 事件流:
- 捕获阶段:事件从最外层的祖先元素开始,逐级向下传播到目标元素。在捕获阶段,事件不会被处理,直到到达目标元素。
- 当前目标阶段:事件到达目标元素,此时目标元素的事件处理程序会被执行。
- 冒泡阶段:事件从目标元素开始,逐级向上传播到最外层的祖先元素。在这个阶段,各级元素的事件处理程序会按照冒泡顺序依次执行。
- 作用域与预解析:
- 作用域:分为全局作用域和局部作用域。在 ES6 之前,JavaScript 没有块级作用域,只有函数作用域。全局作用域中的变量在整个脚本中都可访问,而函数作用域中的变量只能在函数内部访问。ES6 引入了 let 和 const 关键字,具有块级作用域,即一对大括号 {} 内的区域。
- 预解析:在 JavaScript 代码执行前,会进行预解析过程。它会提升 var 声明的变量和 function 声明的函数,将它们的声明提升到当前作用域的顶部,但变量的赋值操作不会被提升。例如,var a = 1; 在预解析时,会将 var a 提升到作用域顶部,实际执行时,先有 a 的声明,再进行赋值操作。
JavaScript 高级
- 原型与原型链:所有引用类型都有一个__proto__属性,它指向该类型的构造函数的 prototype 属性。而函数作为一种特殊的引用类型,有 prototype 属性,该属性指向一个对象,这个对象包含了可以被该函数创建的实例所共享的属性和方法。当访问一个对象的属性或方法时,如果对象本身没有该属性或方法,就会通过__proto__属性沿着原型链向上查找,直到找到该属性或方法,或者到达原型链的顶端(null)。
- 闭包:当一个函数内部访问了外部函数的变量,并且该函数在外部函数执行完毕后仍然存在,就形成了闭包。闭包的作用之一是可以延长变量的生命周期,使得外部函数的变量在函数执行结束后不会被销毁。例如,在一个模块中,通过闭包可以实现数据的私有化,只暴露特定的接口供外部访问。但闭包如果使用不当,可能会导致内存泄漏,因为闭包会一直持有对外部变量的引用,使得这些变量无法被垃圾回收机制回收。所以在不需要闭包时,应该手动释放相关资源,如将闭包函数赋值为 null。
- 内存问题:
- 内存溢出:当程序申请的内存超过了系统所能提供的剩余内存时,就会发生内存溢出错误。通常是由于程序中存在无限循环创建对象、占用大量内存的数据结构等原因导致。
- 内存泄露:指的是程序在使用完内存后,没有及时释放,导致这部分内存一直被占用,无法被其他程序使用。常见的内存泄漏情况包括:全局变量没有及时清理;未清理的回调函数,导致函数中的变量无法被回收;闭包使用不当;频繁创建和使用定时器,没有及时清除;在开发过程中,大量的日志记录也可能导致内存占用过高。
- 递归函数:递归函数是指在函数内部调用自身的函数。递归函数需要设置一个终止条件,否则会陷入死循环。例如计算阶乘的函数:function factorial (n) { if (n === 1) { return 1; } return n * factorial (n - 1); } 递归函数的执行过程类似于循环,每次递归调用都会在调用栈中创建一个新的执行上下文,直到满足终止条件,然后从调用栈中逐层返回。
- 对象创建:
- Object 构造函数:使用 new Object () 创建对象,然后通过点语法或方括号语法添加属性和方法。例如 let obj = new Object (); obj.name = 'test';
- 对象字面量:直接使用 {} 创建对象,并在其中定义属性和方法。例如 let obj = { name: 'test', say: function () { console.log ('Hello'); } };
- 工厂模式:通过一个函数来创建对象,并返回该对象。例如 function createObject (name) { let obj = new Object (); obj.name = name; obj.say = function () { console.log ('My name is'+ this.name); }; return obj; } let obj = createObject ('test');
- 自定义构造函数:定义一个构造函数,使用 new 关键字调用该函数来创建对象。例如 function Person (name) { this.name = name; this.say = function () { console.log ('My name is'+ this.name); }; } let person = new Person ('test');
- 构造函数 + 原型组合:在构造函数中定义实例属性,在原型中定义共享的方法和属性。例如 function Person (name) { this.name = name; } Person.prototype.say = function () { console.log ('My name is'+ this.name); }; let person = new Person ('test');
- class:ES6 引入的 class 语法糖,让对象创建和继承更加直观。例如 class Person {constructor (name) { this.name = name; } say () { console.log ('My name is'+ this.name); } } let person = new Person ('test');
- 原生 Ajax 请求:
- 创建 xhr 对象:使用 XMLHttpRequest 对象来创建一个请求实例。在现代浏览器中,也可以使用 fetch API 来发起请求,但这里主要介绍 XMLHttpRequest 的方式。例如 let xhr = new XMLHttpRequest ();
- 初始化:调用 xhr.open () 方法,设置请求的方法(如 GET、POST)、URL 和是否异步等参数。例如 xhr.open ('GET', 'url', true);
- 发送:如果是 GET 请求,直接调用 xhr.send ();如果是 POST 请求,还需要设置请求头,并在 send 方法中传入请求数据。例如 xhr.setRequestHeader ('Content - type', 'application/x - www - form - urlencoded'); xhr.send ('data=test');
- 事件绑定处理结果:通过监听 xhr 的 onreadystatechange 事件,当 readyState 的值为 4 时,表示请求已完成。在事件处理函数中,根据 status 的值判断请求是否成功,然后获取响应数据进行处理。例如 xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { console.log (xhr.responseText); } else { console.log (' 请求失败 '); } } };
- 跨域:当一个请求的协议、域名、端口号与当前页面的不一致时,就会发生跨域问题。解决跨域的方法有:
- JSONP:利用 script 标签的 src 属性不受同源策略限制的特点,通过动态创建 script 标签,向服务器请求数据,服务器返回一段包含函数调用的 JavaScript 代码,在页面中执行该代码,从而实现跨域数据获取。但 JSONP 只支持 GET 请求。
- cors:跨域资源共享(Cross - Origin Resource Sharing),