一、css
1.说一下css盒子模型
CSS盒子模型(Box Model)是CSS中用于描述元素尺寸和布局的一个重要概念。它定义了元素的内容、内边距、边框、外边距和高度的计算方式。盒子模型对于网页布局和响应式设计至关重要。
在CSS中,每个元素都可以被视为一个盒子,这个盒子由内容(content)、内边距(padding)、边框(border)、外边距(margin)和高度(height)组成。
盒子模型的计算方式如下:
内容(content):元素的内容区域,也就是元素中实际的内容。
内边距(padding):元素内容与边框之间的距离。
边框(border):元素边框的宽度。
外边距(margin):元素边框外侧的距离。
高度(height):元素的高度。
在CSS中,可以通过设置元素的宽度和高度来控制元素的大小。但是,如果元素没有设置高度,那么它的实际高度将根据其内容自动调整。
盒子模型在响应式设计中非常重要,因为它可以帮助我们实现响应式布局。通过调整盒子模型的尺寸,我们可以轻松地实现响应式布局,使网页在不同设备上都能保持良好的显示效果。
盒子组成:内容content、内边距padding、边框border、外边距margin
盒模型类型:
在CSS中,盒模型可以分为两种类型:块级元素盒模型 和 内联元素盒模型。
块级元素盒模型:
块级元素盒模型包括:content(内容)、padding(内边距)、border(边框)和margin(外边距)。
块级元素在网页中占据一行,其高度、宽度、内边距和外边距都可以设置。
内联元素盒模型:
内联元素盒模型包括:content(内容)、padding(内边距)和margin(外边距)。
内联元素在网页中不会占据一行,其高度和宽度不能直接设置,但可以通过设置行高(line-height)和letter-spacing(字符间距)来间接控制。
内联元素盒模型主要用于文本元素,如文字、链接等。
2.css选择器的优先级
CSS(Cascading Style Sheets)是一种层叠样式表,主要用于描述HTML元素在屏幕上的显示样式。CSS具有以下特性:
层叠性:CSS具有层叠性,即可以多次定义同一个元素的样式,且后定义的样式会覆盖先定义的样式。
继承性:CSS具有继承性,即子元素会继承父元素的样式。但是,如果子元素有与父元素相同的样式定义,则子元素的样式会覆盖父元素的样式。
选择器优先级:CSS中,选择器的优先级分为四种,分别是:内联样式、内部样式表 、外部样式表 、浏览器默认样式。
样式表的层叠顺序:当一个HTML元素有多个样式定义时,CSS会按照以下顺序进行层叠:内联样式 > 内部样式表 > 外部样式表 > 浏览器默认样式。
样式表的优先级:当一个HTML元素有多个相同的样式定义时,CSS会按照以下优先级进行层叠:important > 内联样式 > 内部样式表 > 外部样式表 > 浏览器默认样式。
总结:
CSS具有层叠性、继承性和选择器优先级等特性,这些特性可以让我们更好地控制HTML元素的显示样式。
选择器的优先级:
!important、行内样式、、id选择器、类/伪类/属性、标签、全局选择器。
3.隐藏元素的方法
display: none
;可以完全隐藏元素,不占据空间
visibility: hidden
;可以隐藏元素但占据空间,
opacity: 0
;可以隐藏元素但占据空间,
position: absolute
;或position: fixed
;可以隐藏元素并将其移出正常文档流。
4.px和rem的区别
px和rem都是CSS中常用的长度单位,但它们之间存在一些区别。
- 定义范围不同:px是绝对长度单位,它的值是固定的,不会因为浏览器或设备的尺寸而改变。rem是相对长度单位,它的值是基于元素父元素的
font-size
属性值。 - 计算方式不同:px可以直接用来设置元素的大小,而rem需要先设置父元素的font-size属性值,然后通过rem进行计算。
- 兼容性不同:px在大多数浏览器中都可以正常显示,而rem在浏览器中的兼容性较差,特别是在移动设备上。
- 响应式设计:在响应式设计中,rem可以实现不同设备上的自适应效果,而px则无法实现。
- rem:相对单位长度,相对于html根节点的font-size的值,1rem=10px;font-size:62.5%(16px* 62.5%=10)。
5.重绘重排有什么区别?
重绘和重排是网页渲染过程中两种不同的操作,它们对网页性能的影响不同。
- 重绘(Repaint):当一个元素的样式发生变化,但布局不变,此时浏览器会重新绘制该元素,这个过程称为重绘。重绘不会影响网页的性能,因为它不需要重新计算元素的布局。
- 重排(Reflow):当一个元素的布局发生变化,浏览器需要重新计算该元素及其子元素的布局,这个过程称为重排。重排会对网页的性能产生影响,因为它需要重新计算元素的布局,可能会导致页面重新渲染。
重排和重绘的区别在于,重排需要重新计算元素的布局,可能会导致页面重新渲染;而重绘不需要重新计算布局,只是重新绘制元素。
在实际使用中,可以通过减少重排次数来优化网页性能。例如,可以使用display: none;和visibility: hidden;来隐藏元素,而不是直接删除元素;可以使用transform属性来调整元素的位置,而不是直接调整元素的top和left属性。
6.让一个元素水平垂直居中
定位+margin
定位+transform
父元素 {
position: relative;
}
子元素 {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100px; /* 或者设置为其他宽度 */
height: 100px; /* 或者设置为其他高度 */
}
flex布局
父元素 {
display: flex;
justify-content: center;
align-items: center;
height: 100%; /* 或者设置为其他高度 */
}
子元素 {
width: 100px; /* 或者设置为其他宽度 */
height: 100px; /* 或者设置为其他高度 */
}
grid布局
父元素 {
display: grid;
justify-items: center;
align-items: center;
height: 100%; /* 或者设置为其他高度 */
}
子元素 {
width: 100px; /* 或者设置为其他宽度 */
height: 100px; /* 或者设置为其他高度 */
}
table布局
7.css那些属性可以继承,那些不能继承
可以继承的属性:
(1)字体和文本属性:font-family、font-size、font-weight、font-style、text-align、line-height、letter-spacing等。
(2)颜色和背景属性:color、background-color、background-image、background-repeat、background-position、background-size等。
(3)盒模型属性:margin、padding、border、width、height、min-width、max-width、min-height、max-height等。
(4)其他属性:list-style、float、clear、cursor等。
不能继承的属性:
(1)元素类型属性:display、position、float、clear等。
(2)visibility属性:visibility、opacity等。
(3)overflow属性:overflow、overflow-x、overflow-y等。
(4)z-index属性:z-index等。
总结:CSS属性具有继承性,但并非所有的属性都可以继承。可以继承的属性主要包括字体和文本属性、颜色和背景属性、盒模型属性等;不能继承的属性主要包括元素类型属性、visibility属性、overflow属性、z-index属性等。
8.预处理器
预处理语言增加了变量、函数、混入等功能。
二、javascript
1.js的组成
JavaScript是一种脚本语言,通常用于创建交互式的网页。它由三部分组成:ECMAScript、DOM和BOM。
ECMAScript:ECMAScript是一种由ECMA-262标准定义的脚本语言,它是JavaScript的核心部分。ECMAScript定义了JavaScript的基本语法、变量、运算符、函数等基本元素。
DOM:文档对象模型(DOM,Document Object Model)是JavaScript操作网页的接口。它将HTML文档转换为一个由节点和对象组成的树形结构,从而可以通过JavaScript代码来操作网页元素。
BOM:浏览器对象模型(BOM,Browser Object Model)是JavaScript操作浏览器窗口的接口。它提供了与浏览器窗口相关的各种信息,如窗口大小、历史记录、location等,还可以用于实现一些与浏览器相关的功能,如弹出窗口、跳转页面等。
2.js内置对象
JavaScript内置对象是指在JavaScript中预先定义好的对象,可以直接使用。以下是一些常用的内置对象:
- Number:创建一个数字对象。
var num = new Number(123);
- String:创建一个字符串对象。
var str = new String("hello");
- Boolean:创建一个布尔对象。
var bool = new Boolean(true);
- Array:创建一个数组对象。
var arr = new Array("apple", "banana", "orange");
- Object:创建一个对象对象。
var obj = new Object();
obj.name = "John";
obj.age = 30;
- Function:创建一个函数对象。
function sayHello() {
alert("Hello!");
}
- Date:创建一个日期对象。
var date = new Date();
- RegExp:创建一个正则表达式对象。
var reg = new RegExp("\\d+", "g");
- Error:创建一个错误对象。
var err = new Error("Something went wrong.");
3.操作数组的方法有哪些
JavaScript提供了许多操作数组的方法,以下是一些常用的方法:
push() ,pop() ,sort() ,splice() ,unshift() ,shift() ,reverse() ,map()
- push(): 向数组的末尾添加一个或多个元素,并返回新数组的长度。
var arr = [1, 2, 3];
arr.push(4, 5);
console.log(arr); // [1, 2, 3, 4, 5]
- pop(): 删除数组的最后一个元素,并返回被删除的元素。
var arr = [1, 2, 3];
var ret = arr.pop();
console.log(ret); // 3
console.log(arr); // [1, 2]
- unshift(): 向数组的开头添加一个或多个元素,并返回新数组的长度。
var arr = [1, 2, 3];
arr.unshift(0);
console.log(arr); // [0, 1, 2, 3]
- shift(): 删除数组的第一个元素,并返回被删除的元素。
var arr = [1, 2, 3];
var ret = arr.shift();
console.log(ret); // 1
console.log(arr); // [2, 3]
- splice(): 删除数组中指定索引的元素,并返回被删除的元素。
var arr = [1, 2, 3, 4, 5];
var ret = arr.splice(1, 2);
console.log(ret); // [2, 3]
console.log(arr); // [1, 4, 5]
- slice(): 返回一个新数组,包含从开始到结束(不包括结束)的所有元素。
var arr = [1, 2, 3, 4, 5];
var newArr = arr.slice(1, 4);
console.log(newArr); // [2, 3, 4]
- concat(): 连接两个或多个数组,并返回一个新的数组。
var arr1 = [1, 2, 3];
var arr2 = [4, 5, 6];
var newArr = arr1.concat(arr2);
console.log(newArr); // [1, 2, 3, 4, 5, 6]
- reverse(): 反转数组中的元素,并返回新数组。
var arr = [1, 2, 3, 4, 5];
var newArr = arr.reverse();
console.log(newArr); // [5, 4, 3, 2, 1]
- sort(): 对数组进行原地排序,并返回排序后的数组。
var arr = [3, 1, 4, 1, 5];
arr.sort(function(a, b) {
return a - b;
});
console.log(arr); // [1, 1, 3, 4, 5]
- map(): 对数组中的每个元素执行一个给定的函数,并返回一个新的数组。
var arr = [1, 2, 3, 4, 5];
var newArr = arr.map(function(item) {
return item * 2;
});
console.log(newArr); // [2, 4, 6, 8, 10]
- filter(): 对数组中的每个元素执行一个给定的函数,并返回一个新的数组。
var arr = [1, 2, 3, 4, 5];
var newArr = arr.filter(function(item) {
return item % 2 === 0;
});
console.log(newArr); // [2, 4]
4.对数据类型的检测
JavaScript可以通过以下方法检测数据类型:
- typeof运算符:用于检测变量的数据类型。
var num = 123;
console.log(typeof num); // "number"
- instanceof运算符:用于检测一个对象是否属于某个特定的构造函数。
var arr = [1, 2, 3];
console.log(arr instanceof Array); // true
- Object.prototype.toString.call()方法:用于检测变量的数据类型。
var num = 123;
console.log(Object.prototype.toString.call(num)); // "[object Number]"
- Array.isArray()方法:用于检测一个对象是否为数组。
var arr = [1, 2, 3];
console.log(Array.isArray(arr)); // true
- isNaN()函数:用于检测一个值是否为非数字。
var num = "123";
console.log(isNaN(num)); // false
5.闭包,特点
闭包(Closure)是JavaScript中一种重要的概念,它指的是一个函数可以访问其词法作用域中的变量,即使这个函数在其词法作用域之外被调用。
简言之:
闭包:函数嵌套函数,内部函数被外部函数返回并保存下来
闭包的特点如下:
-
函数可以访问其词法作用域中的变量,即使这个函数在其词法作用域之外被调用。
-
闭包可以使得局部变量在函数外部被访问和修改。
-
闭包可以实现模块化和封装,防止全局作用域被污染。
缺点:会消耗内存,导致页面性能下降,在ie浏览器中会导致内存泄漏
使用场景:防抖、节流、避免数据全局污染
下面是一个简单的闭包示例:
function createCounter() {
let count = 0;
return function() {
count++;
console.log(count);
};
}
const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3
在这个示例中,createCounter
函数返回了一个新的函数,这个新函数可以访问createCounter
函数的词法作用域中的count
变量。即使createCounter
函数在其词法作用域之外被调用,count
变量仍然可以被访问和修改。
总结:闭包是JavaScript中一种重要的概念,它可以使得函数访问其词法作用域中的变量,即使这个函数在其词法作用域之外被调用。闭包可以实现模块化和封装,防止全局作用域被污染。
6.前端内存泄漏
js分配内存地址,但长时间没释放导致崩溃的情况。
因素:不声明就赋值、未清除的定时器、闭包、引用元素没有清楚。
前端内存泄漏是指在Web应用程序中,由于程序的错误导致浏览器内存被占用,导致浏览器性能下降,甚至崩溃。
前端内存泄漏的原因有很多,以下是一些常见的原因:
- 意外保存了引用:在函数中保存了对象的引用,但该对象不再被使用,导致内存泄漏。
function saveObject(obj) {
// ...
}
var obj = {
name: "John"
};
saveObject(obj);
- 意外创建了循环引用:两个或多个对象相互引用,导致内存泄漏。
var obj1 = {
name: "John"
};
var obj2 = {
name: "Jane",
friend: obj1
};
obj1.friend = obj2;
- 意外使用了过时的API:使用了过时的API,导致内存泄漏。
var element = document.getElementById("element");
element.style.color = "red";
- 意外使用了全局变量:全局变量在页面关闭时不会被释放,导致内存泄漏。
var globalVar = "some value";
- 没有正确释放对象:在不再需要对象时,没有正确释放对象,导致内存泄漏。
var obj = {
name: "John"
};
// ...
obj = null;
要检测内存泄漏,可以使用浏览器的开发者工具,如Chrome DevTools的Memory标签。通过Memory标签,可以查看当前页面的内存使用情况,包括对象数量、大小等,还可以进行内存泄漏检测。
7.事件委托
原理:事件冒泡机制,子元素事件绑定到父元素身上
阻止事件冒泡 event.stopPropagation()
事件委托是指将事件监听器绑定到父元素上,通过事件冒泡来触发子元素的事件。事件委托可以减少事件监听器的数量,提高性能。
事件委托的原理是事件冒泡,当子元素上的事件触发时,事件会向父元素传递,如果父元素上也有相同类型的事件监听器,那么父元素上的事件监听器也会被触发。
下面是一个简单的例子,使用事件委托实现点击子元素时,父元素上的函数也被触发:
<!DOCTYPE html>
<html>
<head>
<style>
.parent {
background-color: lightblue;
padding: 20px;
}
.child {
background-color: lightcoral;
padding: 20px;
margin: 10px;
}
</style>
</head>
<body>
<div class="parent" id="parent">
<div class="child" id="child1">Child 1</div>
<div class="child" id="child2">Child 2</div>
<div class="child" id="child3">Child 3</div>
</div>
<script>
var parent = document.getElementById("parent");
parent.addEventListener("click", function(event) {
if (event.target.className === "child") {
console.log("Clicked on child element:", event.target.id);
}
});
</script>
</body>
</html>
在这个例子中,我们将事件监听器绑定到父元素parent
上,当点击子元素时,父元素上的事件监听器也会被触发,从而实现事件委托。
总结:事件委托是指将事件监听器绑定到父元素上,通过事件冒泡来触发子元素的事件。事件委托可以减少事件监听器的数量,提高性能。
8.基本数据类型和引用数据类型的区别
JavaScript中有两种数据类型:基本数据类型和引用数据类型。
- 基本数据类型:基本数据类型包括Number、String、Boolean、Undefined和Null。这些数据类型在内存中占用固定大小的空间,可以直接操作。它们保存在栈内存当中,保存的是具体的值。
例如:
var num = 123;
var str = "hello";
var bool = true;
var undef = undefined;
var nullVal = null;
- 引用数据类型:引用数据类型包括Object、Array、Function等。这些数据类型在内存中占用动态分配的空间,不能直接操作。要操作引用数据类型,需要操作其引用(即内存地址)。它们保存在堆内存中,保存的是地址。
例如:
var obj = { name: "John" };
var arr = [1, 2, 3];
var func = function() { console.log("hello"); };
引用数据类型的特点:
-
引用数据类型在内存中占用动态分配的空间,不能直接操作。
-
引用数据类型的值是内存地址,可以通过引用(内存地址)来访问和修改其值。
-
当引用数据类型的值发生变化时,内存地址也会发生变化,因此引用数据类型的值是可变的。
-
引用数据类型可以包含其他引用数据类型,形成嵌套结构。
总结:基本数据类型在内存中占用固定大小的空间,可以直接操作;引用数据类型在内存中占用动态分配的空间,不能直接操作,需要操作其引用(内存地址)。引用数据类型的值是可变的,可以包含其他引用数据类型,形成嵌套结构。
9.原型链
原型链是JavaScript中实现对象间继承的一种方式。在JavaScript中,每个对象都有一个原型(proto),这个原型可以指向另一个对象,从而形成原型链。
当访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript会自动查找该对象的原型,如果原型上有这个属性或方法,则可以直接使用。如果原型上也没有,则继续查找原型的原型,直到找到为止。
下面是一个简单的例子,说明原型链的查找过程:
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function() {
console.log(this.name);
};
function Student(name, grade) {
Person.call(this, name);
this.grade = grade;
}
Student.prototype = new Person();
var student = new Student("Tom", 1);
student.sayName(); // "Tom"
在这个例子中,Student
对象继承了Person
对象,通过原型链的查找过程,Student
对象可以访问Person
对象上的sayName
方法。
原型链的特点:
-
每个对象都有一个原型(proto),可以指向另一个对象。
-
当访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript会自动查找该对象的原型,如果原型上有这个属性或方法,则可以直接使用。
-
如果原型上也没有,则继续查找原型的原型,直到找到为止。
-
原型链的查找过程是沿着原型(proto)属性进行的,直到找到为止。
总结:原型链是JavaScript中实现对象间继承的一种方式,每个对象都有一个原型(proto),可以指向另一个对象。通过原型链的查找过程,可以实现对象间的继承和属性、方法的继承。原型是一个对象,为构造函数的实例共享方法和属性,所有实例引用的都是同一个原型对象。一个实例对象在调用属性和方法的时候,会依次从实例本身、构造函数原型、原型的原型上去查找。
10.new操作符具体做了什么?
new
操作符是JavaScript中用于创建对象的关键字。当使用new
操作符创建一个新对象时,会执行以下步骤:
-
创建一个新的空对象。
-
将新对象的原型(proto)属性设置为构造函数的prototype属性。
-
将构造函数的this指向新对象,执行构造函数的代码,为对象添加属性和方法。
-
返回新对象。
下面是一个简单的例子,说明new
操作符的执行过程:
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function() {
console.log(this.name);
};
var person = new Person("John");
person.sayName(); // "John"
在这个例子中,使用new
操作符创建了一个新的Person
对象,并将Person
构造函数的this
指向新对象,然后执行构造函数的代码,为对象添加了name
属性和sayName
方法。最后返回新对象。
总结:new
操作符用于创建对象,执行过程包括创建一个新的空对象、设置原型(proto)属性、执行构造函数的代码、返回新对象。通过new
操作符,可以实现对象间的继承和属性、方法的继承。
11.js是如何实现继承的
JavaScript中实现继承的方式主要有两种:原型链继承和构造函数继承。
- 原型链继承:通过将一个对象的 prototype 属性设置为另一个对象的实例,从而实现继承。
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function() {
console.log(this.name);
};
function Student(name, grade) {
Person.call(this, name);
this.grade = grade;
}
Student.prototype = new Person();
var student = new Student("Tom", 1);
student.sayName(); // "Tom"
在这个例子中,Student
对象继承了Person
对象,通过原型链的查找过程,Student
对象可以访问Person
对象上的sayName
方法。
- 构造函数继承:通过在子构造函数中调用父构造函数,并将返回值作为子构造函数的实例,从而实现继承。
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function() {
console.log(this.name);
};
function Student(name, grade) {
Person.call(this, name);
this.grade = grade;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
var student = new Student("Tom", 1);
student.sayName(); // "Tom"
在这个例子中,Student
对象继承了Person
对象,通过构造函数的调用过程,Student
对象可以访问Person
对象上的sayName
方法。
总结:JavaScript中实现继承的方式主要有两种:原型链继承和构造函数继承。原型链继承通过将一个对象的 prototype 属性设置为另一个对象的实例实现继承;构造函数继承通过在子构造函数中调用父构造函数,并将返回值作为子构造函数的实例实现继承。
原型链继承
借用构造函数继承
组合式继承
es6的class类继承
12.js中this指向问题
在JavaScript中,this
关键字指向当前对象,即当前函数所在的对象。this
的指向取决于函数的调用方式。
- 普通函数调用:
this
指向全局对象(window)。
function sayHello() {
console.log("Hello, " + this.name);
}
var name = "John";
sayHello(); // "Hello, John"
- 方法调用:
this
指向调用该方法的对象。
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log("Hello, " + this.name);
};
var person = new Person("John");
person.sayHello(); // "Hello, John"
- 事件处理函数:
this
指向触发事件的元素。
<button id="btn">Click me</button>
document.getElementById("btn").addEventListener("click", function() {
console.log("Clicked: " + this.id);
});
- 箭头函数:
this
指向箭头函数所在的对象。
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = () => {
console.log("Hello, " + this.name);
};
var person = new Person("John");
person.sayHello(); // "Hello, John"
总结:在JavaScript中,this
关键字指向当前对象,即当前函数所在的对象。this
的指向取决于函数的调用方式。普通函数调用时,this
指向全局对象(window);方法调用时,this
指向调用该方法的对象;事件处理函数时,this
指向触发事件的元素;箭头函数时,this
指向箭头函数所在的对象。
全局对象中的this指向的是window
普通函数中this指向全局window
this永远指向最后调用它的对象(非箭头函数)
new关键词改变了this的指向
apply、call、bind可以改变this指向(非箭头函数)
箭头函数中的this指向是其父类的this
匿名函数中的this指向window
13.script中async和defer有什么区别
在HTML中,<script>
标签可以包含async
和defer
属性,它们用于控制脚本的加载和执行顺序。
async
属性:async
属性用于异步加载脚本,当脚本加载时不会阻塞页面其他内容的加载,但加载完成后会立即执行。
<script async src="script.js"></script>
defer
属性:defer
属性用于延迟加载脚本,当脚本加载时不会阻塞页面其他内容的加载,但会在页面加载完成后执行。
<script defer src="script.js"></script>
总结:async
属性用于异步加载脚本,当脚本加载时不会阻塞页面其他内容的加载,但加载完成后会立即执行;defer
属性用于延迟加载脚本,当脚本加载时不会阻塞页面其他内容的加载,但会在页面加载。
没有async和defer,浏览器会立刻加载并执行指定脚本。
有async,异步的,加载和渲染元素的过程将和script的加载和执行并行进行。
有defer,加载和渲染元素的过程将和script的加载并行进行,所有元素解析之后才执行。
14.setTimeout最小执行时间
setTimeout:4ms
setInterval:10ms
setTimeout
是 JavaScript 中的一个函数,用于在指定的毫秒数后执行一段代码。但是,如果在指定的毫秒数内,函数已经执行完毕,那么 setTimeout
会将这段代码延迟到下一个宏任务(例如 setTimeout
本身)的开始执行。
因此,setTimeout
的最小执行时间实际上是 4 毫秒。这是因为,浏览器为了保证页面流畅滚动,会强制将小于 4 毫秒的延迟任务放到下一个宏任务中执行。
需要注意的是,这个最小执行时间是在现代浏览器中观察到的,可能随着浏览器和操作系统的更新而改变。
15.ES5和ES6的区别ECMAScript
ECMAScript(ES)是一种由ECMA-262标准定义的脚本语言,JavaScript是ES的一种实现。ES5和ES6是两个不同的版本,它们之间存在一些区别。
- 变量声明:ES5中,变量需要使用
var
关键字声明,而ES6中,可以使用let
、const
和var
关键字声明变量。
ES5:
var a = 1;
var b = 2;
ES6:
let a = 1;
const b = 2;
- 函数声明:ES5中,函数需要使用
function
关键字声明,而ES6中,可以使用function
、let
、const
和class
关键字声明函数。
ES5:
function a() {
console.log(1);
}
ES6:
let a = function() {
console.log(1);
};
- 对象字面量:ES5中,对象字面量需要使用
var
或function
关键字声明,而ES6中,可以使用let
、const
和class
关键字声明对象字面量。
ES5:
var obj = {
name: "John"
};
ES6:
let obj = {
name: "John"
};
- 箭头函数:ES6中引入了箭头函数,可以更简洁地定义函数。
ES6:
const add = (a, b) => a + b;
- 模板字符串:ES6中引入了模板字符串,可以更方便地拼接字符串。
ES6:
const name = "John";
const greeting = `Hello, ${name}!`;
总结:ES5和ES6之间存在一些区别,主要体现在变量声明、函数声明、对象字面量、箭头函数和模板字符串等方面。ES6中引入了一些新的特性,使得JavaScript更加简洁和强大。
16.ES6的新特性
ECMAScript 6(简称 ES6)是 JavaScript 的一个重要版本,它引入了许多新的特性,使得 JavaScript 更加简洁和强大。以下是一些 ES6 的新特性:
- 变量声明:ES6 中可以使用
let
、const
和class
关键字声明变量,而不再需要使用var
关键字。
let a = 1;
const b = 2;
class C {}
- 函数声明:ES6 中可以使用
function
、let
、const
和class
关键字声明函数。
function foo() {
console.log(1);
}
let bar = function() {
console.log(2);
};
- 箭头函数:ES6 中引入了箭头函数,可以更简洁地定义函数。
const add = (a, b) => a + b;
- 模板字符串:ES6 中引入了模板字符串,可以更方便地拼接字符串。
const name = "John";
const greeting = `Hello, ${name}!`;
- 解构赋值:ES6 中引入了解构赋值,可以更方便地从对象中获取值。
const obj = {
name: "John",
age: 30
};
const { name, age } = obj;
- 剩余参数:ES6 中引入了剩余参数(…rest),可以更方便地处理函数参数。
function sum(...numbers) {
return numbers.reduce((acc, num) => acc + num, 0);
}
- 扩展运算符:ES6 中引入了扩展运算符(…),可以更方便地合并数组。
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];
- Promise:ES6 中引入了 Promise,可以更方便地处理异步操作。
async function fetchData() {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
console.log(data);
}
- async/await:ES6 中引入了 async/await,可以更方便地编写异步代码。
async function fetchData() {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
console.log(data);
}
总结:ES6 引入了许多新的特性,使得 JavaScript 更加简洁和强大。这些特性包括变量声明、函数声明、箭头函数、模板字符串、解构赋值、剩余参数、扩展运算符、Promise、async/await 等。
17.call、apply、bind的区别
call
、apply
和 bind
是 JavaScript 中的三个方法,它们都可以改变函数的 this
指向。但是它们之间存在一些区别。
- 参数:
call
和apply
都需要传入一个函数和一个this
值,而bind
需要传入一个函数和一个this
值,以及一个可选的参数数组。
call
示例:
function add(a, b) {
return a + b;
}
add.call(1, 2); // 3
apply
示例:
function add(a, b) {
return a + b;
}
add.apply(1, [2]); // 3
bind
示例:
function add(a, b) {
return a + b;
}
const add5 = add.bind(1, 5);
add5(2); // 7
- 返回值:call 和 apply 都会直接执行函数,并返回函数的执行结果。而 bind 会返回一个新的函数,这个新函数的 this 值被绑定到指定的 this 值,并传入指定的参数数组。
总结:call、apply 和 bind 都可以改变函数的 this 指向,但它们之间存在一些区别。call 和 apply 直接执行函数并返回函数的执行结果,而 bind 返回一个新的函数,这个新函数的 this 值被绑定到指定的 this 值,并传入指定的参数数组。
都是改变this指向和函数的调用
call、apply功能类似,只是传参方法不同
call传参是一个参数列表
apply传参是一个数组
bind传参后不会立刻执行,会返回一个改变this指向的函数,不能去做构造函数,这个函数可以传参bind()()
call方法性能比较好用的多
18.箭头函数和普通函数区别?
箭头函数是 ECMAScript 6(ES6)中引入的一种新的函数语法,它使得函数更加简洁和易读。箭头函数与普通函数存在一些区别。
- 语法:箭头函数的语法更加简洁,它使用一个箭头(=>)来表示函数的参数和返回值。
普通函数:
function add(a, b) {
return a + b;
}
箭头函数:
const add = (a, b) => a + b;
- 简写:箭头函数可以省略
return
关键字,如果函数体只有一行代码,并且是返回值。
普通函数:
function add(a, b) {
return a + b;
}
箭头函数:
const add = (a, b) => a + b;
- 绑定
this
:箭头函数会自动绑定this
值,即箭头函数所在的对象。
普通函数:
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`Hello, ${this.name}!`);
};
const person = new Person("John");
person.sayHello(); // "Hello, John!"
箭头函数:
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = () => {
console.log(`Hello, ${this.name}!`);
};
const person = new Person("John");
person.sayHello(); // "Hello, John!"
总结:箭头函数与普通函数存在一些区别,包括语法、简写和绑定 this
值。箭头函数使得函数更加简洁和易读,并且具有自动绑定 this
值的功能。
19.let const var区别
let
、const
和 var
是 JavaScript 中的三个关键字,用于声明变量。它们之间存在一些区别。
- 作用域:
let
和const
声明的变量具有块级作用域,即变量仅在声明所在的代码块(如循环、函数等)内有效。而var
声明的变量具有函数级作用域,即变量在整个函数内有效。
let
和 const
:
function example() {
let a = 1;
const b = 2;
if (true) {
let a = 3;
const b = 4;
console.log(a, b); // 3 4
}
console.log(a, b); // 3 4
}
example();
var
:
function example() {
var a = 1;
var b = 2;
if (true) {
var a = 3;
var b = 4;
console.log(a, b); // 3 4
}
console.log(a, b); // 3 4
}
example();
- 变量提升:
let
和const
声明的变量会发生变量提升,即在声明之前就可以访问到变量。而var
声明的变量不会发生变量提升。
let
和 const
:
console.log(a); // undefined
let a = 1;
console.log(a); // 1
var
:
console.log(a); // undefined
var a = 1;
console.log(a); // 1
- 重新赋值:
let
和const
声明的变量可以重新赋值,而var
声明的变量可以重新赋值,也可以删除。
let
和 const
:
let a = 1;
a = 2;
console.log(a); // 2
var
:
var a = 1;
a = 2;
console.log(a); // 2
总结:let
、const
和 var
都是用于声明变量的关键字,但它们之间存在一些区别。let
和 const
具有块级作用域,会发生变量提升,不能重新赋值;而 var
具有函数级作用域,不会发生变量提升,可以重新赋值也可以删除。
let const:不存在变量提升、必须定义才能使用、存在暂时性死区的问题、存在块级作用域的内容、不能在同一个作用域内重复声明
var:存在变量提升
20.如何实现深拷贝
实现深拷贝的方法有很多,下面介绍两种常用的方法:
- 使用第三方库:可以使用一些第三方库,如 lodash 的
_.cloneDeep
方法,实现深拷贝。
const _ = require('lodash');
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = _.cloneDeep(obj1);
console.log(obj1); // { a: 1, b: { c: 2 } }
console.log(obj2); // { a: 1, b: { c: 2 } }
console.log(obj1.b === obj2.b); // false
- 使用递归实现深拷贝:
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
let copy = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepClone(obj[key]);
}
}
return copy;
}
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = deepClone(obj1);
console.log(obj1); // { a: 1, b: { c: 2 } }
console.log(obj2); // { a: 1, b: { c: 2 } }
console.log(obj1.b === obj2.b); // false
注意:这两种方法都不是万能的,有些特殊对象(如函数、循环引用的对象等)可能无法完全深拷贝。在实际使用中,需要根据具体需求选择合适的方法。
完全拷贝一个新的对象,在堆内存中开辟新的空间
主要针对引用类型数据
扩展运算符 {…obj}只能实现第一层
json.parse(json.stringify()),不会拷贝内部函数
利用递归函数实现
21.ajax是什么
AJAX(Asynchronous JavaScript and XML)是一种在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页内容的技术。通过 AJAX,前端可以异步地向服务器发送请求,获取数据,然后更新页面,从而提高用户体验。
AJAX 的核心是 XMLHttpRequest 对象,它可以在后台与服务器交换数据,而不会影响用户正在查看的页面。随着 HTML5 的普及,现在可以使用 Fetch API 等更现代的方法来实现 AJAX。
下面是一个简单的 AJAX 示例:
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
console.log(data);
});
这个示例使用 Fetch API 向服务器发送请求,获取 JSON 数据,然后将数据输出到控制台。
总结:AJAX 是一种与服务器交换数据的技术,可以异步地更新网页内容,提高用户体验。AJAX 的核心是 XMLHttpRequest 对象,现在可以使用 Fetch API 等更现代的方法来实现 AJAX。
创建交互式网页应用的网页开发技术
在不重新加载整个网页的前提下,与服务器交换数据并更新部分内容
通过xmlhttprequest对象向服务器发送异步请求,从服务器拿到数据之后,通过js操作dom的方式操作页面
创建xmlhttprequst对象
通过xmh对象里的open()方法和服务器建立连接
构建请求所需的数据,并通过xmh的send()发送给服务器
通过xmh的onreadystate change事件监听服务器和你的通信状态
接受并处理服务器响应的数据结果
把处理的数据更新到html页面上
22.get和post的区别
GET
和 POST
是 HTTP 请求中的两种方法,它们的主要区别如下:
- 请求方式:
GET
请求会将请求的数据附在 URL 中,以?
符号分割,多个参数之间用&
符号分割;POST
请求会将请求的数据放在请求体中,以application/x-www-form-urlencoded
格式编码。
GET
请求示例:
GET /search?q=javascript&type=software
POST
请求示例:
POST /search
Content-Type: application/x-www-form-urlencoded
q=javascript&type=software
-
数据大小:
GET
请求的数据大小有限制,通常不能超过 2KB;POST
请求的数据大小没有限制。 -
安全性:
GET
请求会将请求的数据暴露在 URL 中,可能会导致安全问题;POST
请求会将请求的数据放在请求体中,相对安全。 -
幂等性:
GET
请求具有幂等性,即多次请求同一个 URL,得到的结果是一样的;POST
请求不具有幂等性,多次请求同一个 URL,可能会得到不同的结果。
总结:GET
和 POST
是 HTTP 请求中的两种方法,主要区别在于请求方式、数据大小、安全性以及幂等性。在实际使用中,需要根据具体需求选择合适的请求方法。
get参数会放在url上,安全性比较差,post是放在body中的
get刷新服务器或退回是没有影响的,post则会重新提交
get请求会被缓存,post则不会
get请求会保存在浏览器的历史记录中,post不会
get只能进行url编码,post则支持很多种
23.promise的内部原理和优缺点
Promise 是 JavaScript 中用于处理异步操作的一种对象,它表示一个异步操作的最终结果。Promise 的内部原理主要是通过状态机来实现的,它有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。Promise 的优点是统一了异步操作的处理方式,提供了链式调用和错误处理等功能,提高了代码的可读性和可维护性。
Promise 的内部原理主要是通过状态机来实现的,它有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。Promise 的构造函数接收一个函数参数,这个函数有两个参数,分别是 resolve 和 reject,它们分别用于将 Promise 的状态从 pending 变为 fulfilled 和从 pending 变为 rejected。
Promise 的优点是统一了异步操作的处理方式,提供了链式调用和错误处理等功能,提高了代码的可读性和可维护性。但是,Promise 也有一些缺点,例如:
-
无法取消:一旦创建了一个 Promise,就无法取消它,即使它还没有开始执行。
-
错误处理:Promise 的错误处理比较复杂,需要使用
catch
方法或者then
方法的第二个参数来处理错误。 -
代码可读性:Promise 的代码可读性不如传统的回调函数,特别是对于初学者来说,可能会导致代码难以理解。
总结:Promise 是 JavaScript 中用于处理异步操作的一种对象,它表示一个异步操作的最终结果。Promise 的内部原理主要是通过状态机来实现的,它有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。Promise 的优点是统一了异步操作的处理方式,提供了链式调用和错误处理等功能,提高了代码的可读性和可维护性。但是,Promise 也有一些缺点,例如无法取消、错误处理复杂和代码可读性差等。
Promise对象封装了一个异步操作并且还可以获取成功或失败的结果。
Promise对象主要解决回调地狱的问题,之前如果异步任务比较多,同时他们之间有相互的依赖关系,就只能使用回调函数处理,这样就容易形成回调地狱,代码的可读性、可维护性差。
有三种状态:pending初始状态,fulfilled成功状态,rejected失败状态。
原理:构造一个promise实例,实例需要传递参数,这个参数有两个形参,分别都是函数类型,一个是resolve一个是reject。
promise上还有then方法,用来指定状态改变时的确定操作,resolve是执行第一个函数,reject是执行第二个。
24.promise和async await的区别
Promise 和 async/await 是 JavaScript 中处理异步操作的两种不同方式。Promise 是基于回调函数的,而 async/await 是基于生成器的。
Promise 的主要优点是统一了异步操作的处理方式,提供了链式调用和错误处理等功能,提高了代码的可读性和可维护性。但是,Promise 也有一些缺点,例如无法取消、错误处理复杂和代码可读性差等。
async/await 是基于生成器的,它可以将异步操作转换为同步操作,使得代码更加简洁和易读。但是,async/await 也有一些缺点,例如:
-
无法处理多个异步操作:async/await 只能处理一个异步操作,如果要处理多个异步操作,需要使用
Promise.all
等方法。 -
无法处理错误:async/await 无法直接处理错误,需要使用 try/catch 等语句来处理错误。
-
性能问题:async/await 可能会导致性能问题,因为它会阻塞主线程,直到异步操作完成。
总结:Promise 和 async/await 都是 JavaScript 中处理异步操作的两种方式,它们各有优缺点。在实际使用中,需要根据具体需求选择合适的处理方式。
都是处理异步请求
promise是es6,async await是es7
async await是基于promise实现的,都是非阻塞性的
优缺点:
promise是返回对象,要用then,catch的方法处理和捕获异常,并且书写方式是链式,容易造成代码重叠,可维护性差
async await是通过try catch进行捕获异常
async await能让代码看起来像同步一样,只要遇到await就会立刻返回结果,然后在执行后面的操作
promise.then()会出现请求还没返回,就执行了后面操作
25.浏览器的存储方式
浏览器的存储方式主要有以下几种:
- 本地存储:本地存储将数据存储在本地,即使浏览器关闭,数据也不会丢失。本地存储通常使用
localStorage
对象来实现。
localStorage.setItem('key', 'value');
const value = localStorage.getItem('key');
- sessionStorage:sessionStorage 将数据存储在当前会话中,当会话结束(通常是窗口或标签页关闭)时,数据将被清除。sessionStorage 通常用于存储跨页面的数据。
sessionStorage.setItem('key', 'value');
const value = sessionStorage.getItem('key');
- 缓存:缓存将数据存储在浏览器中,以便在下次访问时更快地加载页面。缓存通常使用
cache
对象来实现。
caches.open('my-cache').then((cache) => {
cache.addAll([
'/',
'/index.html',
'/styles.css',
'/script.js',
]);
});
- IndexedDB:IndexedDB 是一个轻量级的数据库,可以在浏览器中存储大量数据。IndexedDB 通常用于存储复杂的数据结构,如对象和图形。
const db = openDatabase('my-db', '1.0', 'My Database', 2 * 1024 * 1024);
db.transaction((tx) => {
tx.executeSql('CREATE TABLE IF NOT EXISTS my-table (id INTEGER PRIMARY KEY, name TEXT)');
tx.executeSql('INSERT INTO my-table (name) VALUES (?)', ['John']);
});
总结:浏览器的存储方式主要有本地存储、sessionStorage、缓存和 IndexedDB。本地存储和 sessionStorage 用于存储简单的键值对,缓存用于存储网页资源,IndexedDB 用于存储复杂的数据结构。
cookie 标准本地存储方式,兼容性好,存储量小,需要封装
localstorage 以键值对标准的人方式、操作方便、永久存储、兼容性好,保存值的类型被限定,隐私模式下不可读取
sessionstorage 页面关闭就会清理,会话级别的存储方式
indexedDB 键值对方式存储,存储方便
26.token存在哪儿
Token 通常存储在浏览器的本地存储或 sessionStorage 中。Token 是一种身份验证方式,用于确保用户请求的合法性。将 Token 存储在本地存储或 sessionStorage 中,可以在用户多次请求时自动携带 Token,避免每次请求都重新登录。
以下是将 Token 存储在本地存储和 sessionStorage 中的示例:
// 将 Token 存储在本地存储
localStorage.setItem('token', 'your-token');
// 从本地存储获取 Token
const token = localStorage.getItem('token');
// 将 Token 存储在 sessionStorage
sessionStorage.setItem('token', 'your-token');
// 从 sessionStorage 获取 Token
const token = sessionStorage.getItem('token');
注意:Token 存储在本地存储或 sessionStorage 中时,需要确保在用户登录时设置 Token,并在用户注销时清除 Token。
token:验证身份的令牌
如果存在localdtorage里,每次请求接口都要把它当作字段传给后台,容易被xss攻击
存在cookie中,会自动发送,但是不能跨域
27.页面渲染过程
页面渲染过程是指浏览器将 HTML 代码转换为可视化页面的一系列步骤。页面渲染过程主要可以分为以下几个阶段:
-
解析 HTML:浏览器将 HTML 代码解析成 DOM 树。DOM 树是一个树形结构,表示页面上所有的 HTML 元素。
-
解析 CSS:浏览器将 CSS 代码解析成 CSSOM 树。CSSOM 树是一个树形结构,表示页面上所有的 CSS 样式。
-
解析 JavaScript:浏览器将 JavaScript 代码解析成可执行的代码。
-
渲染:浏览器将解析好的 DOM 树和 CSSOM 树结合,生成渲染树。渲染树是一个线性结构,表示页面上可见的元素及其顺序。
-
布局:浏览器根据渲染树计算每个元素的尺寸和位置,并将它们放置在正确的位置。
-
绘制:浏览器将渲染树中的元素绘制到屏幕上,形成可视化页面。
总结:页面渲染过程主要可以分为解析 HTML、解析 CSS、解析 JavaScript、渲染、布局和绘制六个。
dns解析
建立tcp连接
发送http请求
服务器处理请求
渲染页面
浏览器会获取html和css资源,把html解析成dom树
再把css解析成cssom
把dom和cssom合并为渲染树
吧渲染树的每个节点渲染到页面
断开tcp连接
三、HTML/CSS
1.html5、css3有哪些新特性
HTML5:
-
语义化:HTML5 提供了更多的语义化元素,如
<header>
、<nav>
、<section>
、<article>
等,使得页面结构更加清晰。 -
媒体元素:HTML5 新增了
<video>
和<audio>
元素,可以方便地播放音频和视频。 -
表单元素:HTML5 新增了
<input>
元素的多种新类型,如email
、url
、number
、range
等,使得表单验证更加方便。 -
拖放功能:HTML5 提供了拖放功能,可以方便地实现页面元素的拖放操作。
-
离线存储:HTML5 提供了离线存储技术,如
localStorage
和sessionStorage
,使得页面可以在离线状态下正常访问。
CSS3:
-
选择器:CSS3 提供了更多的选择器,如属性选择器、伪类选择器、伪元素选择器等,可以更方便地选择页面元素。
-
样式:CSS3 提供了更多的样式特性,如
box-sizing
、border-radius
、transform
等,可以实现更多的视觉效果。 -
动画:CSS3 提供了动画功能,可以使用
@keyframes
规则定义动画,使用animation
属性应用动画。 -
响应式设计:CSS3 提供了响应式设计技术,如
@media
规则、flexbox
和grid
布局等,可以实现不同设备上的自适应布局。 -
变量:CSS3 提供了变量功能,可以使用
var()
函数定义全局变量,方便在整个项目中使用。