[JS]经典面试题-基础篇
什么是DOM?
DOM是文档对象模型, 将整个页面映射为一个由多层节点构成的DOM树,通过DOM树开发者可以访问和操作网页上的内容。
DOM 的基本组成:
DOM 的基本组成单位是节点, 常用的节点类型有:
- 元素节点(Element Node):对应 HTML 或 XML 文档中的标签,如 <div>、<p> 等。
- 属性节点(Attribute Node):元素的属性,如 <img src="example.jpg"> 中的 src。
- 文本节点(Text Node):元素内的文本内容。
DOM 的主要操作:
1. 添加节点
- 使用appendChild(newNode)方法将新节点添加到父节点的子节点列表的末尾。
- 使用insertBefore(newNode, referenceNode)方法在指定参考节点之前插入新节点。
var newNode = document.createElement("div");
newNode.textContent = "新节点";
var parent = document.getElementById("parentId");
parent.appendChild(newNode);
2. 移除节点
- 使用removeChild(node)方法从父节点中移除指定的子节点。:
var nodeToRemove = document.getElementById("nodeId");
var parent = nodeToRemove.parentNode;
parent.removeChild(nodeToRemove);
3. 移动节点
- 可以通过先移除节点,然后将其添加到新位置来实现。
var nodeToMove = document.getElementById("nodeId");
var oldParent = nodeToMove.parentNode;
var newParent = document.getElementById("newParentId");
oldParent.removeChild(nodeToMove);
newParent.appendChild(nodeToMove);
4. 复制节点
- 使用cloneNode(deep)方法,其中deep参数为true时表示深拷贝(复制节点及其所有子节点),为false时表示浅拷贝(仅复制节点本身)。
var originalNode = document.getElementById("nodeId");
var clonedNode = originalNode.cloneNode(true);
// 然后可以将clonedNode添加到DOM中
5. 创建节点
- 使用createElement(tagName)方法创建一个新的元素节点。
- 使用createTextNode(text)方法创建一个新的文本节点。
var newElement = document.createElement("div");
newElement.textContent = "新创建的元素";
// 然后可以将newElement添加到DOM中
6. 查找节点
- 使用getElementById(id)方法通过ID查找节点。
- 使用getElementsByName(name)方法通过名称查找节点(返回节点集合)。
- 使用getElementsByTagName(tagName)方法通过标签名查找节点(返回节点集合)。
- 使用getElementsByClassName(className)方法通过类名查找节点(返回节点集合)。
- 使用querySelector(selector)和querySelectorAll(selector)方法通过CSS选择器查找节点。
var nodeById = document.getElementById("nodeId");
var nodesByClass = document.getElementsByClassName("someClass");
var firstNodeBySelector = document.querySelector(".someClass");
var allNodesBySelector = document.querySelectorAll(".someClass");
什么是 Bom?
BOM是浏览器对象模型,BOM的核心是window对象。
- window对象:(浏览器窗口)
- 代表了浏览器窗口,提供了很多方法和属性,如打开新窗口、关闭窗口、设置窗口大小、获取窗口位置等。
- location对象: (url信息)
- 表示当前窗口中加载的文档的URL,提供了获取和设置URL的方法和属性。
- ocation.href-- 获取或设置整个URL ->统一资源定位符 ->指定资源的位置
- location.search -> 返回查询字符串 ->包括?和?之后的内容
- location.pathname --返回url中的路径部分 -> /home.html?uname=pink
- history对象:(历史记录)
- history.go() --> 前进和后退页面 --> history.go(num)
- history.back() --> 返回上一页
- history.forward() --> 返回下一页
- navigator对象:(浏览器信息)
- navigator.userAgent (音:ruai真特) --> 获取浏览器信息 (浏览器名称、版本、操作系统等)
- screen对象:(屏幕信息)
- 包含了有关用户屏幕的信息,如屏幕分辨率、颜色深度等。
JS 的各种位置,比如 clientHeight,scrollHeight,offsetHeight ,以及 scrollTop, offsetTop,clientTop 的区别?
(offset/位置信息)(client/元素信息)(scroll/滚动)
offsetHeight:表示可视区域的高度,包含了 border 和滚动条
offsetTop: 返回该元素相对于带有定位的父元素的的距离
scrollHeight:表示了所有区域的高度,包含了滚动被隐藏的部分。
scrollTop:滚动后被隐藏的高度,
1. clientHeight, clientWidth
- 属性:element.clientHeight 和 element.clientWidth
- 描述:返回元素的内部高度和宽度,包括内边距,不包括滚动条、边框、外边距。
- 用途:常用于计算元素的可视区域大小。
2. clientTop, clientLeft
- 属性:element.clientTop 和 element.clientLeft
- 描述:返回元素边框的宽度。如果元素没有边框,这些值将是0
- 用途:用于获取元素的边框宽度,
3. scrollHeight, scrollWidth
- 属性:element.scrollHeight 和 element.scrollWidth
- 描述:这两个属性分别表示元素的总高度和总宽度,包括因溢出而不可见的部分。即,如果元素的内容超出了其指定的尺寸,scrollHeight和scrollWidth会反映整个内容的大小,而不仅仅是可见部分。
- 用途:常用于判断元素内容是否超出了其可视区域。
3. offsetHeight, offsetWidth
- 属性:element.offsetHeight 和 element.offsetWidth
- 描述:这两个属性分别表示元素的总高度和总宽度,包括元素的内边距(padding)、边框(border)、滚动条(如果可见的话)和外边距(margin)的顶部和左侧部分(注意:不包括margin的底部和右侧部分,因为它们是透明的,不占用空间)。但是,对于offsetWidth和offsetHeight来说,实际上并不包括margin,因为margin是透明的,不属于元素的实际尺寸。
- 用途:常用于计算元素占用的总空间大小(包括边框和内边距)。
4. scrollTop, scrollLeft
- 属性:element.scrollTop 和 element.scrollLeft
- 描述:这两个属性分别表示元素内容垂直和水平滚动的距离。当元素的内容可以滚动时,这两个属性表示内容已经被滚动了多少像素。
- 用途:常用于获取或设置元素的滚动位置。
5. offsetTop, offsetLeft
- 属性:element.offsetTop 和 element.offsetLeft
- 描述:这两个属性表示元素的左上角相对于其offsetParent元素的左上角的位置。offsetParent是最近的经过CSS定位(即CSS的position属性不是static的元素)的祖先元素。
- 用途:常用于计算元素在文档中的绝对位置。
let var const
1. var
- 作用域:
var
声明变量的作⽤域限制在声明位置的上下⽂中,如果声明再函数内, 就具有函数作用域, 如果声明在函数外部, 就具有全局作用域 - 提升:使用
var
声明的变量会被提升到其作用域的顶部,但这仅影响变量的声明,而不影响变量的初始化。因此,你可以在声明之前访问var
变量,但会得到undefined
值。 - 重复声明:在同一个作用域内,重复声明同一个变量,但后面的声明会覆盖前面的声明(如果初始化值不同,则更新变量的值)。
2. let
- 作用域:
let
关键字声明的变量具有块级作用域,这意味着变量仅在声明它的代码块(如{}
内)中有效。 - 提升:
let
声明的变量在本质上也会被提升,但与var
不同,let
声明的变量在声明之前存在“暂时死区”,在这个区域内无法访问变量, 所以形式上可以理解为不存在提升。 - 重复声明:在同一个块级作用域内,不能使用
let
重复声明同一个变量。
3. const
- 作用域:与
let
相同,const
声明的变量也具有块级作用域。 - 提升:与
let
类似,const
声明的变量本质上会提升,形式上没有提升 - 不可变:一旦使用
const
声明变量并赋值, 这个变量的值就不能被修改。如果是复杂数据类型,可以修改对象值,但是不能修改对象的引用地址 - 重复声明:在同一个块级作用域内,不能使用
const
重复声明同一个变量。
暂停死区
在代码块内,使用 let、const 命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”
谈谈变量提升?
一、定义
变量提升是指在码执行前,变量和函数的声明会被移动到它们所在作用域的顶部, 这个过程只提升声明, 不提升复制
二、作用范围
- 变量声明:
-
- 使用
var
关键字声明的变量会在代码执行前被提升到其作用域的顶部,但不会立即赋值,默认值为undefined
。 - 使用
let
和const
声明的变量不存在变量提升,它们在声明之前不可访问,这被称为暂时性死区。
- 使用
- 函数声明:
-
- 使用
function
关键字声明的函数,其声明和定义都会被提升到作用域的顶部,可以在声明之前被调用。
- 使用
三、示例
变量提升示例
console.log(a); // 输出: undefined
var a = "Hello";
变量a
的声明被提升到了作用域顶部,但赋值并不会提升
函数提升示例
func(); // 输出: Function executed
function func() { console.log("Function executed");
}
函数func
的声明和定义都被提升到了作用域顶部,注意匿名函数不存在函数提升。
JS 数组和对象的遍历⽅式
数组的遍历方式
- for循环
let arr = [1, 2, 3, 4, 5];
for (let i = 0; i < arr.length; i++) { console.log(arr[i]);
}
优点:兼容性好,可以控制遍历的开始和结束条件。
- forEach
arr.forEach(function(item, index, array) { console.log(item);
});
优点:代码简洁,可以直接操作数组中的每个元素。
- for...of循环
for (let item of arr) { console.log(item);
}
优点:支持遍历类数组对象和所有的可迭代对象。
对象的遍历方式
- for...in循环
let obj = {a: 1, b: 2, c: 3};
for (let key in obj) { if (obj.hasOwnProperty(key)) { console.log(key, obj[key]); }
}
优点:可以遍历对象的所有可枚举属性(包括其原型链上的属性,除非使用hasOwnProperty方法过滤)。
- Object.keys()
Object.keys(obj).forEach(function(key) { console.log(key, obj[key]);
});
优点:只返回对象自身的所有可枚举属性键的数组,不包括原型链上的属性。
- Object.values() 和 Object.entries()
Object.values(obj).forEach(function(value) { console.log(value);
}); Object.entries(obj).forEach(function([key, value]) { console.log(key, value);
});
优点:分别返回对象所有可枚举属性的 值数组 和 键值对数组,可以直接用于遍历。
map与forEach的区别
map与forEach在JavaScript中都是常用的遍历数组的方法, 以下是它们之间主要的区别:
1. 返回值
- map:map方法返回一个新数组。
- forEach:forEach方法返回undefined。
2. 对原数组的影响
- map:map方法不会改变原数组。
- forEach:forEach方法会改变原数组
3. 链式调用
- map:由于map方法返回一个新数组,因此可以支持链式调用
- forEach:由于forEach方法没有返回值,因此不支持链式调用。
4. 提前退出
- map方法和forEach方法都不支持中断循环, 只能使用try-catch结合throw语句, 通过抛出异常来模拟中断操作
javascript创建对象的⼏种⽅式
在JavaScript中,创建对象有多种方式。这些方式提供了灵活性和不同的使用场景。以下是几种常见的创建对象的方式:
- 对象字面量:
这是最直接和常用的创建对象的方式。使用花括号{}
包裹键值对来定义对象的属性和方法。
var person = { firstName: "John", lastName: "Doe", age: 30, greet: function() { console.log("Hello, my name is " + this.firstName + " " + this.lastName); }
};
- 构造函数:
构造函数是一种特殊的函数,用于初始化新创建的对象。使用new
关键字调用构造函数时,会创建一个新对象,并将这个新对象的内部[[Prototype]]
(即__proto__
)链接到构造函数的prototype
对象上,然后调用构造函数,this
被绑定到新创建的对象上。
function Person(firstName, lastName, age) { this.firstName = firstName; this.lastName = lastName; this.age = age; this.greet = function() { console.log("Hello, my name is " + this.firstName + " " + this.lastName); };
}
var person = new Person("John", "Doe", 30);
- 工厂函数:
工厂函数是一种返回对象的函数。它并不使用new
关键字,而是直接返回一个新创建的对象。这种方式相比构造函数来说,缺少了构造函数提供的一些特性,如instanceof
检查和new.target
属性。
function createPerson(firstName, lastName, age) { var obj = new Object("I am an Object"); obj.firstName = firstName; obj.lastName = lastName; obj.age = age; obj.greet = function() { console.log("Hello, my name is " + this.firstName + " " + this.lastName); }; return obj;
}
var person = createPerson("John", "Doe", 30);
- Object.create():
Object.create()
方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
。
var personProto = { greet: function() { console.log("Hello, my name is " + this.firstName + " " + this.lastName); }
};
var person = Object.create(personProto);
person.firstName = "John";
person.lastName = "Doe";
person.age = 30;
- 类(ES6+):
ES6引入了class
关键字,作为基于原型的继承的语法糖。class
可以看作是一个特殊的函数,它允许你使用更接近于传统面向对象编程的语法来定义对象的属性和方法。
class Person { constructor(firstName, lastName, age) { this.firstName = firstName; this.lastName = lastName; this.age = age; } greet() { console.log("Hello, my name is " + this.firstName + " " + this.lastName); }
}
var person = new Person("John", "Doe", 30);
介绍js有哪些内置对象
JavaScript中包含了多个内置对象,这些对象提供了丰富的功能和方法,用于处理各种数据类型和执行各种操作。以下是一些常见的JavaScript内置对象:
- Object:
- 是JavaScript中所有对象的父对象,包括数组、函数等都继承自Object对象。
- Array:
- 用于创建和操作数组的对象,数组是一组按顺序排列的值的集合。
- 提供了许多方法来操作数组,如
push()
用于添加元素到数组的末尾,pop()
用于删除并返回数组的最后一个元素,indexOf()
用于查找特定元素在数组中的位置等。
- String:
- 用于表示和操作字符串的对象,字符串是文本数据的序列。
- 提供了许多方法来操作字符串,如
charAt()
用于获取特定位置的字符,indexOf()
用于查找特定字符串的位置,toUpperCase()
用于将字符串转换为大写等。
- Number:
- 用于表示数字的对象,包括整数和浮点数。
- 虽然基本类型的数字在JavaScript中不是对象,但Number对象提供了一些静态方法,如
Number.parseInt()
和Number.parseFloat()
用于解析字符串为数字。
- Boolean:
- 用于表示布尔值(true或false)的对象。
- 同样地,基本类型的布尔值在JavaScript中不是对象,但Boolean对象提供了一些静态属性和方法。
- Function:
- 用于创建函数的构造函数,函数是可重复使用的代码块。
- 在JavaScript中,函数也是对象,可以像其他对象一样被传递、赋值等。
- Date:
- 用于表示日期和时间的对象,可以进行日期和时间的操作和计算。
- 如获取当前日期和时间、格式化日期时间、进行日期计算等。
- Math:
- 包含数学常量和函数的对象,用于进行数学计算。
- 提供了许多数学计算相关的方法,如
Math.random()
用于生成随机数,Math.floor()
用于向下取整等。
- RegExp:
- 用于处理正则表达式的对象,可以进行字符串的模式匹配和替换操作。
- 提供了一系列的函数和方法来匹配、查找和替换字符串中的模式。
- Error:
- 用于表示错误对象的构造函数,用于抛出和处理异常。
- 可以通过Error对象及其子类(如
SyntaxError
、TypeError
等)来创建和抛出错误。
- JSON:
- 用于解析和序列化JSON数据的对象,可以将数据转换为JSON格式或从JSON格式解析数据。
- 提供了
JSON.stringify()
和JSON.parse()
两个静态方法来分别实现序列化和反序列化。
- Map、Set:
- Map对象用于保存键值对,其中键和值可以是任意类型。
- Set对象用于保存唯一的键。
- Promise对象
Promise
对象用于处理异步操作。
谈⼀谈你理解的函数式编程
函数式编程(Functional Programming)是一种编程范式,通过函数的组合实现程序的功能
核心特性
- 函数是第一等公民:在函数式编程中,函数不仅可以被调用,还可以像其他值(如整数、字符串)一样被传递、组合和操作。
- 纯函数:函数式编程中的函数通常是纯函数,即给定相同的输入,总会产生相同的输出,且不会改变外部状态或产生副作用。
优点
- 代码可读性强:大量使用函数,使得程序更加简洁和易于理解。
- 可维护性高:每个函数都是独立的单元,易于维护。
- 适合处理大规模数据:函数式编程的不可变性质使得并行处理大规模数据变得更加容易和安全。
- 易于并发编程:由于函数没有副作用,因此可以避免并发编程中常见的死锁和竞态条件等问题。
缺点
- 递归使用较多:函数式编程通常使用递归而不是循环来实现迭代逻辑,这可能导致栈溢出等问题。
- 难以与现有系统集成:函数式编程与传统的命令式编程方法不兼容,与现有的系统集成可能会面临一些挑战。
应用场景
- 并行和并发编程
- 数据处理:函数式编程非常适合处理数据流
- 数学运算:函数式编程非常适合处理数学运算和算法实现。
谈⼀谈箭头函数与普通函数的区别?
1. 写法与命名
- 箭头函数:通常是匿名的
- 普通函数:通常有明确的函数名
2. 构造函数
- 箭头函数:不能用作构造函数。因为箭头函数没有
prototype
属性,且this
的绑定方式与普通函数不同。 - 普通函数:可以用作构造函数,通过
new
关键字来创建对象实例。普通函数具有prototype
属性,可以添加方法和属性,供实例继承。
3. this
的绑定
- 箭头函数:不绑定自己的
this
,而是继承自上一级作用域的this
值。 - 普通函数:
this
的绑定取决于函数的调用方式。在全局上下文中,this
指向全局对象(在浏览器中是window
,在Node.js中是global
)。在对象方法中,this
指向调用该方法的对象。在作为构造函数时,this
指向新创建的对象实例。此外,还可以通过call
、apply
和bind
方法来显式地设置this
的绑定。
4. arguments
对象
- 箭头函数:不拥有
arguments
对象。如果需要访问参数列表,可以使用剩余参数(...args
) - 普通函数:每次调用时都会自动创建一个
arguments
对象,该对象包含了传递给函数的所有参数
xhtml , html, xml 有什么区别?
html是超文本标记语言, 主要作用是定义网页的结构, 在网页中展示数据,
xml是可扩展标记语言, 特点是通过标签定义数据结构, 主要作用是传输和储存数据,
xhtml是可扩展超文本标记语言, 就是对htm进行了严格的xml规范化, 目的就是取代html, 但是实际发展过程中, HTML5更受欢迎, w3c也停止了对xhtml的开发
XML和JSON的区别?
XML(eXtensible Markup Language)和JSON(JavaScript Object Notation)是两种常用于数据交换和存储的格式,以下是对这两种格式区别的详细分析:
1. 定义与起源
- XML:是一种可扩展标记语言,专门用于存储和交换数据。
- JSON:是一种轻量级的数据交换格式,基于JavaScript语言的一个子集。
2. 数据表示方式
- XML:使用标签来组织数据,每个标记都有开始和结束标签。这使得XML文件具有高度的结构化和可读性,但也导致文件体积较大。
- JSON:使用键值对来表示数据,键和值之间用冒号分隔,对象用花括号
{}
表示,数组用方括号[]
表示。JSON的数据结构更加紧凑,因此文件体积通常较小。
3. 可读性与编码难度
- 可读性:XML的标签形式,对于人类阅读者来说可能更加直观;而JSON则以简洁的键值对结构,在编程和自动化处理中更加受欢迎。
JSON 的了解
一、JSON的定义与特点
JSON是一种轻量级的数据交换格式, 最初是从JavaScript中发展而来,如今已经成为一种独立于语言的数据交换格式。
JSON使用键值对来表示数据,键和值之间用冒号分隔,对象用花括号{}
表示,数组用方括号[]
表示。
JSON的数据结构紧凑, 非常简洁, 易于阅读理解, 并且文件体积较小。
二、JSON的解析与生成
序列化: JSON.stringify()
函数将JavaScript对象转换为JSON字符串
反序列化: JSON.parse()
函数将JSON字符串转换为JavaScript对象
对web标准、可⽤性、可访问性的理解
对Web标准的理解
Web标准是指万维网联盟(W3C)等权威组织制定的标准。这些标准旨在实现互联网的统一,确保不同平台、浏览器和设备能够正确的呈现网页内容,从而提高用户体验、减少开发和维护成本,并促进互联网的可持续发展。Web标准主要由以下三部分组成:
- 结构标准:定义网页的结构,包括网页的元素、属性、标签等。常见的结构标准包括HTML(超文本标记语言)、XHTML(可扩展超文本标记语言)、XML(可扩展标记语言)等。HTML是Web页面内容的基础,通过标签来描述页面的结构和内容。
- 表现标准:定义网页的表现,包括网页的样式、布局、动画等。常见的表现标准包括CSS(层叠样式表)、SVG(可缩放矢量图形)等。CSS用于控制网页的布局和样式,使网页在不同的设备和浏览器上呈现一致的效果。
- 行为标准:定义网页的行为,包括网页的事件、交互等。常见的行为标准包括JavaScript、DOM(文档对象模型)等。JavaScript是一种编程语言,用于实现网页的动态效果和交互功能,而DOM则提供了访问和操作网页内容的接口。
对Web可用性的理解
Web可用性是一个多因素概念,包括系统的学习成本、使用成本、系统的有效性、用户满意度。
主要措施:
- 简洁明了的界面:保持界面简洁,使用户能够轻松找到所需的信息。
- 一致性和可预测性:保持系统的一致性,使用户能够准确预期网站的行为。
- 清晰的导航:提供明确的导航路径和导航元素,帮助用户快速找到所需的内容。
- 反馈和确认:给与用户明确的操作反馈,以增强用户的控制感和信任感。
对Web可访问性的理解
Web可访问性(Accessibility)是指Web内容对于残障用户的可阅读和可理解性。
提高可访问性的主要方法包括:
- 提供替代文本:为图像、图表等视觉元素提供替代文本(alt文本),以便屏幕阅读器等辅助技术能够解读和呈现这些元素。
- 使用语义化标记:使用HTML的语义化标记来描述网页的结构和内容,以便辅助技术能够理解网页的层次结构和元素之间的关系。
- 确保键盘可访问性:确保网站的所有功能都可以通过键盘来访问和操作,以便那些无法使用鼠标的用户(如视障用户)能够使用网站。
- 良好的内容层级:使用清晰易读的字体, 划分明显的内容层级,以便视觉障碍用户轻松使用网站。
说⼏条写JavaScript的基本规范
良好的编码规范可以显著提高代码的可读性、可维护性和性能。
1. 变量声明
- 避免⼀⾏声明多个变量
- 优先使用
const
声明变量
2. 比较运算符
- 优先使用全等运算符(===)
3. 数组和对象的创建
- 优先使用字面量创建数组和对象
4. 控制结构
- switch语句:必须带有
default
分支,以处理未匹配到任何case
的情况。 - if和for语句:即使只有一行代码,也应使用大括号
{}
来明确代码块的范围
6. 命名规范
- 局部变量遵循小驼峰命名法
- 全局变量使用大写字母和下划线命名
- 类和构造函数遵循大驼峰命名法命名
- 函数遵循小驼峰命名法, 使用动词开头
8. 拆分文件
- 使用外链的JS和CSS
- JS放在文档的底部
- CSS放在文档的头部
什么是按需加载
按需加载是前端性能优化中的重要措施,其核心理念是根据用户的实际操作来加载相应的资源。
优点
- 提升性能:相比一次性加载, 按需加载可以减少初始的加载时间
- 减少服务器压力:由于减少了不必要的资源加载,服务器的负载也会相应降低
- 提升用户体验:用户可以根据自己的需求加载内容,而不必等待所有资源加载完成才能使用页面
实现方式
- 懒加载(Lazy Loading):对于图片、视频等媒体资源,可以采用懒加载的方式,即当用户滚动到这些资源所在的位置时才进行加载。
- 代码分割(Code Splitting):在构建前端项目时,可以将代码分割成多个小块(chunks),然后根据需要动态加载这些小块。这通常通过Webpack等构建工具来实现。
- 异步组件(Async Components):在Vue等现代前端框架中,可以使用异步组件来实现按需加载。这些组件在需要时才会被加载和渲染。
- 按需导入(Dynamic Imports):在JavaScript中,可以使用动态导入语法(如
import()
)来按需加载模块。这种方式允许你在需要某个模块时才加载它。
说说严格模式的限制
JavaScript的严格模式(Strict Mode)是一种具有限制性的JavaScript模式,它使代码隐式地脱离了“懒散(sloppy)模式”,从而以更加严格的方式对代码进行检测和执行。
- 变量声明:
-
- 变量必须通过关键字进行声明
- 删除变量、函数或函数参数:
-
- 不允许使用
delete
操作符删除变量、函数或函数参数
- 不允许使用
- 只读属性赋值:
-
- 不允许对只读属性(如通过
const
关键字声明的常量)进行赋值
- 不允许对只读属性(如通过
- this值:
-
- 在严格模式下,全局函数的
this
不再指向全局对象,而是undefined
- 除非通过
call()
、apply()
或bind()
明确指定
- 在严格模式下,全局函数的
- 重复的函数参数名:
-
- 严格模式下,函数的参数不能重复
- 在非严格模式下,重复的函数参数名会被忽略。
- with语句:
-
- 不允许使用
with
语句。with
语句允许将对象的属性添加到作用域链中,可能导致代码可读性和性能问题。
- 不允许使用
常⻅兼容性问题?
由于不同浏览器对JavaScript的实现细节和特性支持程度存在差异,因此同一份代码在不同浏览器上可能会表现出不同的行为
1. 新语法支持
- 问题描述:不同浏览器对新特性支持程度不同。
- 解决方法:
-
- 使用Babel等JavaScript编译器将ES6+代码转换为向后兼容的ES5代码。
2. DOM差异
- 问题描述:不同浏览器对DOM的操作和渲染存在差异
- 问题示例: 盒模型的差异, 导致的 margin 和 padding 不同。
- 解决方法:
-
- 全局的样式初始化
- 简化或避免DOM操作,从根本上解决问题。
3. 事件处理差异
- 问题描述:旧版IE浏览器使用IE事件模型,而其他浏览器使用W3C标准事件模型。
- 解决方法:
-
- 不同的事件模型在事件注册, 事件解绑, 事件对象都存在差异
- 如果需要兼容旧版IE使浏览器,就需要用条件注释或特性检测添加额外的兼容逻辑
- 举例:
-
- IE 下, event 对象有 x , y 属性,但是没有 pageX , pageY 属性
- Firefox 下, event 对象有 pageX , pageY 属性,但是没有 x,y 属性 .
4.最佳实践
- 避免使用特定的浏览器API:这些扩展或API可能在其他浏览器中不可用,从而导致兼容性问题。
- 使用现代的开发工具和框架:这些工具和框架通常已经处理了大部分的兼容性问题,并提供了丰富的文档和社区支持。
说说前端中的事件流
HTML 与 JS 的交互是通过事件驱动来实现的,事件流描述的是JS从页面中接收事件的顺序
事件流包括事件捕获阶段->处于目标阶段->事件冒泡阶段
addEventListener事件柄最后的参数,如果是true,程序就在捕获阶段执行 , 如果是false,程序就在冒泡阶段执行
IE浏览器只支持事件冒泡。
事件委托
把事件设置在父元素身上,通过事件冒泡,实现父元素监听子元素的事件
- 减少事件处理的数量:你只需在父元素上绑定一个事件处理程序,就可以管理该父元素下所有子元素的事件。
- 增加程序的灵活性:当新的子元素被添加到父元素中时,它们会自动继承来自父元素的事件处理程序,无需再次手动绑定事件。
事件模型
一、定义
事件模型是一种设计模式。它允许对象之间通过事件进行通信和处理
二、组成部分
- 事件源(Event Source):它是事件的起始点,负责生成并触发事件。
- 事件对象(Event Object):包含事件信息的对象,事件发生时会被传递给事件监听器。
- 事件监听器(Event Listener):负责监听事件,在事件发生时执行处理函数。
三、类型
在Web开发中,DOM事件模型是处理用户交互的主要机制。它分为DOM0级事件、DOM2级事件和IE事件。
- DOM0级事件通过为元素添加事件处理属性(如
onclick
)来实现; - DOM2级事件则通过
addEventListener()
方法来实现,支持为元素添加多个事件监听器并控制事件处理的时机(捕获或冒泡阶段); - IE事件模型通过
attachEvent()
方法实现,但仅支持IE浏览器。
JS⾃定义事件
在JavaScript中,自定义事件允许创建并触发自己的事件,然后监听这些事件来执行特定的代码。
1. 创建自定义事件
你可以使用CustomEvent
构造函数来创建一个自定义事件。CustomEvent
构造函数接受几个参数,包括事件名称(type
)、一个配置对象(可选,用于设置事件是否可冒泡、是否可取消等)。
const myEvent = new CustomEvent('myCustomEvent', { bubbles: true, // 是否冒泡 cancelable: true, // 是否可以取消 detail: { // 传递给事件处理程序的额外数据 message: 'Hello, this is a custom event!' }
});
2. 监听自定义事件
使用addEventListener
方法可以为元素或对象添加事件监听器,以监听自定义事件。
document.addEventListener('myCustomEvent', function(e) { console.log(e.detail.message); // 输出: Hello, this is a custom event!
});
3. 触发自定义事件
使用dispatchEvent
方法在元素或对象上触发自定义事件。
// 在document上触发
document.dispatchEvent(myEvent); // 或者在特定元素上触发
myElement.dispatchEvent(myEvent);
注意事项
- 自定义事件名称不应与现有的DOM事件名称冲突。
- 使用
bubbles
和cancelable
选项可以控制事件的冒泡和取消行为。 detail
属性是传递给事件处理函数的额外数据,它应该是一个对象,这样可以传递更复杂的数据。- 在移除事件监听器时,请确保使用与添加时相同的函数引用(除非使用
removeEventListener
的捕获阶段变体)。
addEventListener()和attachEvent()的区别
addEventListener()和attachEvent()都是JavaScript中用于绑定事件处理程序的方法,但它们之间存在几个关键的区别。以下是这些区别的详细分析:
1. 兼容性
- addEventListener():是W3C标准的事件监听方法,在现代浏览器中得到广泛的支持
- attachEvent():主要用于旧版本的IE浏览器(如IE7和IE8)
2. 方法名和参数
- addEventListener():接受三个参数。第一个参数是事件类型(不含"on"前缀,如"click"),第二个参数是事件处理函数,第三个参数用于指定事件在捕获阶段或者冒泡阶段执行
- attachEvent():接受两个参数。第一个参数是事件类型(含"on"前缀,如"onclick"),第二个参数是当事件发生时调用的函数。在IE浏览器中,默认不支持事件冒泡
3. this的指向
- addEventListener():在事件处理函数中,
this
关键字指向触发事件的元素。 - attachEvent():在事件处理函数中,
this
关键字指向window
对象。因为在attachEvent方法中,事件处理程序是在全局作用域下执行的。
4. 事件的执行顺序
- addEventListener():绑定的事件处理程序会按照它们被添加的顺序执行。
- attachEvent():绑定的事件处理程序会按照它们被添加的相反顺序执行,这是IE浏览器特有的行为。
5. 移除事件监听器
- addEventListener():使用
removeEventListener()
方法来移除之前添加的事件监听器。 - attachEvent():使用
detachEvent()
方法来移除之前添加的事件监听器。
mouseover 和 mouseenter 的区别
mouseover:当鼠标移入元素或其子元素都会触发事件,有冒泡的过程。对应的移除事件是 mouseout
mouseenter:当鼠标移入元素本身会触发事件,不会冒泡,对应的移除事件是 mouseleave
什么是事件监听
事件监听的作用是监听事件的触发, 是事件模型的组成部分
事件模型有三部分
- 事件源(Event Source):触发事件的对象。
- 事件对象(Event):记录事件的相关信息
- 事件监听器(Event Listener):监听事件的触发,并触发处理函数
谈谈你对ES6的理解
ES6也称为ECMAScript 2015,是JavaScript语言的一个重大更新版本
ES6的主要新特性
- 变量声明
-
- let:具有块级作用域,不允许在同一作用域内重复声明同一个变量。
- const:也具有块级作用域,声明时必须进行初始化赋值,且赋值后不能再重新赋值修改其值(但如果是一个对象或数组,可以修改其内部属性或元素)。
- 箭头函数
-
- 提供了更简洁的函数写法,并且不创建自己的
this
上下文,继承外层函数的this
值,避免了this
指向错误的问题。
- 提供了更简洁的函数写法,并且不创建自己的
- 模板字符串
-
- 使用反引号(``)标识,支持多行字符串和动态内容插入,使字符串的拼接和处理更加方便、灵活和易读。
- 解构赋值
-
- 允许从数组或对象中提取值,并将其赋给变量,提高了代码的可读性和便捷性。
- 扩展运算符
-
- 主要用于展开数组或对象,以及函数参数传递等场景,提供了更灵活的操作方式。
- 剩余参数
-
- 允许将不定数量的参数表示为一个数组,便于处理函数中的可变参数。
- 模块化
-
- ES6引入了模块化系统,允许开发者将大型代码库分解为更小、更独立的模块,提高了代码的可维护性和复用性。
- 新数据结构
-
- 引入了
Map
和Set
等新的数据结构,提供了更高效的遍历和查找功能。
- 引入了
- 异步编程
-
- 支持
Promise
和async/await
等异步编程特性,提高了异步编程的效率和可读性。
- 支持
ES6的应用与影响
- 前端开发:ES6的新特性极大地提升了前端开发的效率和体验,使得开发者能够编写更加简洁、易读和高效的代码。
- 生态系统:ES6的发布促进了JavaScript生态系统的快速发展,涌现出大量基于ES6特性的工具和库,如Babel等转译器、Webpack等模块打包器等。
简单介绍一下 symbol
一、基本概念
- 定义:Symbol是一种基本数据类型,通过Symbol()函数创建的Symbol值都是唯一的
- 作用:Symbol的主要作用是确保对象属性名的唯一性,避免属性名冲突。
二、特点
- 唯一性:Symbol值的唯一性是通过内部机制保证的
- 不可变性:Symbol值一旦创建,就不能被改变或撤销。
- 不可枚举性:Symbol作为对象属性的键时,这些属性不会出现在for...in循环中
- 描述性:Symbol可以接受一个可选的描述参数,用于描述该Symbol的用途。但描述仅用于调试和显示目的,不会影响Symbol的唯一性。
三、使用方式
- 创建Symbol:使用Symbol()函数可以创建一个Symbol值。可以传递一个可选的描述符字符串作为参数,但这不是必须的。
let sym1 = Symbol();
let sym2 = Symbol('description');
console.log(sym1 === sym2); // false
- 作为对象属性名:Symbol可以作为对象属性的键,以确保属性名的唯一性。由于Symbol不是字符串,因此需要使用方括号([])来访问这些属性。
let sym = Symbol('name');
let obj = { [sym]: 'John'
};
console.log(obj[sym]); // John
- 全局共享的Symbol:使用Symbol.for()方法可以创建或获取一个全局共享的Symbol。如果传入的字符串作为描述符已经存在,则返回该描述符对应的Symbol;如果不存在,则创建一个新的Symbol并添加到全局Symbol注册表中。
let sym1 = Symbol.for('global');
let sym2 = Symbol.for('global');
console.log(sym1 === sym2); // true
- Symbol的内置属性:JavaScript还提供了一些内置的Symbol属性,如Symbol.iterator、Symbol.hasInstance等,用于定义对象的行为。
四、应用场景
- 属性名冲突解决:在多个模块或库之间共享对象时,使用Symbol作为属性名可以避免属性名冲突。
- 定义类的私有属性和方法:由于Symbol的唯一性,可以使用Symbol来定义类的私有属性和方法,从而隐藏类的内部实现细节。
ES6 箭头函数的特性
1. 简洁的语法
- 使用
=>
表示函数定义:箭头函数使用=>
符号来定义,这使得代码更加简洁。
2. 不绑定自己的this
- 继承外层函数的
this
:箭头函数不创建自己的this
值,它会捕获其所在上下文的this
值。 - 无法改变
this
指向:由于箭头函数的this
是词法上确定的,因此call()
、apply()
和bind()
方法无法更改箭头函数内部this
的指向。
3. 不拥有arguments
对象
- 箭头函数没有自己的
arguments
对象。如果需要访问所有参数,使用剩余参数(...args
)。
4. 不具有prototype
属性
- 不是构造函数:箭头函数没有
prototype
属性,因为它们不是构造函数。因此,不能使用new
关键字来调用箭头函数。
7. 注意事项
- 返回对象字面量:如果箭头函数需要返回一个对象字面量,则必须将该对象字面量包裹在括号中,否则会被解释为函数体的一部分。
- 多重箭头函数:多重箭头函数(即箭头函数内部再嵌套箭头函数)可以形成高阶函数,但需要注意作用域和
this
的绑定。
示例
// 简洁的语法
const add = (a, b) => a + b; // 不绑定自己的this
const person = { name: 'Alice', greet: () => console.log(this.name), // 注意:这里的this指向全局对象,而不是person对象 greetWithArrow: function() { return () => console.log(this.name); // 这里的this指向person对象 }
}; person.greet(); // 可能输出undefined,取决于全局环境
person.greetWithArrow()(); // 输出Alice // 不拥有arguments对象
const sum = (...args) => args.reduce((a, b) => a + b, 0);
console.log(sum(1, 2, 3)); // 输出6 // 不具有prototype属性
const func = () => {};
console.log(func.prototype); // undefined // 返回对象字面量
const getPerson = () => ({ name: 'Bob' });
console.log(getPerson()); // 输出{ name: 'Bob' }
对原⽣Javascript了解程度
- 基础语法:
-
- 变量声明(
var
,let
,const
)及其作用域(块级作用域与函数作用域)。 - 数据类型(原始类型如
string
,number
,boolean
,null
,undefined
,symbol
(ES6新增),以及复杂类型如Object
,Array
,Function
)。 - 运算符(算术运算符、比较运算符、逻辑运算符、位运算符等)。
- 条件语句(
if...else
,switch
)和循环语句(for
,while
,do...while
)。
- 变量声明(
- 函数与闭包:
-
- 函数声明与函数表达式。
- 高阶函数、回调函数和匿名函数。
- 闭包的概念及其用途(如封装私有变量)。
this
关键字的作用及其在不同场景下的绑定规则(默认绑定、隐式绑定、显式绑定、new绑定)。
- 对象与原型链:
-
- 对象的字面量语法和构造函数创建对象。
- 原型(
__proto__
)和原型链的概念,以及如何通过原型实现继承。 Object.prototype
上的方法(如hasOwnProperty
,toString
,valueOf
等)。- ES6中引入的
class
关键字和类继承。
- 数组与集合:
-
- 数组的常用方法(如
push
,pop
,shift
,unshift
,slice
,splice
,map
,filter
,reduce
等)。 - 数组迭代方法(
forEach
,for...of
循环)。 - 集合类型(如
Set
,Map
及其迭代器)。
- 数组的常用方法(如
- 异步编程:
-
- 回调函数的使用及其缺点(如回调地狱)。
- Promise对象及其链式调用。
- async/await语法糖,使得异步代码看起来更像是同步代码。
- DOM与BOM操作:
-
- DOM(文档对象模型)的基本操作(如获取元素、修改元素内容、添加/删除事件监听器等)。
- BOM(浏览器对象模型)的基本操作(如操作窗口、定时器、导航和定位等)。
- 了解并应用AJAX(Asynchronous JavaScript and XML)进行异步数据交换。
- ES6+新特性:
-
- 模板字符串、默认参数、展开语法(
...
)、箭头函数等语法糖。 - 模块系统(ES6 Modules)。
Symbol
类型、Set
和Map
集合、Proxy
和Reflect
API等。Promise
、async/await
等异步解决方案。class
语法和类的继承。
- 模板字符串、默认参数、展开语法(
- 性能优化与安全:
-
- 了解JavaScript执行机制(如事件循环、任务队列、宏任务与微任务)。
- 掌握性能优化的技巧(如减少DOM操作、使用事件委托、优化图片和资源加载等)。
- 了解并避免常见的安全漏洞(如XSS攻击、CSRF攻击)。
正则表达式
正则表达式是匹配字符串的规则, 在JS中, 正则表达式也是对象, 通常用于查找或替换符合规则的文本
基本组成
- 普通字符:普通字符在正则表达式中表示它们本身。大写和小写字母、所有数字、标点符号都是普通字符,
- 特殊字符:一些字符在正则表达式中被赋予特殊的含义,用于表示控制或通配的功能。例如:
.
(点)匹配单个字符(除了换行符),*
(星号)匹配出现零次或多次,+
(加号)匹配一次或多次,?
(问号)匹配零次或一次
示例
- 匹配邮箱地址:
\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b
这个正则表达式可以匹配大多数标准的邮箱地址。
- 匹配电话号码(假设电话号码格式为:区号-号码,如010-12345678):
\d{3}-\d{8}
这里\d
表示匹配一个数字,{3}
表示前面的数字字符恰好出现3次,-
是普通字符,表示它本身,{8}
表示前面的数字字符恰好出现8次。
- 匹配HTML标签:
<[^>]+>
这个正则表达式用于匹配HTML标签。<
和>
是HTML标签的开始和结束标记,[^>]+
表示匹配一个或多个非>
字符。
web开发中会话跟踪的⽅法有哪些
HTTP协议本身是无状态的,所以服务器无法直接识别同一客户端的连续请求是否属于同一个会话。因此,需要通过一些技术手段来跟踪用户的会话状态。
1. Cookie
描述:
Cookie是常用的会话跟踪技术。服务器可以在HTTP响应中发送一个Cookie到用户的浏览器。浏览器把Cookie储存在本地, 后续的请求会自动携带Cookie,以便服务器识别用户的会话
优点:
- 跨页面:Cookie可以在多个页面间共享
- 易于使用:开发人员可以方便地通过HTTP响应和请求来设置和读取Cookie。
缺点:
- 安全性问题:Cookie存储在客户端,可能会受到XSS(跨站脚本)和CSRF(跨站请求伪造)等安全攻击。
- 依赖浏览器:如果浏览器禁用Cookie,则无法使用此方法。
2. URL重写
描述:
URL重写是另一种会话跟踪技术,主要用于客户端不支持Cookie的情况。通过在URL的末尾添加参数(如?session_id=ABC123
)来标识用户的会话。服务器解析URL中的参数以获取会话ID
优点:
- 不依赖Cookie:即使浏览器禁用Cookie,也可以通过URL重写来实现会话跟踪。
缺点:
- URL冗长:添加会话ID后,URL可能会变得很长
- 安全性问题:URL中的会话ID存在泄露风险。
3. 隐藏表单字段
描述:
在HTML表单中添加隐藏的字段来存储会话信息。当表单提交时,隐藏的字段会一起发送到服务器。
优点:
- 适用于表单提交场景:在需要提交表单的页面中,隐藏表单字段可以方便地传递会话信息。
缺点:
- 局限性大:仅适用于表单提交的场景。
4. Session
描述:
Session是一种在服务器端实现会话跟踪的技术。服务器为每个会话创建一个唯一的会话ID,并将其存储在内存中或数据库中。服务器通常会将会话ID通过Cookie发送给客户端浏览器。
优点:
- 安全性高:会话数据存储在服务器端,减少了被客户端攻击的风险。
- 灵活性高:服务器可以灵活地管理会话数据,包括设置会话的超时时间、存储会话数据等。
缺点:
- 服务器资源占用:每个会话都需要在服务器端占用一定的资源(如内存或数据库空间)。
- 依赖Cookie: Session本身不依赖Cookie,但通常需要通过Cookie来传递会话ID。
5.JWT令牌
JWT不属于会话跟踪技术,但是已经成为无状态会话管理和身份验证的流行方法
- JWT是一种无状态的、自包含的令牌,用于在用户与服务器之间安全地传输信息
- 当用户成功登录后,服务器会生成一个JWT并将其发送给客户端(通常是通过HTTP响应头中的
Authorization
字段,使用Bearer
模式)。 - 客户端在随后的请求中会携带这个JWT,以便服务器验证用户的身份和权限。
gulp是什么
- gulp 是⼀种基于流的代码构建⼯具,是⾃动化项⽬的构建利器;它不仅 能对⽹站资源进⾏优化,⽽且在开发过程中可以⾃动完成重复的任务
- Gulp的核⼼概念:流
- 流,简单来说就是建⽴在⾯向对象基础上的⼀种抽象的处理数据的⼯具。在流中,定义了 ⼀些处理数据的基本操作,如读取数据,写⼊数据等
- gulp正是通过流和代码优于配置的策略来尽量简化任务编写的⼯作
- Gulp的特点:
- 易于使⽤:通过代码优于配置的策略,gulp 让简单的任务简单,复杂的任务可管理
- 构建快速 : 利⽤ Node.js 流的威⼒,你可以快速构建项⽬并减少频繁的 IO 操作
- 易于学习: 通过最少的 API ,掌握 gulp 毫不费⼒
你对 Electron 的理解
Electron 是一个使用 Web 技术来创建桌面应用的框架。
1. 技术栈与架构
- 核心技术:Electron 基于 Chromium(用于渲染网页)和 Node.js(用于运行 JavaScript 和与操作系统交互)。这种结合使得 Electron 应用能够同时拥有 Web 应用的灵活性和 Node.js 的强大功能。
- 架构:Electron 应用通常包含一个主进程(管理窗口的创建和应用的生命周期)和一个或多个渲染进程(运行在每个窗口中的网页,并控制界面显示)。主进程和渲染进程通过 IPC(进程间通信)进行通信。
2. 跨平台
- 平台支持:Electron 可以让开发者轻松地将应用部署到 Windows、macOS 和 Linux 上。
3. 丰富的 API
- Node.js API:由于 Electron 集成了 Node.js,开发者可以使用 Node.js 的所有 API,包括文件系统、网络请求、数据库操作等。
- 原生 API 访问:Electron 还提供了一系列 API,允许开发者使用操作系统的原生功能,如通知、系统托盘、剪贴板等。
4. 开发体验
- 热重载:在开发过程中,Electron 支持热重载,即开发者可以在不重启应用的情况下实时看到代码更改的效果。
- 调试工具:Electron 应用支持 Chrome DevTools,这使得开发者可以像调试网页一样调试他们的应用。
5. 性能与资源
- 内存和资源使用:由于 Electron 应用包含了完整的 Chromium 浏览器和 Node.js 运行时,因此它会比原生应用占用更多的内存和磁盘空间。
6. 社区与生态
- 工具和库:社区还提供了许多工具和库,如 Electron Builder、Electron Forge 等,这些工具和库可以简化 Electron 应用的开发和打包过程。
7. 应用实例
- Visual Studio Code:微软开发的强大代码编辑器,就是基于 Electron 构建的。
- GitHub Desktop:GitHub 官方的桌面客户端,用于管理 Git 仓库和拉取请求等。
Node的应⽤场景
Node.js基于Chrome V8 擎构建运行环境,拥有非阻塞I/O和事件驱动的特性。
1. Web服务器
- 高并发处理:Node.js天生适用高并发、低延迟的场景, 通过Express.js、Koa等框架,可以快速搭建Web应用。
2. 实时通信服务器
- 技术实现:通过Socket.IO、WebSocket等技术,Node.js可以构建建高实时性的应用,如在线游戏、实时协作工具等。
3. 微服务架构
- 轻量级服务:Node.js的轻量级特性使得它在微服务架构中能够高效地运行多个小型服务。
5. 命令行工具开发
- 自动化任务:Node.js提供了强大的命令行工具开发能力,允许开发者创建自定义命令行工具和脚本,用于自动化任务、数据处理、工作流程等。
7. 数据流处理
- 高效I/O处理:由于Node.js对I/O操作的高效处理,它在处理大量数据和流式数据方面表现出色, 适用于大型文件传输、日志分析、数据导入导出等场景。
10. 前端开发工具
- 构建工具:Node.js可用于构建前端开发工具,如构建工具(Gulp、Webpack)、包管理器(npm)等,提高前端开发效率和代码质量。
attribute和property的区别是什么
- attribute 是 dom 元素在HTML中 作为标签拥有的属性;
- property 是 dom 元素在JS中 作为对象拥有的属性。
- 在默认情况下,Attribute和Property的值是同步的。
- 但是,一旦用户通过界面交互(如输入文本)改变了元素的状态,Property的值会更新,而Attribute的值则不会(除非通过代码显式更新)。
eval是做什么的
- 用法:eval函数接受一个字符串作为参数。如果字符串是表达式,eval会计算并返回表达式的值;如果字符串表示的是语句,eval会执行这些语句。
- 示例:
eval("2 + 3")
会返回5
。 - 安全性:eval可以执行任意代码,应避免在生产环境中使用eval来执行不可信的代码。
window.onload和$(document).ready
window.onload: 页面所有元素加载完成后执行
window.onload = function() { // 页面加载完成后执行的代码
}; // 或者使用 addEventListener(可以添加多个监听器)
window.addEventListener('load', function() { // 页面加载完成后执行的代码
});
$(document).ready: DOM渲染完成后执行
$(document).ready(function() { // DOM加载完成后执行的代码
}); // 或者使用更简洁的写法
$(function() { // DOM加载完成后执行的代码
});