JS基础篇
25019字约83分钟
2025-02-11
JavaScript 基础篇
JS 数据类型
查看答案
- 基本类型:
undefined
、null
、boolean
、number
、string
、symbol
、bigint
- 引用类型:
object
扩展:symbol 有什么用处?
- 可以用来表示一个独一无二的变量防止命名冲突。
- 还可以利用
symbol
不会被常规的方法(除了 Object.getOwnPropertySymbols 外)遍历到,所以可以用来模拟私有变量。 - 主要用来提供遍历接口,布置了
symbol.iterator
的对象才可以使用for···of
循环,可以统一处理数据结构。调用之后回返回一个遍历器对象,包含有一个next
方法,使用next
方法后有两个返回值value
和done
分别表示函数当前执行位置的值和是否遍历完毕。 Symbol.for()
可以在全局访问symbol
0.1 + 0.2 === 0.3 ?为什么?
查看答案
JavaScript 使用 Number
类型来表示数字(整数或浮点数),遵循 IEEE 754
标准,通过 64 位来表示一个数字(1 + 11 + 52)。
- 1 符号位,0 表示正数,1 表示负数 s
- 11 指数位(e)
- 52 尾数,小数部分(即有效数字)
最大安全数字:Number.MAX_SAFE_INTEGER = Math.pow(2, 53) - 1
,转换成整数就是 16 位,所以 0.1 === 0.1
,是因为通过 toPrecision(16) 去有效位之后,两者是相等的。
在两数相加时,会先转换成二进制,0.1 和 0.2 转换成二进制的时候尾数会发生无限循环,然后进行对阶运算,JS 引擎对二进制进行截断,所以造成精度丢失。
所以总结:精度丢失可能出现在进制转换和对阶运算中。
注意,JavaScript 中 0 有 +0 和 -0 的区别,1/+0 是 Infinity,1/-0 是 -Infinity。任何超出安全整数范围的数字,都会被表示为 Infinity,而 NaN 表示一个非数字值。0/0 是 NaN。
如何判断 JS 数据类型?
查看答案
typeof
可以判断基本类型和函数,但是对于null
和object
类型会返回object
instanceof
可以判断引用类型,但是不能判断基本类型Object.prototype.toString.call()
可以判断所有类型
事件如何实现的?事件流?
查看答案
事件
基于发布订阅模式,就是在浏览器加载的时候会读取事件相关的代码,但是只有实际等到具体的事件触发的时候才会执行。 比如点击按钮,这是个事件(Event),而负责处理事件的代码段通常被称为事件处理程序(Event Handler),也就是「启动对话框的显示」这个动作。 在 Web 端,我们常见的就是 DOM 事件:
- DOM0 级事件,直接在 html 元素上绑定
on-event
,比如onclick
,取消的话,dom.onclick = null
,同一个事件只能有一个处理程序,后面的会覆盖前面的。 - DOM2 级事件,通过
addEventListener
注册事件,通过removeEventListener
来删除事件,一个事件可以有多个事件处理程序,按顺序执行,捕获事件和冒泡事件。 - DOM3 级事件,增加了事件类型,比如 UI 事件,焦点事件,鼠标事件等。
事件流
事件流是网页元素接收事件的顺序,"DOM2级事件"规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。首先发生的事件捕获,为截获事件提供机会。然后是实际的目标接受事件。最后一个阶段是事件冒泡阶段,可以在这个阶段对事件做出响应。虽然捕获阶段在规范中规定不允许响应事件,但是实际上还是会执行,所以有两次机会获取到目标对象。
作业域?作用域链?
查看答案
- 作业域:全局作用域和函数作用域,全局作用域就是最外层的作用域,函数作用域就是在函数内部定义的作用域。
- 作用域链:作用域链是指在当前执行环境中,查找变量的顺序。
new 一个函数发生了什么?
查看答案
构造调用:
- 创造一个全新的对象
- 这个对象会被执行
[[Prototype]]
连接,将这个新对象的[[Prototype]]
链接到这个构造函数.prototype
所指向的对象 - 这个新对象会绑定到函数调用的
this
- 如果函数没有返回其他对象,那么
new
表达式中的函数调用会自动返回这个新对象
实现一个 new 函数
查看答案
function myNew(fn, ...args) {
const obj = {}
obj.__proto__ = fn.prototype
const res = fn.apply(obj, args) // 改变 this 指向
return typeof res === 'object' ? res : obj // 如果返回的是对象,就返回这个对象,否则返回新创建的对象
}
什么是闭包?
查看答案
闭包是指有权访问另一个函数作用域中的变量的函数。
function fn() {
let num = 1
return function() {
console.log(num)
}
}
const res = fn()
res() // 1
如何产生闭包
- 返回函数
- 函数当做参数传递
闭包的应用场景
- 封装私有变量
- 防抖节流
- 柯里化
闭包的优缺点
优点:
- 避免全局变量的污染
- 能够读取函数内部的变量
- 可以在内存中维护一个变量
缺点:
- 闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
- 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
创建对象有几种方法?
查看答案
- 1、字面量对象 // 默认这个对象的原型链指向 object
var o1 = {name: '01'};
2、通过 new Object
声明一个对象
var o11 = new Object({name: '011'});
3、使用显式的构造函数创建对象
var M = function(){this.name='o2'};
var o2 = new M();
4、object.create()
var P = {name:'o3'};
var o3 = Object.create(P);
简述创建函数的几种方式?
查看答案
- 函数声明
function fn() {}
- 函数表达式
var fn = function() {}
- 函数对象方式
var fn = new Function("num1","num2","return num1+num2");
- 箭头函数
var fn = () => {}
请指出 JavaScript 宿主对象和原生对象的区别?
查看答案
在 JavaScript 中,宿主对象(Host Objects)和原生对象(Native Objects)是两种不同类型的对象,它们的主要区别如下:
原生对象
是由 JavaScript 语言本身定义的对象,它们是 ECMAScript 标准的一部分,不依赖于特定的宿主环境。例如,Object
、Array
、Function
、Date
、RegExp
等都是原生对象。
宿主对象
是由 JavaScript 宿主环境提供的对象,它们是由 JavaScript 宿主环境(如浏览器、Node.js 等)提供的对象。例如,在浏览器中,window
、document
、location
等都是宿主对象。
区别
原生对象是由 JavaScript 语言本身定义的对象,它们是 ECMAScript 标准的一部分,不依赖于特定的宿主环境。而宿主对象是由 JavaScript 宿主环境提供的对象,它们是由 JavaScript 宿主环境(如浏览器、Node.js 等)提供的对象。
JavaScript 内置的常用对象有哪些?并列举该对象常用的方法?
查看答案
全局对象(Global Objects)
Object
Object.keys(obj)
:返回一个由给定对象的所有可枚举属性组成的数组。Object.values(obj)
:返回一个给定对象自己的所有可枚举属性值的数组。Object.assign(target, ...sources)
:用于将一个或多个源对象的所有可枚举属性复制到目标对象。 Arraypush(element1, ..., elementN)
:在数组的末尾添加一个或多个元素,并返回新的长度。pop()
:移除数组的最后一个元素并返回该元素。shift()
:移除数组的第一个元素并返回该元素。unshift(element1, ..., elementN)
:在数组的开头添加一个或多个元素,并返回新的长度。splice(start, deleteCount, item1, item2, ...)
:通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。slice(begin, end)
:返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。map(callbackFn)
:创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。filter(callbackFn)
:创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。reduce(callbackFn, initialValue)
:对数组中的每个元素执行一个由您提供的 reducer 函数(升序执行),将其结果汇总为单个返回值。 StringcharAt(index)
:返回指定索引位置的字符。concat(str1, ..., strN)
:用于连接两个或多个字符串。substring(indexStart, indexEnd)
:返回一个字符串在开始索引到结束索引之间的一个子集, 或从开始索引直到字符串的末尾的一个子集。split(separator, limit)
:使用指定的分隔符字符串将一个String对象分割成子字符串数组,以一个指定的分割字串来决定每个拆分的位置。toUpperCase()
:将调用该方法的字符串值转换为大写形式。toLowerCase()
:将调用该方法的字符串值转换为小写形式。 NumbertoFixed(digits)
:使用定点表示法来格式化一个数值。toString(radix)
:返回指定 Number 对象的字符串表示形式。 Boolean- 通常直接使用布尔值,较少使用其对象方法。 FunctionDate
getFullYear()
:返回指定日期的年份。getMonth()
:返回指定日期的月份(0 - 11)。getDate()
:返回指定日期的日(1 - 31)。toLocaleString()
:根据本地时间格式,把 Date 对象转换为字符串。 RegExptest(str)
:执行一个检索,用来查看正则表达式与指定的字符串是否匹配。返回 true 或 false。exec(str)
:在一个指定字符串中执行一个搜索匹配。返回一个结果数组或 null。
错误对象(Error Objects)
Error
- 通常用于抛出和捕获错误,较少使用其对象方法。 SyntaxError
- 继承自 Error,用于表示语法错误。 ReferenceError
- 继承自 Error,用于表示引用错误
数学对象(Math Object)
Math
Math.random()
:返回一个浮点数,其范围是 [0, 1)。Math.floor(x)
:返回小于或等于一个给定数字的最大整数。Math.ceil(x)
:返回大于或等于一个给定数字的最小整数。Math.round(x)
:返回一个数字四舍五入后最接近的整数。Math.max(value1, value2, ...)
:返回一组数中的最大值。Math.min(value1, value2, ...)
:返回一组数中的最小值。
Javascript 创建类的几种方式?
查看答案
- 构造函数方式
- 工厂函数方式
- ES6 类语法
谈谈This对象的理解, bind, call, apply的区别和实现?
查看答案
this 是什么?
在 JavaScript 中,this 是一个特殊的关键字,它的值取决于函数的调用方式。this 可以指向不同的对象,具体取决于函数是如何被调用的,以下是几种常见的情况:
全局作用域
在全局作用域中,this
指向全局对象。在浏览器环境中,全局对象是 window
。
函数调用
当函数作为普通函数调用时,this
指向全局对象(在严格模式下,this
为 undefined
)。
方法调用
当函数作为对象的方法调用时,this
指向调用该方法的对象。
构造函数调用
当使用 new
关键字调用函数时,this
指向新创建的对象。
箭头函数
箭头函数中的 this
是在定义函数时确定的,而不是在调用时确定的。箭头函数中的 this
指向的是定义箭头函数的上下文。
bind, call, apply的区别和实现?
call()call
方法允许你调用一个函数,并指定该函数内部的 this
值。它还可以接受多个参数,这些参数会依次传递给被调用的函数。
实现:
Function.prototype.myCall = function(context = window, ...args) {
const fn = Symbol('fn');
context[fn] = this;
const result = context[fn](...args);
delete context[fn];
return result;
};
applyapply
方法与 call
方法类似,它也允许你调用一个函数,并指定该函数内部的 this
值。不同的是,apply
方法接受一个数组作为参数,数组中的元素会依次传递给被调用的函数。
实现:
Function.prototype.myApply = function(context = window, args = []) {
const fn = Symbol('fn');
context[fn] = this;
const result = context[fn](...args);
delete context[fn];
return result;
};
bindbind
方法会创建一个新的函数,在调用时会将 this
值绑定到指定的对象上。它不会立即调用函数,而是返回一个新的函数。
实现:
Function.prototype.myBind = function(context = window, ...args) {
const self = this;
return function(...newArgs) {
return self.apply(context, [...args, ...newArgs]);
};
};
JavaScript原型,原型链?有什么特点?
查看答案
原型(Prototype)
在 JavaScript 里,每个对象都有一个内部属性 [[Prototype]]
(在浏览器环境中可以通过 __proto__
访问),这个属性指向该对象的原型对象。原型对象也是一个普通对象,它同样有自己的原型对象,以此类推,直到最顶层的原型对象 Object.prototype
,其 [[Prototype]]
为 null
。
原型的主要作用是实现对象之间的属性和方法共享。当访问一个对象的属性或方法时,JavaScript 首先会在该对象本身查找,如果找不到,就会沿着原型链到其原型对象中查找,直至找到或者到达原型链的末尾。
原型链(Prototype Chain)
原型链是由多个对象的原型对象依次连接形成的链条。当访问一个对象的属性或方法时,JavaScript 会先在对象本身查找,如果找不到,就会沿着原型链向上查找,直到找到该属性或方法,或者到达原型链的末尾(即 Object.prototype
的原型为 null
)。
原型和原型链的特点
- 属性共享:原型允许对象之间共享属性和方法,避免了代码的重复。
- 动态性:可以在运行时动态地向原型对象添加属性和方法,所有基于该原型的对象都能立即访问这些新添加的属性和方法。
- 继承:原型链实现了 JavaScript 的继承机制,通过原型链,对象可以继承其原型对象的属性和方法。
- 查找效率:属性查找沿着原型链进行,查找效率会受到原型链长度的影响。如果原型链过长,查找属性的性能会下降。
- 顶层原型:所有对象的原型链最终都会指向
Object.prototype
,其[[Prototype]]
为null
,这是原型链的终点。
- 顶层原型:所有对象的原型链最终都会指向
Javascript如何实现继承?
查看答案
在 JavaScript 中,有多种方式可以实现继承,下面为你详细介绍几种常见的继承方式:
1. 原型链继承
原型链继承是 JavaScript 中最基本的继承方式,它通过将子类的原型指向父类的实例来实现。
2. ES6 类继承
ES6 引入了 class
和 extends
关键字,提供了更简洁的继承语法。
Ajax原理和实现?
查看答案
Ajax(Asynchronous JavaScript and XML)即异步的 JavaScript 和 XML,它允许在不刷新整个页面的情况下,与服务器进行异步通信并更新部分网页。其核心原理是利用浏览器提供的 XMLHttpRequest
对象(在现代浏览器中也可以使用 fetch
API)来发送 HTTP
请求,从服务器获取数据,然后使用 JavaScript 动态更新网页内容。
具体步骤如下:
- 创建 XMLHttpRequest 对象:这是实现 Ajax 的基础,用于与服务器进行通信。
- 打开连接:使用 open() 方法指定请求的类型(如 GET、POST)、请求的 URL 以及是否异步。
- 发送请求:使用 send() 方法发送请求到服务器。
- 监听状态变化:通过监听 XMLHttpRequest 对象的 onreadystatechange 事件,当状态发生变化时,检查 readyState 和 status 属性,判断请求是否成功。
- 处理响应:如果请求成功,从 responseText 或 responseXML 属性中获取服务器返回的数据,并使用 JavaScript 更新网页内容。
Ajax 实现
使用 XMLHttpRequest 对象
// 创建 XMLHttpRequest 对象
const xhr = new XMLHttpRequest();
// 打开连接
xhr.open('GET', 'https://api.example.com/data', true);
// 监听状态变化
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
// 请求成功,处理响应数据
const responseData = JSON.parse(xhr.responseText);
console.log(responseData);
}
};
// 发送请求
xhr.send();
使用 fetch API
fetch('https://api.example.com/data')
.then(response => {
if (response.ok) {
return response.json();
}
throw new Error('Network response was not ok');
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Fetch error:', error);
});
总结
- XMLHttpRequest:是传统的实现 Ajax 的方式,兼容性好,但代码相对复杂。
- fetch API:是现代 JavaScript 提供的新 API,语法简洁,支持 Promise,但在旧浏览器中需要使用 polyfill。
Ajax、Fetch、Axios 有什么区别?
查看答案
Ajax
、Fetch
和 Axios
都是用于在浏览器或 Node.js 环境中进行网络请求的工具,但它们之间存在一些区别:
1. 定义和历史背景
Ajax
(Asynchronous JavaScript and XML):是一种在不刷新整个页面的情况下,与服务器进行异步通信并更新部分网页的技术。最初主要使用XMLHttpRequest
对象来实现,现在也可以使用其他方式。Fetch
API:是现代 JavaScript 提供的一个新的 API,用于替代XMLHttpRequest
,提供了更简洁、更强大的接口,基于Promise
实现。Axios
:是一个基于Promise
的 HTTP 客户端,用于浏览器和 Node.js。它是一个第三方库,需要引入使用。
2. 语法和使用方式
Ajax
(使用 XMLHttpRequest):
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
const responseData = JSON.parse(xhr.responseText);
console.log(responseData);
}
};
xhr.send();
Fetch
API:
fetch('https://api.example.com/data')
.then(response => {
if (response.ok) {
return response.json();
}
throw new Error('Network response was not ok');
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Fetch error:', error);
});
Axios
:
axios.get('https://api.example.com/data')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error('Axios error:', error);
});
3. 错误处理
Ajax
:需要手动检查readyState
和status
属性来判断请求是否成功,错误处理相对复杂。Fetch
API:只有在网络请求失败时才会reject
,即使服务器返回404
或500
状态码,Promise
仍然会resolve
,需要手动检查response.ok
属性。Axios
:在请求失败时(包括网络错误和服务器返回错误状态码),Promise
会reject
,错误处理更加方便。
兼容性
Ajax
(XMLHttpRequest):兼容性最好,几乎所有浏览器都支持。Fetch
API:现代浏览器基本支持,但在旧浏览器中需要使用polyfill
。Axios
:需要引入第三方库,但可以通过配置支持旧浏览器。
模块化开发怎么做?
查看答案
在 JavaScript 中,模块化开发可以通过不同的规范和方法来实现,下面为你介绍几种常见的方式:
- CommonJs:是 Node.js 中使用的模块化规范,使用
require
和module.exports
来导出和导入模块。 - AMD:是 RequireJS 中使用的模块化规范,使用
define
和require
来定义和导入模块。 - CMD:是 Sea.js 中使用的模块化规范,使用
define
和require
来定义和导入模块。 - ES6 Module:是 ECMAScript 6 中引入的模块化规范,使用
import
和export
来定义和导入模块。 - UMD:是 Universal Module Definition 的缩写,它可以兼容多种模块化规范。
异步加载JS的方式有哪些?
查看答案
在网页开发中,异步加载 JavaScript 文件可以提高页面的加载速度和性能,避免阻塞页面渲染。以下是几种常见的异步加载 JS 的方式:
使用
script
标签的async
属性async
属性用于异步加载脚本,脚本下载完成后会立即执行,不会按照脚本在 HTML 中的顺序执行。使用
script
标签的defer
属性defer
属性用于异步加载脚本,脚本会在文档解析完成后、DOMContentLoaded
事件触发前按照脚本在 HTML 中的顺序执行。动态创建
script
标签 通过 JavaScript 动态创建script
标签并添加到文档中,可以实现异步加载脚本。使用
import()
动态导入 ES6 模块 在现代 JavaScript 中,可以使用import()
函数动态导入 ES6 模块,实现异步加载。
那些操作会造成内存泄漏?
查看答案
在 JavaScript 中,以下几种常见操作可能会造成内存泄漏:
1. 意外的全局变量
在函数内部,如果没有使用 var
、let
或 const
声明变量,这个变量会成为全局变量。全局变量不会被垃圾回收机制回收,除非页面被关闭。
2. 定时器和回调函数未清除
如果使用 setInterval
或 setTimeout
创建了定时器,并且在不需要它们时没有清除,这些定时器会一直存在于内存中。
3. 闭包的滥用
闭包可以访问其外部函数的变量,如果闭包一直存在,其引用的外部变量也不会被垃圾回收。
4. DOM 元素引用未释放
如果在 JavaScript 中保留了对 DOM 元素的引用,即使这些元素已经从 DOM 树中移除,它们也不会被垃圾回收。
5. 事件监听器未移除
如果在 DOM 元素上添加了事件监听器,并且在元素被移除之前没有移除这些监听器,会导致内存泄漏。
XML和JSON的区别?
查看答案
XML(可扩展标记语言)和JSON(JavaScript对象表示法)都是用于数据交换的格式,它们在语法、用途、性能等方面存在一些区别,以下是详细对比:
1. 语法结构
- XML:使用标签来描述数据,标签需要成对出现,并且可以嵌套。XML 文档必须有一个根元素。
<book>
<title>JavaScript高级程序设计</title>
<author>Nicholas C. Zakas</author>
<year>2012</year>
</book>
- JSON:使用键值对的方式来描述数据,键和值之间用冒号分隔,多个键值对之间用逗号分隔。JSON 数据可以是对象(用花括号
{}
包裹)或数组(用方括号[]
包裹)。
{
"title": "JavaScript高级程序设计",
"author": "Nicholas C. Zakas",
"year": 2012
}
2. 数据类型支持
- XML:本身没有明确的数据类型,所有数据都被视为字符串。需要通过额外的规范(如 XML Schema 或 DTD)来定义数据类型。
- JSON:支持多种数据类型,包括字符串、数字、布尔值、对象、数组、null。
3. 可读性和可维护性
- XML:标签和嵌套结构使得 XML 文档具有较好的可读性,尤其是对于复杂的数据结构。但是,标签的使用会增加文档的冗余度,导致文件体积较大。
- JSON:简洁的语法使得 JSON 数据易于阅读和编写。JSON 的结构清晰,适合表示简单的数据结构。
4. 解析难度
- XML:解析 XML 需要使用专门的 XML 解析器,解析过程相对复杂,尤其是处理嵌套结构和命名空间时。
- JSON:JSON 可以直接被 JavaScript 解析,现代编程语言也都提供了内置的 JSON 解析器,解析过程简单高效。
5. 应用场景
- XML:常用于数据交换、配置文件、文档存储等领域。在企业级应用、Web 服务(如 SOAP)中广泛使用。
- JSON:由于其简洁性和易于解析的特点,JSON 成为了 Web 开发中最常用的数据交换格式,尤其是在 RESTful API 中。
6. 性能
- XML:由于标签的冗余和解析的复杂性,XML 在数据传输和解析时的性能相对较低。
- JSON:JSON 的简洁语法和高效解析使得它在性能上优于 XML。
IIFE是什么? 有什么用处?
查看答案
IIFE 即立即调用函数表达式(Immediately Invoked Function Expression),是一种在定义后立即执行的 JavaScript 函数。其语法结构通常有两种形式:
// 形式一
(function() {
// 函数体
})();
// 形式二
(function() {
// 函数体
}());
IIFE 的用处:
- 创建独立的作用域 在 JavaScript 中,函数会创建自己的作用域。使用 IIFE 可以避免全局作用域的污染,将变量和函数封装在一个独立的作用域中。
(function() {
let privateVariable = '这是一个私有变量';
console.log(privateVariable); // 可以在 IIFE 内部访问
})();
// 这里无法访问 privateVariable
// console.log(privateVariable); // 会报错
- 实现私有变量和方法 通过 IIFE,可以模拟面向对象编程中的私有变量和方法。外部代码无法直接访问 IIFE 内部定义的变量和函数。
const myModule = (function() {
let privateValue = 0;
function privateMethod() {
return privateValue;
}
return {
increment: function() {
privateValue++;
},
getValue: function() {
return privateMethod();
}
};
})();
myModule.increment();
console.log(myModule.getValue()); // 输出 1
- 避免变量提升带来的问题 在 JavaScript 中,变量和函数声明会被提升到当前作用域的顶部。使用 IIFE 可以避免变量提升带来的意外问题。
(function() {
let x = 10;
console.log(x); // 输出 10
})();
- 处理异步代码 在处理异步代码时,IIFE 可以用来创建独立的作用域,避免闭包带来的问题。
for (var i = 0; i < 5; i++) {
(function(index) {
setTimeout(function() {
console.log(index);
}, 1000 * index);
})(i);
}
null、undefined 和 未声明变量之间有什么区别?如何检查判断这些状态值?
查看答案
在 JavaScript 中,null、undefined 和未声明变量是不同的概念,下面详细介绍它们的区别以及检查判断的方法。
1. null
null 是一个原始值,表示一个空对象指针。通常用于手动表示某个变量不指向任何对象。
2. undefined
undefined 是一个原始值,表示一个未定义的变量。通常用于表示变量未被初始化或者函数没有返回值。
3. 未声明变量
未声明变量是指在使用之前没有使用 var
、let
或 const
声明的变量。在 JavaScript 中,未声明变量会自动成为全局变量。
检查判断方法
===
typeof
操作符Object.prototype.toString.call()
如何区分数组和对象?
查看答案
- 使用
Array.isArray()
方法
- 使用
- 使用
instanceof
运算符
- 使用
- 使用
Object.prototype.toString.call()
方法
- 使用
- 检查
length
属性
- 检查
列举三种强制类型转换和两种隐式类型转换?
查看答案
在 JavaScript 中,强制类型转换是指通过特定的函数或方法来明确地将一个数据类型转换为另一个数据类型,而隐式类型转换则是在某些操作中自动发生的类型转换。以下是三种强制类型转换和两种隐式类型转换的示例:
强制类型转换
- 使用
Number()
函数
- 使用
- 使用
String()
函数
- 使用
- 使用
Boolean()
函数
- 使用
隐式类型转换
- 加法运算符 +
- 比较运算符 <、>、<=、>=
JavaScript 中怎么获取当前日期的月份?
查看答案
在 JavaScript 里,可以借助 Date
对象来获取当前日期的月份。Date
对象提供了 getMonth()
方法,不过要注意,这个方法返回的月份是从 0 开始计数的,也就是 0 代表 1 月,11 代表 12 月。
// 创建一个 Date 对象,表示当前日期和时间
const currentDate = new Date();
// 获取当前日期的月份(从 0 开始计数)
const month = currentDate.getMonth();
// 为了得到实际的月份(1 - 12),需要加 1
const actualMonth = month + 1;
console.log(`当前月份是: ${actualMonth}`);
什么是类数组(伪数组),如何将其转化为真实的数组?
查看答案
类数组(伪数组)是一种类似数组的数据结构,它具有以下特点:
- 具有
length
属性,用于表示元素的个数。 - 可以通过索引访问元素,但不具备数组的方法和属性,例如
push
、pop
、map
等。
常见的类数组对象有函数内部的 arguments
对象、DOM 操作返回的 NodeList 和 HTMLCollection 等。
将类数组转化为真实数组的方法
- 使用
Array.from()
方法 - 使用扩展运算符
...
- 使用
Array.prototype.slice.call()
方法 - 使用
Array.prototype.concat.apply()
方法
如何遍历对象的属性?
查看答案
在 JavaScript 中,有多种方法可以遍历对象的属性,以下是几种常见的方式:
- 使用
for...in
循环 for...in 循环可以遍历对象的可枚举属性,包括对象自身的属性和继承的属性。
- 使用
const person = {
name: 'John',
age: 30,
city: 'New York'
};
for (let key in person) {
console.log(key + ': ' + person[key]);
}
- 使用
Object.keys()
方法Object.keys()
方法返回一个由对象的可枚举属性组成的数组,然后可以使用forEach()
方法遍历这个数组。
- 使用
const person = {
name: 'John',
age: 30,
city: 'New York'
};
const keys = Object.keys(person);
keys.forEach(key => {
console.log(key + ': ' + person[key]);
});
- 使用
Object.entries()
方法Object.entries()
方法返回一个由对象的可枚举属性的键值对组成的数组,然后可以使用forEach()
方法遍历这个数组。
- 使用
const person = {
name: 'John',
age: 30,
city: 'New York'
};
const entries = Object.entries(person);
entries.forEach(([key, value]) => {
console.log(key + ': ' + value);
});
- 使用
Object.getOwnPropertyNames()
方法Object.getOwnPropertyNames()
方法返回一个由对象的所有属性(包括不可枚举属性,但不包括 Symbol 类型的属性)组成的数组,然后可以使用forEach()
方法遍历这个数组。
- 使用
const person = {
name: 'John',
age: 30,
city: 'New York'
};
const propertyNames = Object.getOwnPropertyNames(person);
propertyNames.forEach(key => {
console.log(key + ': ' + person[key]);
});
src 和 href 的区别是?
查看答案
在 HTML 中,src
和 href
是两个常用的属性,它们的主要区别如下:
用途:
- src:src(source)属性主要用于嵌入外部资源,比如图片、脚本、视频等。当浏览器遇到带有 src 属性的标签时,会暂停当前文档的解析,去下载 src 指定的资源,下载完成后再继续解析文档。
- href:href(hypertext reference)属性用于建立当前文档与外部资源之间的关联,通常用于链接样式表、超链接等。浏览器在遇到 href 属性时,会并行下载资源,不会阻塞文档的解析。
加载方式
- src:当浏览器解析到
src
属性时,会将该资源嵌入到当前文档中。例如,当使用<img>
标签的src
属性加载图片时,图片会直接显示在页面上;使用<script>
标签的src
属性加载脚本时,脚本会被执行。 - href:
href
属性建立的是一种引用关系,不会将资源嵌入到当前文档中。例如,使用<link>
标签的href
属性链接样式表时,样式表会应用到当前文档,但不会改变文档的结构。
如何使用原生 JavaScript 给一个按钮绑定两个 onclick 事件?
查看答案
在原生 JavaScript 中,有两种主要的方式可以给一个按钮绑定多个 onclick
事件:
使用
addEventListener
方法addEventListener
方法允许你为元素添加多个事件监听器,这些监听器会按照添加的顺序依次执行。在
onclick
属性中调用多个函数 你也可以在onclick
属性中直接调用多个函数。
如何在 JavaScript 中比较两个对象?
查看答案
使用 == 或 === 对两个不同却具有相同属性及属性值的对象进行比较,他们的结果却不会相等。这是因为等号比较的是他们的引用(内存地址),而不是基本类型为了测试两个对象在结构上是否相等,需要一个辅助函数。 他将遍历每个对象的所有属性,然后测试他们是否具有相同的值,嵌套对象也需如此。当然,也可以使用参数来控制是否对原型链进行比较。
像数字和字符串这样的基本类型只需对比他们的值,当一个对象赋值给另一个新对象时,使用等号进行对比,他们就会相等。因为他们的引用(内存地址)是同一个。
1. 浅比较
浅比较只比较对象的第一层属性是否相等,不递归比较嵌套对象。
function shallowEqual(obj1, obj2) {
// 首先检查两个对象是否是同一个引用
if (obj1 === obj2) {
return true;
}
// 检查两个对象是否都是对象类型
if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
return false;
}
if (typeof obj1 === "function" && typeof obj2 === "function") {
return obj1.toString() === obj2.toString()
}
if (obj1 instanceof Date && obj2 instanceof Date) {
return obj1.getTime() === obj2.getTime()
}
// 获取两个对象的所有属性名
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
// 检查属性数量是否相同
if (keys1.length !== keys2.length) {
return false;
}
// 遍历属性名,比较属性值
for (let key of keys1) {
if (obj1[key]!== obj2[key]) {
return false;
}
}
return true;
}
const objA = { a: 1, b: 2 };
const objB = { a: 1, b: 2 };
const objC = { a: 1, b: 3 };
console.log(shallowEqual(objA, objB)); // true
console.log(shallowEqual(objA, objC)); // false
2. 深比较
深比较会递归比较对象的所有属性,包括嵌套对象。
function deepEqual(obj1, obj2) {
// 首先检查两个对象是否是同一个引用
if (obj1 === obj2) {
return true;
}
// 检查两个对象是否都是对象类型
if (typeof obj1!== 'object' || obj1 === null || typeof obj2!== 'object' || obj2 === null) {
return false;
}
if (typeof obj1 === "function" && typeof obj2 === "function") {
return obj1.toString() === obj2.toString()
}
if (obj1 instanceof Date && obj2 instanceof Date) {
return obj1.getTime() === obj2.getTime()
}
// 获取两个对象的所有属性名
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
// 检查属性数量是否相同
if (keys1.length!== keys2.length) {
return false;
}
// 遍历属性名,递归比较属性值
for (let key of keys1) {
if (!deepEqual(obj1[key], obj2[key])) {
return false;
}
}
return true;
}
const nestedObjA = { a: 1, b: { c: 2 } };
const nestedObjB = { a: 1, b: { c: 2 } };
const nestedObjC = { a: 1, b: { c: 3 } };
console.log(deepEqual(nestedObjA, nestedObjB)); // true
console.log(deepEqual(nestedObjA, nestedObjC)); // false
JavaScript 中的作用域、预解析与变量声明提升?
查看答案
在 JavaScript 里,作用域、预解析与变量声明提升是非常重要的概念,下面为你详细介绍:
作用域(Scope)
作用域定义了变量和函数的可访问范围,它决定了变量和函数的生命周期。在 JavaScript 中有两种主要的作用域:
全局作用域(Global Scope)
在代码的最外层定义的变量和函数拥有全局作用域,它们可以在代码的任何地方被访问。
函数作用域(Function Scope)
在函数内部定义的变量和函数只能在该函数内部被访问,外部无法访问。
块级作用域(Block Scope)
ES6 引入了块级作用域(Block Scope),使用 let
和 const
关键字声明的变量会被限制在块级作用域内,块级作用域由 {}
包裹。
如何延长作用域链?
执行环境的类型只有两种,全局和局部(函数)。但是有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。
具体来说就是执行这两个语句时,作用域链都会得到加强:
- 1、
try-catch
语句的catch
块;会创建一个新的变量对象,包含的是被抛出的错误对象的声明 - 2、
with
语句,with
语句会将指定的对象添加到作用域链中
预解析(Hoisting)
JavaScript 引擎在执行代码之前会进行预解析,它会将变量和函数的声明提升到当前作用域的顶部。这意味着你可以在变量和函数声明之前使用它们。
变量声明提升(Variable Hoisting)
使用 var
声明的变量会被提升到当前作用域的顶部,但赋值操作不会被提升。在变量声明之前访问变量,会得到 undefined
。
函数声明提升(Function Hoisting)
函数声明会被提升到当前作用域的顶部,因此你可以在函数声明之前调用函数。
函数提升的优先级大于变量提升的优先级,即函数提升在变量提升之上。
JavaScript 中什么情况下会返回 undefined 值?
查看答案
- 变量声明但未赋值
- 函数没有返回值
- 访问对象不存在的属性
- 函数参数未传递
- void 运算符
- 数组空位
数组方法有哪些? 区别是什么?
查看答案
== 和 === 的区别是什么?
查看答案
1. ==
(相等运算符)
==
运算符在比较时会进行类型转换,也被称为宽松相等。当比较的两个值类型不同时,JavaScript 会尝试将它们转换为相同的类型,然后再进行比较。
console.log(5 == '5'); // true,字符串 '5' 被转换为数字 5 后进行比较
console.log(null == undefined); // true,null 和 undefined 在使用 == 比较时被视为相等
2. ===
(严格相等运算符)
===
运算符在比较时不会进行类型转换,也被称为严格相等。只有当两个值的类型和值都完全相同时,才会返回 true
。
console.log(5 === '5'); // false,数字 5 和字符串 '5' 类型不同
console.log(5 === 5); // true,类型和值都相同
请解释关于 JavaScript 的同源策略?
查看答案
JavaScript 中的同源策略(Same-Origin Policy)是一种重要的安全机制,它限制了一个源(origin)的网页如何与另一个源的资源进行交互。以下是关于同源策略的详细解释:
什么是同源
如果两个 URL 的协议(protocol)、域名(domain)和端口号(port)都相同,那么这两个 URL 就被认为是同源的。
同源策略的作用
同源策略的主要目的是防止不同源的脚本对彼此的数据进行非法访问和操作,从而保护用户的信息安全。例如,它可以防止恶意网站通过 JavaScript 读取用户在其他网站上的敏感信息,如登录凭证、个人数据等。
同源策略的限制
- DOM 访问:不同源的页面之间无法直接访问彼此的 DOM 元素。例如,一个页面无法通过 JavaScript 直接获取另一个不同源页面的
document
对象。 - AJAX 请求:默认情况下,使用
XMLHttpRequest
或fetch
API 发起的 AJAX 请求只能访问同源的资源。如果需要访问不同源的资源,就会受到同源策略的限制,导致请求失败。 - Cookie、LocalStorage 和 IndexedDB 访问:不同源的页面无法直接访问彼此的
Cookie
、LocalStorage
和IndexedDB
数据。每个源都有自己独立的存储区域,其他源的脚本无法读取或修改这些数据。
如何解决跨域问题?
查看答案
在前端开发中,由于浏览器的同源策略,不同源的页面之间进行数据交互和资源共享会受到限制,这就是所谓的跨域问题。以下是几种常见的解决跨域问题的方法:
1. JSONP
JSONP 是一种古老的跨域解决方案,它的原理是利用 <script>
标签的 src
属性不受同源策略限制的特点。服务器返回的数据会被包裹在一个回调函数中,前端通过动态创建 <script>
标签来请求这个数据。
2. CORS
CORS 是现代浏览器支持的跨域解决方案,它通过服务器设置响应头来允许跨域请求。服务器可以设置 Access-Control-Allow-Origin
头来指定允许访问的源。
3. WebSocket
WebSocket 是一种双向通信协议,不受同源策略的限制。可以使用 WebSocket 来实现跨域通信。
let、var 和 const 创建变量有什么区别?
查看答案
在 JavaScript 里,let
、var
和 const
都能用来创建变量,不过它们之间存在显著差异,下面为你详细介绍:
1. 作用域
var
:使用var
声明的变量拥有函数作用域或者全局作用域。要是在函数外部声明,那它就是全局变量;若在函数内部声明,就是局部变量。let
和const
:使用let
和const
声明的变量具备块级作用域,也就是由{}
包裹的代码块。
2. 变量提升
var
:使用var
声明的变量会出现变量提升的情况,也就是变量的声明会被提升到当前作用域的顶部,不过赋值操作不会被提升。在变量声明之前访问变量,会得到undefined
。let
和const
:使用let
和const
声明的变量不会进行变量提升,在变量声明之前访问,会触发ReferenceError
错误。
3. 重复声明
var
:使用var
可以在同一作用域内对变量进行重复声明。let
和const
:使用let
和const
不允许在同一作用域内对变量进行重复声明。
4. 变量赋值
var
和let
:使用var
和let
声明的变量可以先声明,之后再赋值,也能在声明时直接赋值,并且可以对变量进行重新赋值。const
:使用const
声明常量时,必须在声明的同时进行赋值,而且一旦赋值之后,就不能再对其重新赋值。不过,如果const
声明的是对象或者数组,对象的属性或者数组的元素是可以修改的。
箭头函数和普通函数有什么区别?
查看答案
1. 语法
- 普通函数:使用
function
关键字定义,可以有函数名,也可以是匿名函数。 - 箭头函数:使用箭头
=>
定义,语法更加简洁。如果只有一个参数,可以省略括号;如果函数体只有一条语句,可以省略花括号和return
关键字。
2. this
值
- 普通函数:
this
的值取决于函数的调用方式。它可以是全局对象(在全局作用域中)、调用该函数的对象(作为方法调用时)、新创建的对象(作为构造函数调用时)等。 - 箭头函数:
this
的值继承自外层函数或全局作用域,它在定义时就确定了,不会随着调用方式的改变而改变。
3. arguments
对象
- 普通函数:函数内部有一个
arguments
对象,它是一个类数组对象,包含了函数调用时传递的所有参数。 - 箭头函数:没有自己的
arguments
对象,它会使用外层函数的arguments
对象。
4. 使用 new
关键字
- 普通函数:可以使用
new
关键字作为构造函数来创建对象实例。 - 箭头函数:不能使用
new
关键字,因为它没有自己的this
和prototype
,不能作为构造函数。
5. yield 关键字
- 普通函数:可以使用
yield
关键字,使其成为生成器函数。 - 箭头函数:不能使用
yield
关键字,因此不能成为生成器函数。
如何实现数组的随机排序?
查看答案
方法一:使用 sort 方法结合随机数
可以使用数组的 sort
方法,并结合 Math.random()
函数来生成随机数,从而实现数组的随机排序。
function shuffleArray(array) {
return array.sort(() => Math.random() - 0.5);
}
const originalArray = [1, 2, 3, 4, 5];
const shuffledArray = shuffleArray(originalArray);
console.log(shuffledArray);
方法二:使用 Fisher-Yates 洗牌算法
Fisher-Yates 洗牌算法是一种经典的随机排序算法,它的时间复杂度为 O(n),可以保证每个元素在每个位置出现的概率是相等的。
function shuffleArray(array) {
let currentIndex = array.length;
let temporaryValue, randomIndex;
// 当还有元素未被处理时
while (currentIndex!== 0) {
// 随机选择一个未处理的元素
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// 交换当前元素和随机选择的元素
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
const originalArray = [1, 2, 3, 4, 5];
const shuffledArray = shuffleArray(originalArray);
console.log(shuffledArray);
Function foo() {}和 var foo = function() {}之间 foo 的用法上的区别?
查看答案
区别
1、var foo =function (){}
这种方式是声明了个变量,而这个变量是个方法,变量在JavaScript中是可以改变的。
2、function foo(){}
这种方式是声明了个方法,foo这个名字无法改变
function b(){}
为函数声明,程序运行前就已存在, var a = function(){}
为函数表达式,是变量的声明,属于按顺序执行,所以 a 为 undefined
。
简述 Array.form 和 Array.of 的使用及区别?
查看答案
Array.from
的使用
Array.from()
方法用于将类数组对象或可迭代对象转换为真正的数组。类数组对象是指具有 length
属性和索引元素的对象,可迭代对象包括字符串、Set、Map 等。
Array.from(arrayLike[, mapFn[, thisArg]])
arrayLike
:必选参数,要转换为数组的类数组对象或可迭代对象。mapFn
:可选参数,一个映射函数,用于对每个元素进行处理。thisArg
:可选参数,执行mapFn
时的this
值。
// 将字符串转换为数组
const str = 'hello';
const arrFromStr = Array.from(str);
console.log(arrFromStr); // ['h', 'e', 'l', 'l', 'o']
// 将 Set 转换为数组
const set = new Set([1, 2, 3]);
const arrFromSet = Array.from(set);
console.log(arrFromSet); // [1, 2, 3]
// 使用映射函数
const numbers = [1, 2, 3];
const squaredNumbers = Array.from(numbers, x => x * x);
console.log(squaredNumbers); // [1, 4, 9]
Array.of
的使用
Array.of()
方法用于创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。
Array.of(element0[, element1[, ...[, elementN]]])
- element0, element1, ..., elementN:要创建数组的元素。
const arr1 = Array.of(1);
console.log(arr1); // [1]
const arr2 = Array.of(1, 2, 3);
console.log(arr2); // [1, 2, 3]
const arr3 = Array.of();
console.log(arr3); // []
区别
- 参数处理方式:
- Array.from 接收一个类数组对象或可迭代对象作为第一个参数,并可以选择性地接收一个映射函数。
- Array.of 接收任意数量的参数,并将这些参数作为数组的元素。
- 创建空数组的行为:
- Array.from 不会创建空数组,除非传入的类数组对象或可迭代对象为空。
- Array.of 可以创建空数组,当不传入任何参数时,返回一个空数组。
- 创建单元素数组的行为:
- Array 构造函数在传入单个数字参数时,会创建一个具有指定长度的空数组,而不是包含该数字的数组。
- Array.of 会将传入的单个参数作为数组的唯一元素。
根据你的理解,请简述 JavaScript 脚本的执行原理
查看答案
原理
JavaScript是一种动态、弱类型、基于原型的语言,通过浏览器可以直接执行当浏览器遇到 <script>
标记的时候,浏览器会执行之间的 JavaScript 代码。嵌入的 JavaScript 代码是顺序执行的,每个脚本定义的全局变量和函数,都可以被后面执行的脚本所调用。变量的调用,必须是前面已经声明,否则获取的变量值是 undefined
。
JavaScript 高级篇
解释一下什么是回调函数,并提供一个简单的例子?
查看答案
回调函数(Callback Function)是作为参数传递给另一个函数的函数,并且在那个函数内部被调用执行。回调函数的主要作用是在某个操作完成后执行特定的代码逻辑,常用于异步编程场景,比如处理网络请求、定时器等。
什么是内存泄漏?
查看答案
哪些操作会造成内存泄漏?
查看答案
在 JavaScript 里,内存泄漏指的是程序中一些不再使用的内存空间,由于某些原因无法被垃圾回收机制回收,从而导致这部分内存一直被占用,随着时间推移,可用内存逐渐减少,最终可能引发程序性能下降甚至崩溃。
常见的内存泄漏场景:
- 事件监听器未移除:当在 DOM 元素上添加事件监听器后,如果在元素被移除之前没有移除这些监听器,会导致内存泄漏。因为事件监听器会引用相关的函数和元素,使得这些对象无法被垃圾回收。
- 定时器未清除:使用 setInterval 或 setTimeout 创建定时器后,如果在不需要时没有清除这些定时器,定时器会持续占用内存。
- 闭包使用不当:闭包会引用其外部函数的变量,若闭包一直存在,这些被引用的变量就无法被垃圾回收。
- 全局变量的滥用:在全局作用域中声明的变量,除非页面关闭,否则不会被垃圾回收。如果在全局作用域中创建了大量不必要的变量,会导致内存占用过高。
JS 内存泄漏的解决方式?
为避免内存泄漏,开发者需要在合适的时机移除事件监听器、清除定时器、避免不必要的闭包和全局变量的使用。
JavaScript 实现冒泡排序。数据为 23、45、18、37、92、13、24?
查看答案
冒泡排序是一种简单的排序算法,它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
function bubbleSort(arr) {
const len = arr.length;
for (let i = 0; i < len; i++) {
for (let j = 0; j < len - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// 交换元素
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
const data = [23, 45, 18, 37, 92, 13, 24];
const sortedData = bubbleSort(data);
console.log(sortedData);
用 js 实现随机选取 10–100 之间的 10 个数字,存入一个数组并排序?
查看答案
已知数组 var stringArray = [“This”,“is”, “Baidu”,“Campus”]
,Alert 出 This is Baidu Campus
查看答案
已知有字符串 foo=”get-element-by-id”
,写一个 function 将其转化成驼峰表示法 getElementById
查看答案
有这样一个 URL: http://item.taobao.com/item.htm?a=1&b=2&c=&d=xxx&e
,请写一段 JS 程序提取 URL 中的各个 GET 参数(参数名和参数个数不确定),将其按 key-value
形式返回到一个 json 结构中,如 {a: "1", b: "2", c: "", d: "xxx", e: undefined}
查看答案
输出今天的日期,以 YYYY-MM-DD 的方式,比如今天是 2014 年 9 月 26 日,则输出 2014-09-26
查看答案
js 数组去重,能用几种方法实现?
查看答案
谈谈你对 Javascript 垃圾回收机制的理解?
查看答案
Class 和普通构造函数有何区别?
查看答案
什么是 js 事件循环 event loop?
查看答案
计算字符串字节数?
查看答案
Eval 是做什么的?为什么要求禁止使用?
查看答案
eval 函数的作用
在 JavaScript 中,eval()
函数会将传入的字符串作为 JavaScript 代码进行解析和执行。以下是一个简单的示例:
const code = 'const a = 1; const b = 2; console.log(a + b);';
eval(code); // 输出 3
为什么要求禁止使用 eval
虽然 eval()
函数提供了动态执行代码的能力,但它存在诸多严重的问题,因此在实际开发中通常不建议使用,主要原因如下:
- 安全风险
eval()
可以执行任意代码,如果传入的字符串来自用户输入或不可信的数据源,可能会导致代码注入攻击。攻击者可以通过构造恶意代码来执行非法操作,例如窃取用户信息、修改数据等。
- 安全风险
- 性能问题
eval()
函数需要动态解析和执行代码,这会带来额外的性能开销。每次调用eval()
时,JavaScript 引擎都需要重新解析代码,而不是像普通代码那样进行预编译和优化。
- 性能问题
- 代码可读性和可维护性差 使用
eval()
会使代码变得难以理解和调试。由于代码是动态执行的,很难确定具体执行的代码内容,也不利于代码的维护和重构。
- 代码可读性和可维护性差 使用
- 作用域问题
eval()
会影响当前作用域,它可以访问和修改当前作用域中的变量,这可能会导致意外的结果和难以调试的问题。
- 作用域问题
什么是防抖和节流?js 如何处理防抖和节流?
查看答案
防抖(Debounce)
防抖是指在一定时间内,只有最后一次触发事件才会执行相应的处理函数。如果在这个时间间隔内再次触发事件,那么计时会重新开始。常用于搜索框输入提示、窗口大小改变等场景,避免频繁触发事件导致性能问题。
function debounce(func, wait) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, wait);
};
}
// 使用示例
const debouncedFunc = debounce(myFunction, 300);
节流(Throttle)
节流是指在一定时间内,只执行一次事件处理函数。无论在这个时间间隔内触发多少次事件,都只会按照固定的时间间隔执行处理函数。常用于滚动加载、按钮点击等场景,限制事件的触发频率。
function throttle(func, delay) {
let timer = null;
return function() {
const context = this;
const args = arguments;
// 如果定时器不存在,执行函数并设置定时器
if (!timer) {
func.apply(context, args);
timer = setTimeout(() => {
timer = null;
}, delay);
}
};
}
// 使用示例
function scrollHandler() {
console.log('滚动事件触发');
}
const throttledScroll = throttle(scrollHandler, 500);
// 模拟滚动事件
window.addEventListener('scroll', throttledScroll);
浅拷贝和深拷贝的区别?
查看答案
在 JavaScript 中,浅拷贝和深拷贝是处理对象和数组复制时的两种不同方式,它们的主要区别在于复制的深度和对嵌套对象的处理。
浅拷贝(Shallow Copy)
浅拷贝是指只复制对象的第一层属性和属性值,对于嵌套的对象,只复制引用,而不复制嵌套对象本身。
const original = { a: 1, b: { c: 2 } };
const shallowCopy = { ...original };
shallowCopy.b.c = 3;
console.log(original.b.c); // 输出 3,因为浅拷贝共享引用类型属性
深拷贝(Deep Copy)
深拷贝是指不仅复制对象的第一层属性和属性值,还会递归复制嵌套对象的属性和属性值。
function deepCopy(obj) {
if (typeof obj!== 'object' || obj === null) {
return obj;
}
let copy;
if (Array.isArray(obj)) {
copy = [];
for (let i = 0; i < obj.length; i++) {
copy[i] = deepCopy(obj[i]);
}
} else {
copy = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopy(obj[key]);
}
}
}
return copy;
}
const original = { a: 1, b: { c: 2 } };
const deepCopyObj = deepCopy(original);
deepCopyObj.b.c = 3;
console.log(original.b.c); // 输出 2,因为深拷贝对象是独立的
什么是进程、什么是线程、它们之间是什么关系?
查看答案
在操作系统中,进程和线程是两个非常重要的概念,下面为你详细解释它们的定义以及它们之间的关系。
进程(Process)
进程是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位。简单来说,进程就是正在运行的程序。每个进程都有自己独立的内存空间、系统资源(如文件描述符、打开的网络连接等)和执行上下文。进程之间相互独立,一个进程的崩溃通常不会影响其他进程。
例如,当你打开一个浏览器时,操作系统会为浏览器程序创建一个进程,这个进程会拥有自己的内存、CPU 时间片等资源,用于执行浏览器的各种功能。
线程(Thread)
线程是进程中的一个执行单元,是 CPU 调度和分派的基本单位。一个进程可以包含多个线程,这些线程共享进程的内存空间和系统资源,但每个线程都有自己独立的执行栈和程序计数器,用于记录线程的执行状态。
线程可以并发执行,从而提高程序的执行效率。例如,在浏览器进程中,可以有一个线程负责渲染页面,另一个线程负责处理用户的输入事件,还有一个线程负责网络请求等。
进程和线程的关系
- 包含关系:一个进程可以包含多个线程,这些线程在进程的上下文中并发执行。进程是线程的容器,为线程提供了执行的环境和资源。
- 资源共享:同一进程内的所有线程共享该进程的内存空间、系统资源(如文件描述符、全局变量等),但每个线程都有自己独立的执行栈和程序计数器。
- 调度和执行:进程是系统进行资源分配的基本单位,而线程是 CPU 调度和分派的基本单位。操作系统会为每个进程分配一定的资源,而线程则在这些资源的基础上进行执行。
- 独立性:进程之间相互独立,一个进程的崩溃通常不会影响其他进程;而同一进程内的线程之间相互依赖,如果一个线程崩溃,可能会导致整个进程崩溃。
什么是任务队列?
查看答案
在 JavaScript 中,任务队列(Task Queue)是实现异步编程的一个重要概念,它和 JavaScript 的事件循环(Event Loop)机制紧密相关。由于 JavaScript 是单线程的,一次只能执行一个任务,为了处理异步操作,就引入了任务队列。
任务队列的分类
在 JavaScript 里,任务队列主要分为两种:
- 宏任务队列(Macrotask Queue):常见的宏任务有 setTimeout、setInterval、setImmediate(Node.js 环境)、I/O 操作、UI rendering 等。
- 微任务队列(Microtask Queue):常见的微任务有 Promise.then、MutationObserver、process.nextTick(Node.js 环境)等。
工作原理
JavaScript 的执行流程遵循以下步骤:
- 执行栈(Call Stack):JavaScript 引擎首先会执行全局代码,将同步任务依次压入执行栈中执行。
- 异步任务处理:当遇到异步任务时,比如 setTimeout 或者 Promise,这些任务不会立即执行,而是会交给浏览器的 Web API 处理。
- 任务入队:当异步任务完成后,对应的回调函数会被放入相应的任务队列中。宏任务的回调函数放入宏任务队列,微任务的回调函数放入微任务队列。
- 事件循环(Event Loop):事件循环不断检查执行栈是否为空。如果执行栈为空,事件循环会先从微任务队列中取出所有任务依次执行,直到微任务队列为空。然后从宏任务队列中取出一个任务放入执行栈执行,执行完后再检查微任务队列,如此循环。
栈和队列的区别?
查看答案
在计算机科学中,栈(Stack)和队列(Queue)是两种重要的数据结构,它们在操作方式、应用场景等方面存在明显的区别,以下是详细介绍:
操作特性
栈(Stack): 遵循后进先出(Last In First Out,LIFO)的原则。就像一摞盘子,最后放上去的盘子总是最先被拿走。栈的主要操作有入栈(push)和出栈(pop),入栈是将元素添加到栈顶,出栈是从栈顶移除元素。
队列(Queue): 遵循先进先出(First In First Out,FIFO)的原则。类似于排队,先到的人先接受服务。队列的主要操作有入队(enqueue)和出队(dequeue),入队是将元素添加到队列的尾部,出队是从队列的头部移除元素。
操作位置
- 栈:所有的操作都在栈顶进行,只允许在栈顶插入和删除元素。
- 队列:插入操作在队列的尾部进行,删除操作在队列的头部进行。
应用场景
- 栈:常用于实现递归调用、表达式求值、浏览器的后退功能、函数调用栈等。例如,在递归函数调用时,每次调用都会将当前的上下文信息压入栈中,当递归返回时,再从栈中弹出相应的信息。
- 队列:常用于任务调度、消息队列、广度优先搜索(BFS)等场景。例如,在操作系统中,任务调度器会将待执行的任务放入队列中,按照先进先出的顺序依次执行。
代码示例
以下是使用 JavaScript 实现栈和队列的简单示例:
// 栈的实现
class Stack {
constructor() {
this.items = [];
}
// 入栈
push(element) {
this.items.push(element);
}
// 出栈
pop() {
if (this.isEmpty()) {
return null;
}
return this.items.pop();
}
// 获取栈顶元素
peek() {
if (this.isEmpty()) {
return null;
}
return this.items[this.items.length - 1];
}
// 判断栈是否为空
isEmpty() {
return this.items.length === 0;
}
// 获取栈的大小
size() {
return this.items.length;
}
}
// 队列的实现
class Queue {
constructor() {
this.items = [];
}
// 入队
enqueue(element) {
this.items.push(element);
}
// 出队
dequeue() {
if (this.isEmpty()) {
return null;
}
return this.items.shift();
}
// 获取队首元素
front() {
if (this.isEmpty()) {
return null;
}
return this.items[0];
}
// 判断队列是否为空
isEmpty() {
return this.items.length === 0;
}
// 获取队列的大小
size() {
return this.items.length;
}
}
// 使用示例
const stack = new Stack();
stack.push(1);
stack.push(2);
console.log(stack.pop()); // 输出: 2
const queue = new Queue();
queue.enqueue(1);
queue.enqueue(2);
console.log(queue.dequeue()); // 输出: 1
栈和堆的区别?
查看答案
在计算机科学里,栈(Stack)和堆(Heap)是内存管理中两个关键的概念,它们在内存分配、访问速度、数据存储方式等方面存在显著差异,以下是详细介绍:
内存分配方式
- 栈:由操作系统自动分配和释放。当进入一个函数时,系统会为该函数的局部变量、参数等在栈上分配内存;函数执行结束后,这些内存会自动被释放。栈的内存分配和释放是连续的,遵循后进先出(LIFO)的原则。
- 堆:需要程序员手动分配和释放(在某些高级语言中,如 Java、Python 等,有垃圾回收机制来自动管理堆内存)。程序运行时,通过特定的函数(如 C 语言中的 malloc()、free())来在堆上申请和释放内存。堆的内存分配是不连续的,操作系统会根据程序的需求动态地分配内存块。
访问速度
- 栈:由于栈的内存分配和释放是连续的,访问速度相对较快。栈上的变量可以直接通过指针访问,无需进行间接寻址。
- 堆:堆的内存分配是不连续的,访问速度相对较慢。需要通过指针和偏移量来访问堆上的变量。
数据存储方式
- 栈:主要存储局部变量、函数调用的上下文信息(如返回地址、寄存器值等)。栈上的数据通常是临时的,随着函数的执行结束而被销毁。
- 堆:主要存储动态分配的数据,如对象、数组等。堆上的数据可以在程序的不同部分共享和访问,其生命周期由程序员控制(或由垃圾回收机制管理)。
内存空间大小
- 栈:内存空间相对较小,通常由操作系统预先分配一个固定大小的栈空间。如果程序在栈上分配的内存超过了这个限制,会导致栈溢出错误。
- 堆:内存空间相对较大,可以根据程序的需求动态地分配和释放内存。但是,如果程序频繁地在堆上分配和释放内存,可能会导致内存碎片的问题,影响内存的使用效率。
常见 HTTP 请求方法区别
查看答案
在 HTTP 协议里,GET
、POST
、PUT
、DELETE
属于常见的请求方法,它们各自有不同的用途,下面为你详细介绍它们的区别以及其他常见的 HTTP 请求方法。
常见 HTTP 请求方法区别
GET
- 用途:主要用于从服务器获取资源。比如访问网页、获取 API 数据等。
- 特点:请求参数会附加在
URL
后面,以查询字符串的形式呈现,所以不适合传递敏感信息和大量数据。GET
请求通常是幂等的,即多次执行相同的请求,结果不会改变。
POST
- 用途:一般用于向服务器提交数据,像表单提交、创建新资源等。
- 特点:请求参数会放在请求体中,适合传递大量数据和敏感信息。
POST
请求不是幂等的,多次执行相同的请求可能会产生不同的结果。
PUT
- 用途:主要用于更新服务器上的资源,若资源不存在则创建新资源。
- 特点:请求参数放在请求体中,
PUT
请求是幂等的,多次执行相同的请求,结果相同。
DELETE
- 用途:用于删除服务器上的资源。
- 特点:通常不需要请求体,
DELETE
请求是幂等的,多次执行相同的请求,结果相同。
其他常见 HTTP 请求方法
HEAD
- 用途:和 GET 方法类似,但服务器只返回响应头,不返回响应体。常用于获取资源的元信息,如资源的大小、修改时间等。
PATCH
- 用途:用于对资源进行部分更新,和 PUT 方法不同,PATCH 只更新资源的部分属性。
OPTIONS
- 用途:用于获取服务器支持的请求方法和其他相关信息,常用于跨域请求的预检请求。
Promise 是什么? 如何实现?
查看答案
在 JavaScript 里,Promise 是一种处理异步操作的对象。它代表一个尚未完成且预计在未来完成的操作结果。Promise 有三种状态:
- 待定(Pending):初始状态,尚未完成或被拒绝。
- 已完成(Fulfilled):操作成功完成,结果是一个值。
- 已拒绝(Rejected):操作失败,原因是一个错误。 Promise 有两个关键方法:
then(onFulfilled, onRejected)
:用于注册回调函数,当 Promise 的状态变为已完成或已拒绝时,会调用相应的回调函数。catch(onRejected)
:用于注册一个错误处理函数,当 Promise 的状态变为已拒绝时,会调用该函数。
取消操作
Promise 不支持取消操作,因为 Promise 一旦创建就会立即执行,无法中途取消。如果需要取消操作,可以使用 AbortController 来实现。
链式调用
Promise 的 then
方法返回一个新的 Promise 对象,这使得 Promise 可以进行链式调用,每个 then
方法都返回一个新的 Promise 对象,从而实现了 Promise 的链式调用。
错误处理
Promise 的 catch
方法用于处理 Promise 被拒绝的情况,当 Promise 的状态变为已拒绝时,会调用 catch
方法中的回调函数。
静态方法
Promise 提供了一些静态方法,如 Promise.all
、Promise.race
、Promise.resolve
、Promise.reject
等,用于处理多个 Promise 的并发操作。
错误捕获
Promise 提供了 catch
方法来捕获 Promise 被拒绝的情况,当 Promise 的状态变为已拒绝时,会调用 catch
方法中的回调函数。
如何实现 Promise
下面是一个简单的 Promise 实现示例:
class MyPromise {
constructor(executor) {
// 初始状态为 pending
this.status = 'pending';
this.value = undefined;
this.reason = undefined;
// 存储成功和失败的回调
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status === 'pending') {
this.status = 'fulfilled';
this.value = value;
// 执行所有成功回调
this.onFulfilledCallbacks.forEach(callback => callback());
}
};
const reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected';
this.reason = reason;
// 执行所有失败回调
this.onRejectedCallbacks.forEach(callback => callback());
}
};
try {
// 执行 executor 函数
executor(resolve, reject);
} catch (error) {
// 捕获异常并调用 reject
reject(error);
}
}
then(onFulfilled, onRejected) {
// 处理 onFulfilled 和 onRejected 为可选参数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : error => { throw error; };
return new MyPromise((resolve, reject) => {
const handleFulfilled = () => {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch (error) {
reject(error);
}
};
const handleRejected = () => {
try {
const result = onRejected(this.reason);
resolve(result);
} catch (error) {
reject(error);
}
};
if (this.status === 'fulfilled') {
setTimeout(handleFulfilled, 0);
} else if (this.status === 'rejected') {
setTimeout(handleRejected, 0);
} else {
// 存储回调
this.onFulfilledCallbacks.push(() => setTimeout(handleFulfilled, 0));
this.onRejectedCallbacks.push(() => setTimeout(handleRejected, 0));
}
});
}
catch(onRejected) {
return this.then(null, onRejected);
}
static resolve(value) {
return new MyPromise((resolve) => resolve(value));
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
}
// 使用示例
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('Success');
}, 1000);
});
promise.then(value => {
console.log(value);
}).catch(error => {
console.error(error);
});
ES6
ES5和ES6的区别,说几个ES6的新增方法?
查看答案
ES6的继承和ES5的继承有什么区别?
查看答案
var、let、const之间的区别?
查看答案
Class、extends是什么,有什么作用?
查看答案
module、export、import有什么作用?
查看答案
举例ES6对String字符串类型做的常用升级优化?
查看答案
举例ES6对Array数组类型做的常用升级优化?
查看答案
举例ES6对Number数字类型做的常用升级优化?
查看答案
举例ES6对Function函数类型做的常用升级优化?
查看答案
举例ES6对Object类型做的常用升级优化?
查看答案
使用箭头函数应注意什么/箭头函数和普通函数的区别?
查看答案
ES6的模板字符串有哪些新特性?并实现一个类模板字符串的功能?
查看答案
介绍下Set、Map的区别?
查看答案
setTimeout、Promise、Async/Await 的区别?
查看答案
Promise有几种状态,什么时候会进入catch?
查看答案
ES6 怎么写Class ,为何会出现Class?
查看答案
Promise构造函数是同步执行还是异步执行,那么then 方法呢?
查看答案
Promise只有成功和失败2个状态,怎么让一个函数无论成功还是失败都能被调用?
查看答案
ES6如何转化为ES5,为什么要转化?
查看答案
日常前端代码开发中,有哪些值得用ES6去改进的编程优化或者规范?
查看答案
ES6 和 node 的 commonjs 模块化规范的区别?
查看答案
Promise 中 reject 和 catch 处理上有什么区别?
查看答案
理解 async/await 以及对 Generator 的优势
查看答案
手写一个Promise
查看答案
Promise如何封装一个Ajax?
查看答案
下面的输出结果是多少?
查看答案
以下代码依次输出的内容是?
查看答案
分析下列程序代码,得出运行结果,解释其原因?
查看答案
使用结构赋值,实现两个变量的值的交换
查看答案
设计一个对象,键名的类型至少包含一个symbol类型,并且实现遍历所有key
查看答案
下面Set结构,打印出的size值是多少?
查看答案
使用class 手写一个Promise
查看答案
说一下ES6的导入导出模块?
查看答案
WebAPI
什么是 dom?
查看答案
什么是dom
DOM(Document Object Model)即文档对象模型,是 HTML 和 XML 文档的编程接口。它将网页文档表示为一个树形结构,树中的每个节点都是一个对象,这些对象可以被 JavaScript 等脚本语言访问和操作。通过 DOM,开发者可以动态地改变文档的内容、结构和样式,实现网页的交互效果。例如,我们可以使用 JavaScript 来修改 HTML 元素的文本内容、属性,或者添加、删除元素等。
简单来说,DOM 提供了一种方式,让程序和脚本能够动态地访问和更新文档的内容、结构和样式。
dom 是哪种基本的数据结构?
查看答案
DOM是一种树形结构的数据结构
dom 操作的常用 api 有哪些?
查看答案
dom操作的常用 api 有以下几种:
获取dom节点
- document.getElementById('div1')
- document.getElementsByTagName('div')
- document.getElementsByClassName('div')
- document.querySelector('p')
- document.querySelectorAll('p')
property(js对象的property)
var p = document.getElementByTagName('p')[0]
console.log(p.nodeName);//nodeName是p的property,即nodeName是p的属性
attribute
- p.getAttribute('data-name');
- p.setAttribute('data-name','imooc');
dom 节点的 Attribute 和 Property 有何区别?
查看答案
1. 什么是Property?
每个 DOM
节点都是一个 object
对象,它可以像其他的 js Object
一样具有自己的,property
和 method
,所以 property
的值可以是任何数据类型,大小写敏感,原则上,property
应该仅供 js 操作,不会出现在 html 中(默认属性除外:id/src/href/className/dir/title/lang
等),和其他 js object
一样,自定义的 property
也会出现在 object
的 for…in
遍历中。
2. 什么是Attribute?
attribute
出现在 dom
中,js提供了 getAttribute/setAttribute
等方法来获取和改变它的值,attribute 的值只能是字符串且大小写不敏感,最后作用于html中,可以影响 innerHTML
获取的值。可以通过访问 dom
节点的 attributes
属性来获取改节点的所有的 attribute
。(在 IE<9
中, attribute
获取和改变的实际上是 property
。)
3、两者之间的区别是
- 自定义的
Property
与Attribute
不同步, 不相等。 - 非自定义的
DOM property
与attributes
是有条件同步的。 - 非自定义的属性(
id/src/href/name/value
等),通过setAttribute
修改其特性值可以同步作用到property
上,而通过property
修改属性值有的(value
)时候不会同步到attribute
上,即不会反应到html
上(除以下几种情况,非自定义属性在二者之间是同步的)。
dom 结构操作怎样添加、移除、移动、复制、创建和查找节点?
查看答案
1、创建新节点
createDocumentFragment()
: 创建一个DOM片段createElement()
: 创建一个具体的元素createTextnode()
: 创建一个文本节点
2、添加、移除、替换、插入
appendChild()
: 作为子节点添加到某个父节点的子节点列表末尾insertBefore()
: 插入到某个父节点的某个子节点之前removeChild()
: 移除某个父节点下的某个具体的子节点replaceChild()
: 替换某个父节点下的某个具体的子节点
3、查找
getElementById()
: 通过元素的id获取元素对象getElementsByTagName()
: 通过标签名称获取元素对象集合getElementsByClassName()
: 通过class名称获取元素对象集合getElementsByName()
: 通过元素的name属性值获取元素对象集合querySelector()
: 返回匹配指定CSS选择器的第一个元素querySelectorAll()
: 返回匹配指定CSS选择器的所有元素
dom 事件的级别?
查看答案
DOM 级别一共可以分为四个级别:DOM0
级、DOM1
级、DOM2
级和 DOM3
级。
而 DOM 事件分为3个级别:DOM 0
级事件处理,DOM 2
级事件处理和DOM 3
级事件处理。由于DOM 1
级中没有事件的相关内容,所以没有DOM 1
级事件
1、DOM0级事件处理
element.οnclick=function(){}
2、DOM2级事件处理
element.addEventListener(event,function,useCapture)
: // 默认是false。false:冒泡阶段执行,true:捕获阶段产生。
3、DOM3级事件处理
element.addEventListener(‘keyup’, function(){}, false)
事件类型增加了很多,鼠标事件、键盘事件、UI事件,当用户与页面上的元素交互时触发,如:load
、scroll
, 焦点事件,当元素获得或失去焦点时触发,如:blur
、focus
, 鼠标事件,当用户通过鼠标在页面执行操作时触发,如:click
、dbclick
、mouseover
、mouseout
, 滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel
, 文本事件,当在文档中输入文本时触发,如:textInput
, 键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydown
、keypress
, 合成事件,当为IME(输入法编辑器)输入字符时触发,如:compositionstart
, 变动事件,当底层DOM结构发生变化时触发,如:DOMsubtreeModified
, 同时DOM3级事件也允许使用者自定义一些事件。
dom 事件模型?
查看答案
DOM 事件模型定义了事件在页面元素间的传播方式,主要有两种:事件捕获和事件冒泡。
事件捕获
事件捕获从文档根元素开始,由外向内依次触发元素的同类型事件,直至目标元素。例如点击事件,触发顺序为:根元素 -> 目标元素的祖先元素 -> 目标元素的父元素 -> 目标元素。
事件冒泡
事件冒泡与事件捕获相反,事件从目标元素开始,由内向外传播,直至根元素。点击事件触发顺序为:目标元素 -> 目标元素的父元素 -> 父元素的父元素 -> 根元素。
优先级
事件传播时,先进行事件捕获,再进行事件冒泡。
dom 事件流?
查看答案
DOM 事件流描述了事件在页面元素间的传播顺序,包含三个阶段:事件捕获、目标阶段和事件冒泡。
事件捕获阶段
事件传播由目标节点的祖先节点逐级传播到目标节点。先由文档的根节点 document(window)开始触发对象,最后传播到目标节点,从外向内捕获事件对象
处于目标阶段
事件到达目标对象,事件触发,如果事件不允许冒泡,事件会在这一阶段停止传播
事件冒泡阶段
从目标节点逐级传播到 document 节点
什么是事件冒泡,它是如何工作的?如何阻止事件冒泡?
查看答案
事件冒泡定义
事件冒泡是 DOM 事件模型中的一种事件传播方式,事件从目标元素开始,由内向外传播,直至根元素。
工作原理
以点击事件为例,触发顺序为:目标元素 -> 目标元素的父元素 -> 父元素的父元素 -> 根元素。比如页面上有一个按钮嵌套在一个 <div>
里,点击按钮时,事件先在按钮上触发,接着传播到 <div>
元素,再继续向外传播,直到文档根元素。
阻止事件冒泡
event.stopPropagation()
return false
event.preventDefault()
JavaScript 动画和 CSS3 动画有什么区别?
查看答案
JavaScript 动画和 CSS3 动画在多个方面存在区别,以下是详细对比:
性能表现
- JavaScript 动画:JavaScript 动画由 JavaScript 代码控制,需要不断修改 DOM 元素的样式属性,这会频繁触发重排(reflow)和重绘(repaint),性能开销较大,尤其是在复杂动画或大量元素的场景下,可能导致页面卡顿。
- CSS3 动画:CSS3 动画由浏览器的渲染引擎直接处理,利用 GPU 加速,性能较高。浏览器可以对 CSS3 动画进行优化,减少重排和重绘的次数,因此在性能上通常优于 JavaScript 动画。
代码复杂度
- JavaScript 动画:实现复杂动画时,JavaScript 代码通常较为复杂,需要编写大量的逻辑来控制动画的开始、暂停、结束、循环等状态,以及处理动画的时间和速度。
- CSS3 动画:CSS3 动画的代码相对简洁,只需要定义关键帧和动画属性,就可以实现各种动画效果,无需编写复杂的逻辑代码。
动画控制
- JavaScript 动画:JavaScript 动画具有更高的灵活性,可以根据用户的交互、数据变化等动态控制动画的行为,例如暂停、继续、改变动画的参数等。
- CSS3 动画:CSS3 动画的控制相对有限,通常只能通过添加或移除 CSS 类来启动或停止动画,难以实现复杂的动态控制。
兼容性
- JavaScript 动画:JavaScript 动画的兼容性较好,几乎可以在所有浏览器中运行,不受浏览器版本的限制。
- CSS3 动画:CSS3 动画的兼容性相对较差,一些旧版本的浏览器可能不支持某些 CSS3 动画属性,需要使用前缀或降级处理。
应用场景
- JavaScript 动画:适用于需要复杂交互和动态控制的动画场景,例如游戏开发、数据可视化、用户交互特效等。
- CSS3 动画:适用于简单的过渡效果、状态变化动画、页面加载动画等场景,例如按钮的悬停效果、菜单的展开和收缩等。
event 对象的常见应用?
查看答案
- 1、
event.preventDefault();
// 阻止默认行为,阻止a链接默认的跳转行为 - 2、
event.stopPropagation();
// 阻止冒泡 - 3、
event.stopImmediatePropagation();
// 按钮绑定了2个响应函数,依次注册a, b两个事件,点击按钮,a事件中加event.stopImmediatePropagation()
就能阻止 b 事件 - 4、
event.currentTarget
// 早期的ie不支持,当前绑定的事件event.target
自定义事件/ 模拟事件?
查看答案
1、给一个按钮自己增加一个事件,在其他地方触发,而不是用回调的方式触发
var ev = document.getElementById('ev');
var eve = new Event('custome'); // eve:事件对象
ev.addEventListener('custome', function(){
console.log('custome');
});
ev.dispatchEvent(eve);
2、customeEvent
通用事件绑定/ 编写一个通用的事件监听函数?
查看答案
function bindEvent(element, type, selector, fn) {
if(fn == null) {
fn = selector;
selector = null;
}
element.addEventListener(type, function(e) {
var target;
if(selector) {
target = e.target;
if(target.matches(selector)) {
fn.call(target, e);
}
} else {
fn(e);
}
});
}
// 使用代理
var div1 = document.getElementById('div1');
bindEvent(div1, 'click', 'a', function(e) {
console.log(this.innerHTML);
});
// 不使用代理
var a = document.getElementById('a1');
bindEvent(a, 'click', function(e) {
console.log(a.innerHTML);
});
代理的好处
- 1、代码简洁
- 2、减少浏览器内存占用
事件冒泡
事件冒泡的应用:代理
dom 和 bom 的区别?
查看答案
bom
- BOM是Browser Object Model的缩写,即浏览器对象模型。
- BOM没有相关标准。
- BOM的最根本对象是window
dom
- DOM是Document Object Model的缩写,即文档对象模型。
- DOM是W3C的标准。
- DOM最根本对象是document(实际上是window.document)
事件三要素?
查看答案
- 事件源:触发事件的对象,比如网页上的按钮、链接、输入框等元素。例如点击一个按钮,这个按钮就是事件源。
- 事件:表示具体的动作,如常见的点击(click)、鼠标滑过(mouseover)、键盘按键按下(keydown)等。
- 事件处理函数:当事件发生时要执行的代码块,它定义了事件触发后要做什么。比如点击按钮后弹出提示框,弹出提示框的代码就是事件处理函数。
事件执行过程?
查看答案
- 事件捕获过程:当我们点击TEXT时,首先是window -> document -> body -> div -> text 这个过程称为事件捕获,W3C浏览器的标准执行流程。
- 事件冒泡过程:text -> div -> body -> document -> window.这个过程称为事件冒泡。IE浏览器只支持冒泡,不支持捕获。W3C浏览器先执行捕获,后执行冒泡
获取元素位置?
查看答案
使用 getBoundingClientRect() 方法
getBoundingClientRect()
方法会返回一个 DOMRect
对象,该对象包含了元素相对于视口的位置和尺寸信息。
使用 offsetTop
和 offsetLeft
属性
offsetTop
和 offsetLeft
属性分别表示元素相对于其 offsetParent
元素的顶部和左侧偏移量。
使用 pageXOffset
和 pageYOffset
结合 getBoundingClientRect()
如果你需要获取元素相对于文档的位置,可以结合使用 pageXOffset
和 pageYOffset
属性。
封装运动函数?
查看答案
下面为你封装一个通用的运动函数,它可以实现元素的缓动动画,支持改变元素的 left、top、width、height 等样式属性。
/**
* 封装的运动函数,实现元素的缓动动画
* @param {HTMLElement} element - 需要进行动画的元素
* @param {Object} target - 目标样式属性和值的对象,例如 {left: 200, top: 300}
* @param {number} duration - 动画持续时间,单位为毫秒
*/
function animate(element, target, duration) {
// 记录动画开始时间
const startTime = Date.now();
// 获取元素当前样式
const currentStyle = {};
for (const prop in target) {
currentStyle[prop] = parseFloat(getComputedStyle(element)[prop]);
}
// 定义动画帧函数
function step() {
// 计算已经过去的时间
const elapsedTime = Date.now() - startTime;
// 如果已经超过了指定的持续时间
if (elapsedTime >= duration) {
// 直接将元素样式设置为目标值
for (const prop in target) {
element.style[prop] = target[prop] + 'px';
}
return;
}
// 计算当前进度
const progress = elapsedTime / duration;
// 根据进度更新元素样式
for (const prop in target) {
const startValue = currentStyle[prop];
const endValue = target[prop];
const currentValue = startValue + (endValue - startValue) * progress;
element.style[prop] = currentValue + 'px';
}
// 请求下一帧动画
requestAnimationFrame(step);
}
// 开始动画
requestAnimationFrame(step);
}
// 使用示例
const box = document.getElementById('box');
animate(box, { left: 200, top: 300 }, 1000);
这个 animate
函数接收三个参数:要进行动画的元素、目标样式属性和值的对象,以及动画持续时间。函数内部使用 requestAnimationFrame
来实现平滑的动画效果,通过计算动画进度来逐步更新元素的样式。你可以根据需要修改目标样式和持续时间来实现不同的动画效果。
绑定事件和解除事件的区别?
查看答案
1、事件绑定
定义:一个事件可以加多次,且不会覆盖。
绑定方法:
- attachEvent ('on+事件名',函数名)这个只兼容ie 6-8
- addEventListener ('事件名',函数名,false)
绑定事件的封装:
function addEvent(element, type, fn) {
if(element.addEventListener) {
element.addEventListener(type, fn);
} else {
element.attachEvent('on' + type, fn);
}
}
2、事件解除
解除方法:
- detachEvent ('on+事件名',函数名)这个只兼容ie 6-8
- removeEventListener ('事件名',函数名,false)
解除事件的封装:
function removeEvent(element, type, fn) {
if(element.removeEventListener) {
element.removeEventListener(type, fn);
} else {
element.detachEvent('on' + type, fn);
}
}
谈谈事件委托的理解?
查看答案
事件委托,也叫事件代理,是一种利用事件冒泡原理实现的事件处理技巧。它允许将事件监听器绑定到一个父元素上,而非每个子元素,当子元素上的事件触发时,事件会冒泡到父元素,由父元素上的监听器统一处理。
原理
事件冒泡机制是事件委托的基础。当一个元素上的事件被触发时,该事件会从这个元素开始,依次向上传播到它的父元素、祖父元素,直至文档根元素。
优点
- 代码简洁:无需为每个子元素单独绑定事件监听器,减少代码量。
- 减少内存占用:只在父元素上绑定一个监听器,而非为大量子元素分别绑定,降低内存开销。
- 动态添加元素时无需重新绑定:新添加的子元素也能自动享受事件委托带来的事件处理。
function bindEvent(element, type, selector, fn) {
if(fn == null) {
fn = selector;
selector = null;
}
element.addEventListener(type, function(e) {
var target;
if(selector) {
target = e.target;
if(target.matches(selector)) {
fn.call(target, e);
}
} else {
fn(e);
}
});
}
// 使用代理
var div1 = document.getElementById('div1');
bindEvent(div1, 'click', 'a', function(e) {
console.log(this.innerHTML);
});
缺点
- 事件委托基于冒泡,对于不冒泡的事件不支持
- 层级过多,冒泡过程中,可能会被某层阻止掉。 3、理论上委托会导致浏览器频繁调用处理函数,虽然很可能不需要处理。所以建议就近委托,比如在
table
上代理td
,而不是在document
上代理td
。 4、把所有事件都用代理就可能会出现事件误判。比如,在document
中代理了所有button
的click
事件,另外的人在引用改js时,可能不知道,造成单击button
触发了两个click
事件
应用场景
适用于有大量子元素需要处理相同事件的场景,如列表项点击、表格单元格点击等。
JavaScript 中的定时器有哪些?他们的区别及用法是什么?
查看答案
JavaScript中的定时器有以下几种:
setTimeout()
方法用于在指定的毫秒数后调用函数或计算表达式。setInterval()
方法可按照指定的周期(以毫秒计)来调用函数或计算表达式。
setInterval()
方法会不停地调用函数,直到 clearInterval()
被调用或窗口被关闭。由 setInterval()
返回的ID 值可用作 clearInterval()
方法的参数。
比较 attachEvent 和 addEventListener?
查看答案
attachEvent
和 addEventListener
都是用于绑定事件的方法,但它们在浏览器兼容性、参数和事件传播机制等方面存在差异。
1. 浏览器兼容性
attachEvent
:是 Internet Explorer 6 - 8 特有的方法,现代浏览器(如 Chrome、Firefox、Safari 等)不支持该方法。addEventListener
:是 W3C 标准的事件绑定方法,被现代浏览器广泛支持,IE9 及以上版本也支持该方法。
2. 参数差异
attachEvent
:接收两个参数,第一个参数是事件名称,需要加上on
前缀(如onclick
);第二个参数是事件处理函数。
element.attachEvent('onclick', function() {
console.log('点击事件触发');
});
addEventListener
:接收三个参数,第一个参数是事件名称,不需要加on
前缀(如click
);第二个参数是事件处理函数;第三个参数是一个布尔值,用于指定事件是在捕获阶段触发(true
)还是冒泡阶段触发(false
),默认值为false
。
element.addEventListener('click', function() {
console.log('点击事件触发');
}, false);
3. 事件传播机制
attachEvent
:只支持事件冒泡,不支持事件捕获。addEventListener
:既支持事件冒泡,也支持事件捕获,通过第三个参数来控制。
this 指向
attachEvent
:事件处理函数中的this
指向window
对象。addEventListener
:事件处理函数中的this
指向绑定事件的元素。
document.write 和 innerHTML 的区别?
查看答案
document.write
和 innerHTML
都是在网页中插入内容的方法,但它们有以下区别:
1. 执行时机
document.write
:在文档加载过程中调用时,会直接将内容写入文档流。若在文档加载完成后调用,会覆盖整个文档内容。innerHTML
:可在文档加载的任何阶段使用,用于修改指定元素的 HTML 内容。
2. 作用范围
document.write
:作用于整个文档,直接向文档流写入内容。innerHTML
:作用于指定的 DOM 元素,可精确控制内容插入位置。
3. 性能影响
- document.write:在文档加载完成后使用会导致整个文档重新渲染,性能开销大。
- innerHTML:只影响指定元素及其子元素的渲染,性能相对较好。
4. 安全性
- document.write:若写入的内容包含用户输入,可能存在 XSS 攻击风险。
- innerHTML:同样存在 XSS 风险,但可通过适当的输入过滤来降低风险。
什么是 window 对象?什么是 document 对象?
查看答案
window
对象和 document
对象是 Web 开发中非常重要的两个对象,下面为你详细介绍它们的概念和用途。
什么是 window 对象?
window
对象是浏览器的全局对象,代表浏览器窗口。在浏览器中,所有的全局变量和函数实际上都是 window
对象的属性和函数。例如,当你定义一个全局变量 var a = 1;
,实际上是在 window
对象上定义了一个属性 window.a = 1;
。
主要用途:
- 全局变量和函数的容器:所有全局定义的变量和函数都可以通过 window 对象访问。
- 浏览器窗口控制:可以使用 window 对象的方法来控制浏览器窗口的大小、位置、打开和关闭等。例如,window.open() 可以打开一个新的浏览器窗口,window.resizeTo() 可以调整窗口的大小。
- 定时器管理:window 对象提供了 setTimeout() 和 setInterval() 方法来创建定时器,用于在指定的时间后执行代码或周期性地执行代码。
- 事件处理:可以使用 window 对象来监听和处理浏览器窗口的事件,例如 window.onload 事件在页面加载完成后触发。
什么是 document 对象?
document
对象是 window
对象的一个属性,代表当前加载的 HTML 文档。它提供了对文档内容、结构和样式的访问和操作接口。通过 document
对象,你可以获取和修改 HTML 元素、文本内容、属性等。
主要用途:
- 访问和修改 HTML 元素:可以使用 document 对象的方法来获取 HTML 元素,例如
document.getElementById()
、document.getElementsByTagName()
、document.getElementsByClassName()
等。然后可以对这些元素进行修改,例如修改元素的文本内容、属性、样式等。 - 创建和插入新元素:可以使用 document 对象的方法来创建新的 HTML 元素,例如
document.createElement()
、document.createTextNode()
等。然后可以将这些新元素插入到文档中,例如使用appendChild()
、insertBefore()
等方法。 - 事件处理:可以使用 document 对象来监听和处理文档中的事件,例如
document.onclick
事件在文档中的任何位置点击时0触发。
总结
window
对象是浏览器的全局对象,代表浏览器窗口,提供了对浏览器窗口的控制和全局变量、函数的管理。document
对象是window
对象的一个属性,代表当前加载的 HTML 文档,提供了对文档内容、结构和样式的访问和操作接口。
Js 拖动的原理?(必会)
查看答案
js的拖拽效果主要用到以下三个事件:
- mousedown 鼠标按下事件
- mousemove 鼠标移动事件
- mouseup 鼠标抬起事件
描述浏览器的渲染过程,DOM 树和渲染树的区别?
查看答案
1、浏览器的渲染过程:
- 解析HTML构建DOM(DOM树),并行请求css/image/js
- CSS 文件下载完成,开始构建CSSOM(CSS树)
- CSSOM 构建结束后,和DOM 一起生成Render Tree(渲染树)
- 布局(Layout):计算出每个节点在屏幕中的位置
- 显示(Painting):通过显卡把页面画到屏幕上
2、DOM树和渲染树的区别:
- DOM树与HTML标签一一对应,包括head和隐藏元素
- 渲染树不包括head和隐藏元素,大段文本的每一个行都是独立节点,每一个节点都有对应的css属性
dom 树和 render 树之间的关系?
查看答案
- 1、dom树,css树合并成成渲染树(render树)
- 2、DOM树与HTML标签一一对应,包括head和隐藏元素
- 3、渲染树不包括 head 和隐藏元素,大段文本的每一个行都是独立节点,每一个节点都有对应的 css 属性
获取到页面中所有的 CheckBox 怎么做(不适用第三方框架)?
查看答案
在不使用第三方框架的情况下,可以使用原生 JavaScript 来获取页面中所有的 CheckBox。可以通过 document.querySelectorAll
方法或者 document.getElementsByTagName
方法来实现。
使用 document.querySelectorAll
方法
// 使用 querySelectorAll 方法选择所有 type 为 checkbox 的元素
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
// 遍历所有的 checkbox 元素
checkboxes.forEach(function(checkbox) {
console.log(checkbox);
});
使用 document.getElementsByTagName
方法
// 获取所有的 input 元素
const inputs = document.getElementsByTagName('input');
const checkboxes = [];
// 遍历所有的 input 元素,筛选出 type 为 checkbox 的元素
for (let i = 0; i < inputs.length; i++) {
if (inputs[i].type === 'checkbox') {
checkboxes.push(inputs[i]);
}
}
// 遍历所有的 checkbox 元素
checkboxes.forEach(function(checkbox) {
console.log(checkbox);
});
简单说一下页面重绘和回流?
查看答案
回流(Reflow)
- 定义:当 DOM 的变化影响了元素的布局信息(元素的宽高、边距、位置、大小尺寸、边距等几何信息),浏览器需要重新计算元素在视口内的布局信息,将其安放到界面中的正确位置,这个过程叫做回流。
- 触发场景:添加或删除可见的 DOM 元素;元素的尺寸改变(边距、填充、边框、宽度和高度等);内容改变,比如文本改变或图片被另一个不同尺寸的图片所替代;浏览器窗口尺寸改变等。
- 影响:回流是一个比较昂贵的操作,因为浏览器需要重新计算布局,可能会导致性能问题,尤其是在频繁触发的情况下。
重绘(Repaint)
- 定义:当一个元素的外观发生改变,但没有影响到布局信息,浏览器会将新样式赋予给元素并重新绘制它的外观,这个过程叫做重绘。
- 触发场景:元素的外观改变,如颜色、背景色、透明度等。
- 影响:重绘的开销相对回流较小,但如果过于频繁,也会影响性能。
两者关系
回流一定会触发重绘,而重绘不一定会触发回流。为了优化性能,应尽量减少回流和重绘的次数。
display: none 和 visibility: hidden 是否会导致页面的重绘和回流?
display: none
:当设置元素的 display 属性为 none 时,元素会被完全从文档流中移除,不会占用任何空间。因此,当元素被隐藏时,会触发回流和重绘。visibility: hidden
:当设置元素的 visibility 属性为 hidden 时,元素仍然会占据空间,但不会显示在页面上。因此,当元素被隐藏时,仅触发重绘。
如何最小化页面的重绘和回流?
查看答案
- 需要要对元素进行复杂的操作时,可以先隐藏(display:"none"),操作完成后再显示
- 需要创建多个DOM节点时,使用 DocumentFragment 创建完后一次性的加入document
- 缓存 Layout 属性值,如:
var left = elem.offsetLeft;
这样,多次使用 left 只产生一次回流 - 尽量避免用 table 布局(table 元素一旦触发回流就会导致 table 里所有的其它元素回流)
- 避免使用 css表达式(expression),因为每次调用都会重新计算值(包括加载页面)
- 尽量使用 css 属性简写,如:用
border
代替border-width
,border-style
,border-color
- 批量修改元素样式:
elem.className
和elem.style.cssText
代替elem.style.xxx
Js 延迟加载的方式有哪些?
查看答案
在 JavaScript 中,有多种方式可以实现脚本的延迟加载,以下是几种常见的方法:
- 使用
defer
属性
- 使用
- 使用
async
属性
- 使用
- 动态创建
script
标签
- 动态创建
- 使用
window.onload
事件
- 使用
- 使用
DOMContentLoaded
事件
- 使用
- 使用
setTimeout
延迟方法
- 使用
IE 与 标准事件模型有哪些差别?
查看答案
IE(Internet Explorer)旧版本(IE6 - IE8)的事件模型与标准的 W3C 事件模型存在一些差别,以下是详细对比:
1. 事件绑定方法
- IE 事件模型:使用 attachEvent 方法来绑定事件。该方法接收两个参数,事件名称需要加上 on 前缀,例如 onclick。
- 标准事件模型:使用
addEventListener
方法来绑定事件。该方法接收三个参数,事件名称不需要加 on 前缀,第三个参数是一个布尔值,用于指定事件是在捕获阶段触发(true)还是冒泡阶段触发(false),默认值为 false。
2. 事件传播机制
- IE 事件模型:只支持事件冒泡,不支持事件捕获。事件从目标元素开始,依次向上传播到它的父元素、祖父元素,直至文档根元素。
- 标准事件模型:既支持事件冒泡,也支持事件捕获。事件传播分为三个阶段:事件捕获阶段、目标阶段和事件冒泡阶段。可以通过
addEventListener
的第三个参数来控制事件在捕获阶段还是冒泡阶段触发。
3. 事件对象获取方式
- IE 事件模型:事件对象存储在全局的
window.event
对象中。在事件处理函数中,可以通过window.event
来访问事件对象。 - 标准事件模型:事件对象作为参数传递给事件处理函数。在事件处理函数中,可以直接使用该参数来访问事件对象。
4. this 指向
- IE 事件模型:事件处理函数中的
this
指向window
对象。 - 标准事件模型:事件处理函数中的
this
指向绑定事件的元素。
5. 事件解绑方法
- IE 事件模型:使用
detachEvent
方法来解绑事件。该方法接收两个参数,事件名称需要加上on
前缀。 - 标准事件模型:使用
removeEventListener
方法来解绑事件。该方法接收三个参数,事件名称不需要加on
前缀,第三个参数需要与绑定事件时的参数一致。