自我介绍+项目
知识图谱
开发文档
设计模式
let
和const
关键字声明变量,实现了块级作用域。反引号
和${}
实现更方便的字符串拼接和表达式注入。class
和extends
关键字,支持面向对象的编程方式。import
和export
关键字实现模块化开发,可以更方便地管理代码和依赖。导入improt、导出export defaultfor…of
循环 ES6引入了for…of循环,它可用于迭代数组,字符串,Map
,Set
和其他可迭代的对象。forEach
、filter
过滤事件循环是指一种机制,用于处理 JavaScript 中的异步任务,例如定时器任务、网络请求等。JavaScript 引擎会在主线程中维护一个任务队列,其中包括宏任务和微任务,事件循环会不断地从这个任务队列中取出一个任务来执行,直到队列为空。
宏任务指的是一些需要在主线程中执行的任务,例如 setTimeout、setInterval、I/O 操作等。当这些任务加入到任务队列中时,事件循环会等待主线程空闲时再去执行它们。
微任务指的是一些需要在当前任务执行结束后立即执行的任务,例如 Promise 的 resolve 和 reject 回调函数、MutationObserver 的回调函数等。当这些任务加入到任务队列中时,事件循环会尽快执行它们。在执行宏任务时,如果它产生了微任务,那么这些微任务会先被放入到微任务队列中,等待当前宏任务执行完毕后立即执行。
总结一下,事件循环是一种处理 JavaScript 中异步任务的机制,包括宏任务和微任务,其中微任务具有优先级。在执行宏任务时,如果它产生了微任务,那么这些微任务会被放到微任务队列中等待执行。
最后一个没有的原因是因为resolve()的意思是把 Promise对象实例的状态从pending变成 fulfilled(即成功),最后一个没有执行resolve()即不执行then
例题
例题
arguments
对象,但是可以使用rest
参数来取代arguments对象。new
关键字调用,因为它没有this绑定。Generator
函数,而普通函数可以。总的来说,箭头函数比普通函数更为简洁和直观,可以使代码更为易读易懂。但是,在一些特殊情况下,比如需要使用this或者需要定义构造函数时,仍然需要使用普通函数。
this 箭头函数中的this
var,let和const都是用于声明变量的关键字,但是它们有一些区别。
var:在ES5中,var是用于声明变量的关键字,它的作用域是函数作用域或全局作用域。在ES6中,var仍然可以使用,但是一般不推荐使用,因为它容易造成变量的提升问题。
let:在ES6中,let是用于声明变量的关键字,它的作用域是块级作用域。它可以重新赋值,但不能重新声明已经存在的变量。
const:在ES6中,const也是用于声明变量的关键字,但它用于声明常量。它的作用域也是块级作用域,并且不能重新赋值,也不能重新声明已经存在的常量。
由此可见,使用let和const可以避免变量提升问题,让代码更加安全可靠。同时,合理使用const还可以让代码更加易于维护。
暂时性死区
暂时性死区(Temporal Dead Zone,简称TDZ)是指在ES6规范中,let和const声明的变量,在声明前使用会抛出异常,这个异常即为暂时性死区。
具体来说,JS引擎会在代码块开始执行前,扫描该块内的所有变量声明(var声明的变量除外),将这些变量全部记录在一个地方,然后就可以开始执行代码了。如果在代码块中使用了一个尚未声明的变量,JS引擎会根据这个变量所出现的位置,去查询这个变量是否存在记录中。如果记录中存在这个变量,JS引擎就会将这个变量的值设置为undefined。但是,如果这个变量在记录中不存在,JS引擎就会抛出一个ReferenceError错误,表示这个变量在暂时性死区中。
以下是一个例子:
console.log(x); // ReferenceError: x is not defined
let x = 'hello';
在这个例子中,由于x变量在声明前被使用了,所以会抛出一个ReferenceError错误。这是因为let声明的变量在声明前不会被赋值,也不会被提升,而是处于暂时性死区中。
function sum(...args) {
return args.reduce((total, num) => total + num);
}
console.log(sum(1, 2, 3)); //输出6
console.log(sum(1, 2, 3, 4, 5)); //输出15
function add(a, b, c) {
return a + b + c;
}
const arr = [1, 2, 3];
console.log(add(...arr)); //输出6
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [...arr1, ...arr2];
console.log(arr3); //输出[1, 2, 3, 4, 5, 6]
const person = { name: 'Alice', age: 25, address: '123 Main Street' };
const { name, ...rest } = person;
console.log(name); //输出Alice
console.log(rest); //输出{ age: 25, address: '123 Main Street' }
const obj = { a: 1, b: 2, c: 3 };
const { a, b } = obj;
console.log(a, b); // 输出:1 2
在上面的代码中,对象 obj 中的属性 c 并没有被读取,也没有被分配到任何对象中。
然而,在解构赋值时,我们可以通过使用 ...
操作符来捕获这些未被读取的属性,并将它们分配到指定的对象上。例如:
const obj = { a: 1, b: 2, c: 3 };
const { a, ...rest } = obj;
console.log(a, rest); // 输出:1 { b: 2, c: 3 }
Object.assign()
:将一个或多个对象的属性复制到另一个对象中。Object.keys()
:返回一个给定对象的所有可枚举属性的数组。Object.values()
:返回一个给定对象的所有可枚举属性值的数组。Object.entries()
:返回一个给定对象的所有可枚举属性的键值对数组。Object.is()
:比较两个值是否相等。Object.setPrototypeOf()
:设置一个对象的原型(即__proto__属性)。Object.getOwnPropertySymbols()
:返回一个给定对象自身的所有symbol属性的数组。Object.getOwnPropertyDescriptors()
:返回一个给定对象自身的所有属性的描述符对象。Object.fromEntries()
:将一个键值对的数组转换成一个对象。Array.from()
:将类数组对象或可迭代对象转换为真正的数组。Array.of()
:创建一个新数组,无论传入的参数数量或类型如何,都将其作为一个元素添加到数组中。Array.prototype.fill()
:使用指定的值填充数组中的所有元素。Array.prototype.find()
:返回第一个满足条件的数组元素。Array.prototype.findIndex()
:返回第一个满足条件的数组元素的索引。Array.prototype.keys()
:返回一个新的Array Iterator对象,该对象包含数组中每个索引的键。Array.prototype.values()
:返回一个新的Array Iterator对象,该对象包含数组中每个索引的值。Array.prototype.entries()
:返回一个新的Array Iterator对象,该对象包含数组中每个索引的键/值对。Set和Map是ES6中新增的数据结构。
Set是一种无序且不重复的集合,其中的每个元素都是唯一的,可以用来存储一些不需要重复的数据,如数字、字符串等。Set具有以下特点:
Set的创建方法如下:
let set = new Set(); // 创建一个空的Set
let set = new Set([1, 2, 3]); // 创建一个包含1、2、3的Set
Set的常用方法包括:
Map是一种键值对的集合,其中的每个元素都由键和值组成,可以用来存储一些键值对的数据,如对象、字符串等。Map具有以下特点:
Map的创建方法如下:
let map = new Map(); // 创建一个空的Map
let map = new Map([
["name", "张三"],
["age", 18],
["gender", "男"]
]); // 创建一个包含三个键值对的Map
Map的常用方法包括:
for...of
不能循环普通对象,需要和Object.keys()
搭配使用for...in
,在遍历数组的时候的时候使用for...of
for...of
循环用于遍历可迭代对象(如数组、字符串、Set、Map等),它会循环遍历集合中的值,而不是索引。语法如下:
for (const element of iterable) {
// do something with element
}
for...in
循环用于遍历对象的可枚举属性(包括自身的属性和继承的属性),它会循环遍历对象的属性名。语法如下:
for (const key in object) {
if (object.hasOwnProperty(key)) {
// do something with object[key]
}
}
总之,for...of
用于遍历集合中的值,for...in
用于遍历对象的属性名。
相同
每次执行匿名函数都支持3个参数,参数分别是item(当前每一项)、index(索引值)、arr(原数组)
不同
map
方法将数组中的每个元素按照一定规则进行转换,并返回一个新的数组,不改变原数组。例如,将一个数组中的每个元素都加1,即可使用如下代码:
const arr = [1, 2, 3, 4];
const newArr = arr.map(item => item + 1);
console.log(newArr); // [2, 3, 4, 5]
forEach
方法则是遍历数组中的每个元素,并执行指定的函数回调。例如,将一个数组中的每个元素都打印出来,即可使用如下代码:
const arr = [1, 2, 3, 4];
arr.forEach(item => console.log(item));
其中的函数回调可以接受三个参数:当前元素、当前元素的索引和数组本身。例如:
const arr = [1, 2, 3, 4];
arr.forEach((item, index, array) => console.log(item, index, array));
需要注意的是,map
方法返回一个新的数组,而forEach
方法并不返回任何值。
promise
是ES6的内容,async/await
属于ES7.then
,又可以用try-catch
捕捉添加链接描述
面向对象编程中,有三个基本特征,即:封装、继承和多态。
这三个特征是面向对象编程的基础,也是代码设计的关键。在面向对象编程的实践中,要充分运用封装、继承和多态,为代码设计提供更好的抽象和封装。
document.getElementById(id)
方法,其中id
为元素的ID属性值。document.getElementsByTagName(tagName)
方法,其中tagName
为标签名称。document.getElementsByClassName(className)
方法,其中className
为元素的class属性值。document.getElementsByName(name)
方法,其中name
为元素的name属性值。document.querySelector(selector)
方法,其中selector
为CSS选择器表达式。document.querySelectorAll(selector)
方法,其中selector
为组合的CSS选择器表达式。document.querySelector('[attribute=value]')
方法,其中attribute
为自定义属性名,value
为属性值。let arr = [1, 1, 2, 2, 3, 3];
let newArr = [...new Set(arr)];
console.log(newArr); // [1, 2, 3]
let arr = [1, 1, 2, 2, 3, 3];
let newArr = [];
for(let i = 0; i < arr.length; i++){
if(newArr.indexOf(arr[i]) === -1){
newArr.push(arr[i]);
}
}
console.log(newArr); // [1, 2, 3]
第二种方式比第一种方式效率低,因为它需要使用循环来检查元素是否已经存在于新数组中。而第一种方式使用Set对象会自动去重,所以速度更快。
在JavaScript中,null
和undefined
都代表着没有值的情况,但是两者有一些细微的区别:
undefined
表示声明了变量但没有给它赋值,或者对象的属性没有被赋值。null
表示变量被明确地赋值为null,表示该变量的值为空。总之,null
是在程序中明确地将变量赋值为空,而undefined
是在程序中隐式地将变量赋值为空。
数组和类数组的区别主要在于它们的数据类型和属性。
数组
是 JavaScript 中的一种数据结构,它可以存储多个值,并且这些值都是同一种数据类型。而类数组
是指一组类似数组的对象,它们有类似数组的属性和方法,但并不是真正的数组,因为它们没有完整的数组功能和方法。数组
具有一系列的属性,如 length 属性表示数组的长度,而类数组
一般没有 length 属性,如果想获取类数组的长度,需要使用类数组的相关方法来实现。数组
拥有很多方法,如 push()、pop()、shift()、unshift()等,而类数组
只有部分数组方法可以使用,如 slice()、splice()等。总的来说,数组
是一种完整的数据类型,有自己的属性和方法,而类数组
只是一组具有类似数组属性和方法的对象,不能完全替代数组。
添加链接描述
JavaScript闭包是指函数可以访问其创建时所在的词法作用域,即使函数在其创建时已经离开了该作用域。换句话说,闭包可以“记住”其创建时的环境。
闭包的常见用途包括:
保护变量:使用闭包可以避免变量被外界修改,增加代码的稳定性和可靠性。
延长变量生命周期:闭包可以使得在函数执行完毕后仍能访问到函数内部的变量,从而实现变量的长期存储。
封装私有变量:使用闭包可以将变量封装在函数内部,外部无法访问,从而提高代码的安全性。
实现模块化:通过使用闭包可以实现类似于模块化的功能,将一些变量和方法封装在函数内部,从而避免全局命名冲突。
函数内套函数
嵌套函数可访问声明于它们外部作用域的变量
能够读取其他函数内部变量的函数 或者子函数在外调用,子函数所在的父函数的作用域不会被释放
add5 是等于 makeAdder(5) 返回的 function(y),所以add5(2)为function(2)因为闭包,能访问外层x=5,所以return x+y=7
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5); //add5 是等于 makeAdder(5) 返回的 function(y),所以add5(2)为function(2)因为闭包,能访问外层x=5,所以return x+y=7
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5)(40);
var add10 = makeAdder(10);
console.log(add5); // 45
console.log(add10(2)); // 12
在JavaScript中,可以使用闭包来实现私有变量。
function Person(name) {
var age = 0; //私有变量
function increaseAge() { //私有方法
age++;
}
//返回对象
return {
getName: function() {
return name;
},
getAge: function() {
return age;
},
grow: function() {
increaseAge();
}
}
}
var person = Person('张三');
console.log(person.getName()); //输出:张三
console.log(person.getAge()); //输出:0
person.grow();
console.log(person.getAge()); //输出:1
在这个例子中,age和increaseAge方法都是私有的,只能在Person函数内部访问。getName、getAge和grow方法是可访问的公有方法,可以访问和操作age变量和increaseAge方法。使用闭包将返回对象和内部变量和方法关联起来,保证了age和increaseAge方法的私有性。
添加链接描述
push()
: 在尾部添加一个或多个元素,并返回新的数组长度
pop()
: 删除尾部元素,并返回该元素的值
unshift()
: 在头部添加一个或多个元素,并返回新的数组长度
shift()
: 删除头部元素,并返回该元素的值
slice()
: 返回从开始位置到结束位置之间选定的数组部分,不会修改原数组
splice()
: 从指定位置删除/插入制定数量的元素,并返回被删除的元素
reverse()
: 颠倒数组中元素的顺序
sort()
: 按照升序或降序排列数组中的元素
concat()
: 合并两个或多个数组,并返回新数组
join()
: 将数组转换为字符串并连接其中的元素
indexOf()
: 返回匹配元素的第一个索引,如果没有找到则返回-1
lastIndexOf()
: 返回匹配元素的最后一个索引,如果没有找到则返回-1
forEach()
: 遍历数组中的每个元素,并对其进行操作
map()
: 将数组中的每个元素传递给函数,并返回包含结果的新数组
filter()
: 返回包含数组中满足条件的所有元素的新数组
reduce()
: 从左到右执行累加器函数,以便将数组减少为单个值
isArray()
: 确定一个值是否为数组
includes()
返回布尔值,表示是否至少找到一个与指定元素匹配的项
every()
: 如果数组中所有元素都满足条件,则返回true,否则返回false
some()
: 如果数组中至少有一个元素满足条件,则返回true,否则返回false
from()
: 将类数组结构转换为数组实例
of()
: 一组参数转换为数组实例
keys()
返回数组索引
values()
返回数组 值
entries()
返回数组 索引和值
find()
返回第一个匹配的元素
findIndex()
返回第一个匹配元素的索引
添加链接描述
map
,它接收一个回调函数作为参数,回调函数接收三个参数 currentValue
(当前元素值)、index
(当前索引)和 array
(原始数组)。result
,用于存储回调函数处理后的值。result
数组中。result
数组。Array.prototype.myMap = function (callback) {
const result = [];
for (let i = 0; i < this.length; i++) {
const value = callback(this[i], i, this);
result.push(value);
}
return result;
};
// 使用示例
const arr = [1, 2, 3];
const mappedArray = arr.myMap((value) => value * 2);
console.log(mappedArray); // [2, 4, 6]
可以通过遍历数组,筛选出符合条件的元素,将其放入一个新数组中返回,从而手动实现Array.prototype.filter方法。
Array.prototype.myFilter = function(callback) {
const newArray = [];
for (let i = 0; i < this.length; i++) {
if (callback(this[i], i, this)) {
newArray.push(this[i]);
}
}
return newArray;
}
const arr = [1, 2, 3, 4, 5];
const result = arr.myFilter(item => item % 2 === 0);
console.log(result); // [2, 4]
这里使用了回调函数作为参数,回调函数接收当前遍历到的元素、元素索引以及原数组作为参数,如果回调函数返回true,则将该元素加入新数组中。
Array.prototype.reduce方法是一个高阶函数,用于遍历数组并对每个元素进行处理,最终返回一个结果。
手动实现Array.prototype.reduce方法的步骤如下:
Array.prototype.myReduce = function(callback, initialValue) {
var accumulator = initialValue !== undefined ? initialValue : this[0];
for (var i = initialValue !== undefined ? 0 : 1; i < this.length; i++) {
accumulator = callback(accumulator, this[i], i, this);
}
return accumulator;
};
var arr = [1, 2, 3];
var sum = arr.myReduce(function(acc, curr) {
return acc + curr;
}, 0);
console.log(sum); // 输出6
使用方法与原生的Array.prototype.reduce方法一样
for...in
循环:使用for…in循环可以遍历对象的所有属性,包括对象继承而来的属性。Object.keys(obj)
:Object.keys()方法返回一个包含对象所有属性名的数组,可以遍历对象自身的属性。Object.values(obj)
:Object.values()方法返回一个数组,包含对象所有可枚举属性的值。Object.entries(obj)
:Object.entries()方法返回一个包含对象所有可枚举属性的键值对的数组。Object.getOwnPropertyNames(obj)
:Object.getOwnPropertyNames()方法返回一个数组,包含对象自身的所有属性名,无论属性是否可枚举。使用Symbol.iterator
:可以使用Symbol.iterator方法定义对象的遍历器,然后使用for…of循环或者展开运算符(…)遍历对象的属性和值。JSON.stringify(obj)
和JSON.parse(str)
:使用JSON.stringify()将对象转换为JSON格式的字符串,使用JSON.parse()将JSON格式的字符串转换为对象,并遍历对象的属性和值。添加链接描述
charAt(index)
: 返回指定位置的字符。concat(str1, str2)
: 连接两个或多个字符串,并返回新的字符串。indexOf(substr, [start])
: 返回字符串中指定子串出现的位置,如果没有找到则返回-1。lastIndexOf(substr, [start])
: 从字符串的末尾开始查找指定子串出现的位置,如果没有找到则返回-1。match(regexp)
: 将正则表达式和字符串进行匹配,并返回匹配结果。replace(regexp/substr, replacement)
: 替换字符串的指定部分。search(regexp)
: 在字符串中查找指定内容的位置。slice(start, [end])
: 截取字符串的一部分并返回。split([separator, [limit]])
: 将字符串分割****为子字符串数组。substr(start, [length])
: 从指定位置开始截取字符串中指定长度的子字符串并返回。substring(start, [end])
: 返回字符串的指定部分。toLowerCase()
: 将字符串转换为小写字母。toUpperCase()
: 将字符串转换为大写字母。trim()
: 去除字符串两端的空格并返回。JavaScript 中存在显式类型转换和隐式类型转换。
Number()
将数据类型转换为数值型,包括字符串、布尔值等String()
将数据类型转换为字符串类型Boolean()
将数据类型转换为布尔类型parseInt()
将字符串转换为整型parseFloat()
将字符串转换为浮点型toString()
将数据类型转换为字符串类型添加链接描述
异步回调地狱指的是在处理多个异步任务时,由于异步任务之间的依赖关系和回调函数的嵌套,导致代码可读性差、维护难度大的问题。
为了解决异步回调地狱问题,可以采用以下方法:
Promise
Promise
是 ES6 中新增的异步编程解决方案,主要解决了回调函数多层嵌套的问题。通过使用 Promise,可以将异步操作转换为链式的代码结构,使得代码更加易读、易懂、易维护。async/await
async/await
是 ES8 中引入的异步编程解决方案,基于 Promise 实现。async/await 通过使用 async 关键字和 await 关键字,使得异步代码的执行流程更加清晰,代码结构更加简洁、易读。事件驱动模型
前端中的事件流指的是事件在页面元素间传递的过程。常见的事件流有三种:
在事件流中,事件对象可以通过 event.stopPropagation()
和 event.preventDefault()
方法来停止事件的传播或默认行为的发生。
一般是先捕获后冒泡,实现冒泡后捕获的效果,对于同一事件 监听捕获和冒泡 分别对应相应的处理函数 监听到捕获事件 先暂缓执行 直到冒泡事件被捕获后在执行捕获事件
事件委托(Event Delegation)是一种用于提高页面性能并减少内存消耗的优化技巧,在页面中通过事件冒泡实现的。
事件代理是通过将事件处理器绑定到一个父级元素,而不是每个子元素上,来提高性能的技术。它利用了事件冒泡机制,将子元素的事件委托给它们的父级元素。
例如,假设我们有一个列表,其中包含多个项。如果我们要为每个列表项添加一个单击事件处理程序,我们可以使用事件委托来实现。相反,为每个列表项分别绑定事件处理程序可能会导致性能问题。
<ul id="menu">
<li>首页li>
<li>商品li>
<li>分类li>
<li>购物车li>
<li>我的li>
ul>
document.getElementById('menu').addEventListener('click', function(event) {
if (event.target.nodeName === 'LI') {
console.log('你单击了选项 ' + event.target.textContent);
}
});
在此示例中,我们只将单击事件处理程序绑定到列表的父级元素。当用户单击列表项时,事件冒泡到父级元素,并通过检查事件目标的节点名称来确定用户单击的是哪个列表项。
优点:
是我们只绑定了一个事件处理程序,而不是为每个列表项都绑定一个事件处理程序,这样可以提高页面性能,减少内存消耗。
添加链接描述
添加链接描述
JavaScript中的函数是一等公民,因此它们可以赋值给变量,作为参数传递等。同时,函数具有this关键字,它指向调用该函数的对象。
有时候我们需要改变函数内部this指针的指向,这时可以使用bind、apply和call方法。
function.bind(thisArg[, arg1[, arg2[, ...]]])
参数说明:
let obj1 = {
name: 'Lucy',
sayName: function() {
console.log(this.name);
}
};
let obj2 = {
name: 'Bob'
};
let sayBobName = obj1.sayName.bind(obj2);
sayBobName(); // 输出:Bob
function.apply(thisArg, argsArray)
参数说明:
let obj1 = {
name: 'Lucy',
sayName: function() {
console.log(this.name);
}
};
let obj2 = {
name: 'Bob'
};
obj1.sayName.apply(obj2); // 输出:Bob
function.call(thisArg, arg1, arg2, ...)
参数说明:
let obj1 = {
name: 'Lucy',
sayName: function() {
console.log(this.name);
}
};
let obj2 = {
name: 'Bob'
};
obj1.sayName.call(obj2); // 输出:Bob
clientHeight
:元素的可见高度,包括padding但不包括border和margin。clientTop
:元素上边框(border)的宽度。scrollHeight
:元素内容的总高度,包括超出视口的部分,不包括border和margin。scrollTop
:元素内容被卷起的高度,即滚动条向下滚动的距离。offsetHeight
:元素在垂直方向上的高度,包括padding、border和可选的margin。offsetTop
:元素相对于父元素的顶部位置,与包含块相关。瀑布流,又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。 瀑布流常用于图片库、社交网站和博客等网站,可以让用户更轻松的浏览大量内容。
如何判断浏览器是否触底
document.body.clientHeigth + document.body.scrollTop===document.body.scrollHeight
window.onscroll = function() {
// 获取页面文档高度
const offsetHeight = document.documentElement.offsetHeight;
// 获取页面滚动高度
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
// 获取浏览器窗口高度
const windowHeight = window.innerHeight;
// 如果滚动到页面底部
if (offsetHeight - scrollTop - windowHeight <= 0) {
console.log("触底啦");
}
}
JS拖拽功能的实现主要分为以下几个步骤:
mousedown
事件,当鼠标按下时记录鼠标坐标和被拖拽元素的位置。document
对象添加mousemove
事件,当鼠标移动时计算出被拖拽元素的新位置,并设置其样式。document
对象添加mouseup
事件,当鼠标释放时取消鼠标移动事件。var dragElement = document.getElementById("dragElement"); // 获取元素
var mouseX = 0, mouseY = 0, offsetX = 0, offsetY = 0;
dragElement.addEventListener("mousedown", function(e) {
// 鼠标坐标
mouseX = e.clientX;
mouseY = e.clientY;
// 被拖拽元素的位置
offsetX = mouseX - dragElement.offsetLeft;
offsetY = mouseY - dragElement.offsetTop;
// 给`document`对象添加`mousemove`事件
document.addEventListener("mousemove", mousemoveHandler);
});
// 给`document`对象添加`mouseup`事件
document.addEventListener("mouseup", function() {
document.removeEventListener("mousemove", mousemoveHandler);
});
// 计算元素的偏移值
function mousemoveHandler(e) {
dragElement.style.left = (e.clientX - offsetX) + "px";
dragElement.style.top = (e.clientY - offsetY) + "px";
}
在以上代码中,我们首先获取了被拖拽元素的dom节点和鼠标按下时的坐标。然后给document添加mousemove事件,当鼠标移动时更新被拖拽元素的样式,其中使用了鼠标坐标和元素的偏移值来计算新的位置。当鼠标释放时,移除mousemove事件。
HTML5提供了拖放API,使得开发者可以在网页上实现拖放操作,丰富用户的交互体验。
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Drag and Droptitle>
<style>
#drag {
width: 100px;
height: 100px;
background-color: red;
cursor: move;
}
#drop {
width: 200px;
height: 200px;
border: 2px solid black;
}
style>
<script>
function allowDrop(event) {
event.preventDefault();
}
function drag(event) {
event.dataTransfer.setData("text", event.target.id);
}
function drop(event) {
event.preventDefault();
var data = event.dataTransfer.getData("text");
event.target.appendChild(document.getElementById(data));
}
script>
head>
<body>
<div id="drag" draggable="true" ondragstart="drag(event)">div>
<div id="drop" ondrop="drop(event)" ondragover="allowDrop(event)">div>
body>
html>
在这个例子中,我们创建了一个红色的可拖拽元素(id为“drag”),以及一个“放置区域”(id为“drop”)。我们给可拖拽元素添加了draggable="true"
属性,并设置了ondragstart
事件处理函数drag()
。drag()
函数使用数据传输对象将元素的id传递给拖放事件。
在放置区域上我们添加了ondragover
和ondrop
事件处理函数。ondragover
函数调用preventDefault()
方法,用于防止浏览器默认地处理拖放事件。ondrop
函数中我们调用preventDefault()
方法停止浏览器默认行为,然后获取传递的数据,并使用它在放置区域中定位拖拽元素。
这是一个简单的演示,不过它能够展示HTML5的拖放功能。
异步加载JavaScript代码是为了避免页面加载速度过慢而采用的一种方案。异步加载js方法可以有以下几种实现方式:
defer
属性:将<script defer src="script.js"></script>
async
属性:将<script async src="script.js"></script>
var script = document.createElement('script');
script.src = 'script.js';
document.head.appendChild(script);
CommonJS
、AMD:RequireJS
、CMD
都是JavaScript模块化规范。每个模块都是一个单独的文件,有自己的作用域,模块之间通过特定的方式引用和调用。
CommonJS
规范:Node.js
采用的模块规范。它的主要特点是通过require()
方法同步加载依赖的模块,在Node.js中,一个文件就是一个模块,每个模块都有自己的作用域,要想在模块之间共享变量,可以通过exports
对象把变量或函数暴露出去,其他模块则通过require()方法来引用这些变量或函数。AMD
规范:define()
函数来定义模块,使用require()
函数来异步加载依赖的模块,依赖模块通过回调函数作为参数传递给require()函数。RequireJS
是一个AMD模块加载器,它实现了AMD规范,可以在浏览器环境下异步加载模块。它的主要特点是可以通过配置文件来加载模块,使得模块的加载变得非常方便。CMD
规范:define()
函数来定义模块,但与AMD不同的是,它使用require()函数来同步加载模块。CMD规范的代表是Sea.js
。实现一个 once
函数:
function once(fn) {
let result;
console.log(result,'result')
let hasBeenCalled = false;
return function(...args) {
if (!hasBeenCalled) {
result = fn(...args);
hasBeenCalled = true;
}
return result;
}
}
once
函数接收一个函数作为参数,并返回一个新的函数。新的函数只会执行一次传入的函数,并返回其结果。在第一次执行之后,再次调用该新函数时会直接返回上一次调用的结果,不会再次执行传入的函数。
示例:
function add(a, b) {
console.log('add called');
return a + b;
}
const addOnce = once(add);
console.log(addOnce(1, 2)); // 输出 add called 和 3
console.log(addOnce(3, 4)); // 只输出 3,因为 add 函数只被调用了一次
function ajax(method, url, data) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.onload = function() {
if (this.status >= 200 && this.status < 300) {
resolve(xhr.response);
} else {
reject({
status: this.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = function() {
reject({
status: this.status,
statusText: xhr.statusText
});
};
xhr.send(data);
});
}
可以使用以下方式发起请求并处理响应:
ajax('GET', '/api/someUrl')
.then(response => {
console.log(response);
})
.catch(error => {
console.error(error);
});
这将使用GET请求发起一个/ajax/someUrl的API请求,并在请求成功或失败时分别打印响应或错误信息。
可以使用ES6中引入的Proxy
对象来监听对象属性的改变。Proxy对象可以拦截对象的各种操作,包括获取属性值、设置属性值、删除属性等,从而实现监听属性变化的功能。
let obj = { name: 'Tom', age: 20 };
let proxy = new Proxy(obj, {
get(target, key, receiver) {
console.log(`读取属性${key}`);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log(`设置属性${key}=${value}`);
return Reflect.set(target, key, value, receiver);
}
});
proxy.name = 'Jerry';
console.log(proxy.age);
在上述代码中,首先创建一个普通的对象obj,然后使用Proxy对象对其进行代理。在Proxy对象的构造器中,通过get和set函数分别实现对属性的读取和设置的拦截。其中,target
表示被代理的对象,key
表示属性名,value
表示属性值,receiver
表示Proxy对象本身。
通过这种方式,当使用proxy对象对obj的属性进行读取或设置时,会触发相应的拦截操作,从而实现监听属性变化的功能。
setTimeout、setInterval和requestAnimationFrame都是JavaScript中用于延迟执行或定时执行函数的方法,它们的作用相似但实现方式不同。
setTimeout
方法用于延迟执行函数一次,它的语法是:
setTimeout(function, delay)
其中,第一个参数为需要延迟执行的函数,第二个参数为延迟的时间(单位为毫秒)。例如:
setTimeout(function() {
console.log("Hello, world!");
}, 1000);
这段代码会在延迟1秒后输出"Hello, world!"。
setInterval
方法用于定时执行函数,它的语法是:
setInterval(function, delay)
其中,第一个参数为需要定时执行的函数,第二个参数为定时时间(单位为毫秒)。例如:
setInterval(function() {
console.log("Hello, world!");
}, 1000);
这段代码会每隔1秒钟就输出一次"Hello, world!"。
requestAnimationFrame
方法也用于定时执行函数,但它的实现方式不同于setTimeout和setInterval。它的执行频率与屏幕的刷新频率相同,通常为每秒60次。它的语法是:
requestAnimationFrame(function)
其中,参数为需要定时执行的函数。例如:
function loop() {
console.log("Hello, world!");
requestAnimationFrame(loop);
}
loop();
这段代码会每隔1/60秒就输出一次"Hello, world!",直到程序停止或调用cancelAnimationFrame方法停止执行。相比于setTimeout和setInterval,requestAnimationFrame具有更好的性能和更准确的执行时间。但它也需要更多的代码实现。
- 闭包
- 意外的全局变量
- 被遗忘的定时器
- 脱离dom的引用
高阶函数指的是接收一个或多个函数作为参数,并且返回另一个函数作为结果的函数。这意味着高阶函数可以操作其他函数,使得代码更加抽象和灵活。
例如,以下是一个将函数f作为参数并返回一个新函数的高阶函数:
def apply_func(func, arg):
return func(arg)
当我们调用这个高阶函数并传入一个函数f和一个参数x时,它会返回f(x):
def square(x):
return x ** 2
apply_func(square, 2) # returns 4
在这个例子中,函数apply_func被定义为一个高阶函数,因为它接收一个函数作为参数,并在函数中使用它来计算结果。
添加链接描述
<button id="sendCodeBtn">发送验证码button>
document.getElementById("sendCodeBtn").addEventListener("click",
function() {
// 发送验证码的逻辑
});
let isSending = false; // 是否正在发送验证码
let countDownTime = 60; // 倒计时时间,单位为秒
function sendCode() {
if (isSending) {
return;
}
// 发送验证码的逻辑,例如通过 AJAX 请求发送验证码到后端
isSending = true;
countDown();
}
function countDown() {
let btn = document.getElementById("sendCodeBtn");
if (countDownTime > 0) {
btn.disabled = true;
btn.textContent = countDownTime + "秒后重试";
countDownTime--;
setTimeout(countDown, 1000);
} else {
btn.disabled = false;
btn.textContent = "发送验证码";
countDownTime = 60;
isSending = false;
}
}
sendCode()
方法即可开始发送验证码并启动倒计时。在倒计时过程中,按钮将被禁用,并显示剩余倒计时时间。当倒计时结束后,按钮将重新启用并显示“发送验证码”文本。添加链接描述
添加链接描述
添加链接描述
图片防抖节流优化
添加链接描述
添加链接描述
添加链接描述
添加链接描述
border-radius: 10px;
box-shadow: 5px 5px 5px #888888;
background: linear-gradient(red, blue);
text-shadow: 1px 1px 1px #888888;
-webkit-text-fill-color: blue;
-webkit-text-stroke: 1px black;
word-wrap: break-word;
@font-face {
font-family: "MyFont";
src: url("myfont.woff2") format("woff2"),
url("myfont.woff") format("woff");
}
font-size: 14px;
font-style: italic;
transform: rotate(45deg);
transform: scale(1.5);
transform: skew(30deg);
transform: perspective(1000px);
transition: all 1s ease;
@keyframes my-animation {
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}
animation: my-animation 2s infinite;
column-count: 3;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
box-shadow: 5px 5px 5px #888888;
box-sizing: border-box;
width: calc(100% - 20px);
@media screen and (max-width: 768px) {
/* styles for small screens */
}
<img src="image.jpg" srcset="image-small.jpg 480w, image-medium.jpg 768w, image-large.jpg 1200w" />
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
flex: 1 1 50%;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
grid-column: 1 / 3;
grid-row: 1 / 2;
这些新特性为开发人员提供了更多的选择和灵活性来创建美观、响应式且交互性强的网页和Web应用程序。
常用的属性值有:
div{
width: 0;
height: 0;
border: 200px transparent solid;
border-left: blue 200px solid;
}
它具有: content(不包含 padding 和 border),padding,border,margin 四个属性,这就是盒子模型
盒模型有两种形式:
box-sizing:content-box;
默认值,代表W3C盒模型,width只包含内容宽度box-sizing:border-box;
代表IE盒模型 width包含padding和borderFlex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性
.box{
display: inline-flex;
}
-webkit
前缀.box{
display: -webkit-flex; /* Safari */
display: flex;
}
设为 Flex 布局以后,子元素的float
、clear
和vertical-align
属性将失效
row
(默认值):主轴为水平方向,起点在左端。row-reverse
:主轴为水平方向,起点在右端。column
:主轴为垂直方向,起点在上沿。column-reverse
:主轴为垂直方向,起点在下沿flex-direction
属性和flex-wrap
属性的简写形式row nowrap
flex-start
(默认值):左对齐flex-end
:右对齐center
: 居中space-between
:两端对齐,项目之间的间隔都相等。space-around
:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍space-evenly
:每个弹性项目之间的距离相同,包含第一个和最后一个与弹性容器的距离stretch
:默认值。元素被拉伸以适应容器center
:元素位于容器的中心flex-star
t:元素位于容器的开头flex-end
:元素位于容器的末尾baseline
:元素位于容器的基线上 .clearfix::after{
content:"";
display:block;
clear:both;
height:0;
visibility:hidden; //规定元素是否可见,与display的区别在于,只是视觉上消失,在文档中的占位还在
}
overflow:hidden
将浮动元素和非浮动元素分隔开inline-block
清除浮动:display:inline-block
当不给父元素设置宽高时,父元素的宽高会被子元素的内容撑开。但是当子元素设置浮动属性后,子元素会溢出到父元素外,父元素的宽高也不会被撑开了,称之为“高度塌陷”
解决
overflow: hidden
显示一行
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; //设置不换行
显示指定行数
display: -webkit-box; //将对象作为弹性盒模型显示
-webkit-box-orient: vertical; //指定弹性盒子子元素的排列方式:从顶部向底部垂直布置子元素
-webkit-line-clamp: 3; //限制在一个块元素显示的文本的行数
overflow: hidden;
在图表数据初始化函数之后,调用echarts对象的**resize()**函数
window.onresize = function () {
echarts.resize()
}
//写在父盒子中:
display:flex;
justify-content:center;
align-item:center;
.container {
position: relative;
}
.box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.container {
display: table;
}
.box {
display: table-cell;
vertical-align: middle;
}
.container {
display: grid;
place-items: center; /* 水平居中和垂直居中 */
}
例子
伪类
用于当已有元素处于的某个状态时,为其添加对应的样式
伪元素
用于创建一些不在文档树中的元素,并为其添加样式
重绘:当页面中的节点样式发生改变时,不影响它在文档流中的位置时,浏览器会对当前页面进行重绘
重排:指当页面布局和元素位置发生改变时,浏览器需要重新计算网页布局,并重新排列元素
重排一定会发生重绘,但重绘不一定会发生重排
重排的开销是很大的,因为它会涉及到整个页面的布局和元素位置的重新计算,而重绘的开销则比较小,只需要重新绘制少数几个元素即可。
transform
和 opacity
来代替top、left
等属性来执行动画效果,因为 transform
和 opacity
的动画不会引起重排。flexbox
或 grid
布局来代替传统的 float
和 position
布局,因为它们能更好地避免重排。requestAnimationFrame
方法来调用重排操作,因为该方法会在下一次重绘前执行操作,可以避免重排和重绘的多次触发。总的来说,减少页面重排的次数,能够提高页面的性能和用户体验。
实现BFC(块级格式上下文),可以通过以下方式实现:
overflow
属性:将某个元素的overflow属性设置为非visible
值,如hidden、scroll等,就可以创建一个BFC。display
属性:将某个元素的display属性设置为table-cell, table-caption, inline-block, flex, inline-flex
等,也可以创建一个BFC。position
属性:将某个元素的position属性设置为absolute或fixed
,可以创建一个BFC。float
属性:将某个元素设置为float
元素,也可以创建一个BFC。需要注意的是,BFC具有一些特性和规则,如:
margin
会发生重叠,但是与外部元素的margin不会重叠。src和href都是HTML中用于引用资源的属性,但用法和含义有所不同。
src属性通常用于引用页面中嵌入的资源,例如图片、音频、视频或脚本等。它指定了资源的路径或URL。浏览器会解析这个路径或URL,然后加载并显示该资源。例如:
<img src="images/picture.jpg">
<script src="scripts/script.js">script>
href属性通常用于指定文档之间的超链接。它通常用于链接到其他HTML文档、CSS文件或JS文件等。当用户点击链接时,浏览器会打开指定的URL。例如:
<a href="https://www.example.com">Link to example.coma>
<link href="styles/style.css" rel="stylesheet">
link
和@import
都用于引入外部资源,如CSS和JavaScript文件。
link
是HTML中最常用的引入外部资源的方法,可以用于引入CSS、JavaScript、图标等。link
标签包含rel
、href
、type
等属性,其中rel
指示链接的类型,href
指示资源的位置,type
指示资源的MIME类型。例如:
<link rel="stylesheet" href="styles.css" type="text/css">
<link rel="icon" href="favicon.ico" type="image/x-icon">
@import
是CSS中引入外部CSS文件的方法。与link
不同,@import
是通过在CSS文件中使用@import
规则来引入外部CSS文件的。例如:
@import url("styles.css");
在使用@import
时需要注意以下几点:
@import
只能用于引入CSS文件,不能用于引入其他类型的文件。综上,link
是HTML中引入资源的主要方式,而@import
则是CSS中引入CSS文件的方式。如果要引入CSS文件,建议使用link
。
mask-size
设置蒙版绘画区域上蒙版图像的大小
在同一级别中,后面定义的样式会覆盖前面定义的样式。例如,在类选择器和属性选择器之间,后面定义的样式将覆盖前面定义的样式。
需要注意的是,如果两个样式具有相同的优先级,那么后面的样式将会覆盖前面的样式。
添加链接描述
添加链接描述
config-overrides.js
文件中,修改"@primary-color"
属性为自己需要的颜色
、
,使得开发者能够更好地描述内容的语义。
元素的值)、autofocus 属性(页面加载时,域自动获得焦点)、multiple 属性(规定
元素中可选择多个值)
和音频
标签
元素,使得在Web页面上绘制图形和动画变得更加容易。Geolocation
的API,允许Web应用程序获取用户的地理位置信息。localStorage
和sessionStorage
API,允许在客户端浏览器上存储数据。
开源:开放源代码
解决浏览器兼容
CSS 兼容前缀
text-shadow: 2px 2px 4px #000000;
-webkit-text-shadow: 2px 2px 4px #000000; /* Chrome 和 Safari */
-moz-text-shadow: 2px 2px 4px #000000; /* Firefox */
-ms-text-shadow: 2px 2px 4px #000000; /* Internet Explorer */
:页眉通常包括网站标志、主导航、全站链接以及搜索框。
什么是语义化标签
在HTML中使用具有明确语义意义的标签来编写网页,使得网页的结构更加清晰、易于理解和维护。
使用语义化标签好处
HTML5浏览器端存储指的是一种在Web浏览器中存储数据的技术,允许网站和应用程序在用户的浏览器上存储和检索数据,而无需依赖于服务器端。
HTML5浏览器端存储技术具有多种实现方式,包括:
使用HTML5浏览器端存储技术可以提高Web应用程序的性能和用户体验,并减少对服务器端的依赖。
localStorage
:localStorage是HTML5中新增的本地存储方法,用于在浏览器中存储数据。它可以在浏览器关闭后仍然保留数据,并且可以跨浏览器标签使用。它支持存储大量数据,最大可以存储5-10MB左右。sessionStorage
:sessionStorage也是HTML5中新增的本地存储方法,用于在浏览器中存储数据。与localStorage不同的是,sessionStorage仅在当前会话中有效,当浏览器关闭后,存储的数据将被清除。sessionStorage最大可以存储5-10MB左右。cookie
:cookie是一种小型文本文件,存储在用户的计算机中。它可以在浏览器关闭后仍然保留数据,并且可以跨浏览器标签使用。它的大小通常限制在4KB左右。cookie的存储时间可以自定义,可以长期存储或短期存储。但是,cookie中存储的数据会被发送到服务器,存在一定的安全风险。需要注意的是,localStorage和sessionStorage
存储的数据仅在浏览器中有效,不会发送到服务器,因此更安全。cookie
虽然可以存储在服务器端,但存在被截获和篡改的风险,因此应该使用一些加密和验证等技术对其进行保护。
HTML的Doctype是指在HTML文件开头声明文档类型的语句,它会告诉浏览器如何解析文档。典型的HTML5文档类型声明如下:
这个声明告诉浏览器,这个文档是HTML5文档,应该按HTML5规范来解析。在HTML4时代,Doctype声明是这样的:
这个声明告诉浏览器,这个文档应该按HTML4.01规范严格模式解析。另外还有一些其他的Doctype声明,例如HTML5的“简化版”、XHTML等。
是document type(文档类型)的简写,用来说明你用的XHTML或者HTML是什么版本
作用
Doctype声明位于文档的最前面,处于标签之前。目的是告知浏览器的解析器,用什么文档类型规范来解析这个文档。Doctype声明也是用于区分html和html5的方法之一。
严格模式与混杂模式
页面加载优化是指通过一系列技术手段,来减小网页加载所需的时间,增加用户体验度。
background-color: expression((new Date()).getHours()%2?"#FFFFFF": "#000000" );
这里的CSS属性可以是元素固有的属性,也可以是自定义属性SPA( single-page application:单页面应用 )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载
SPA常见的技术栈包括React、Angular和Vue等前端框架
优点:
缺点:
一个小型的node.js Express服务器(静态文件服务器),能够用于快速开发应用程序
在devServer
中配置:
open: true
开服之后自动打开页面hot: true
热更新devServer
中,通过proxy
设置跨域地址
npx webpack serve
使用原因
保留了单个模块的可维护性,减少了页面的http请求,减少了页面加载时间,从而增加了页面的显示速度,让整个应用的体验更好
① 模块化开发(import,require)
② 预处理(Less,Sass,ES6,TypeScript……)
③ 主流框架脚手架支持(Vue,React,Angular)
④ 庞大的社区(资源丰富,降低学习成本)
自动化构建工具
添加链接描述
JSON是一种轻量级数据交换格式,常用于Web应用程序的数据传输。其全名为JavaScript Object Notation,是一种基于文本的数据格式。JSON数据基于键值对构成的集合,其中键名必须为字符串,值可以为数字、字符串、布尔类型、数组、对象或null。JSON格式通常采用Unicode字符集编码。
以下是一个JSON对象的例子:
{
"name": "John",
"age": 30,
"isMarried": true,
"hobbies": ["reading", "swimming"],
"address": {
"street": "Main St",
"city": "New York"
}
}
在此例中,name、age、isMarried、hobbies和address都是键名,对应的值为"John"、30、true、[“reading”, “swimming”]和{“street”: “Main St”, “city”: “New York”}。注意键名和字符串值都需要用双引号包裹。
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于读写和解析。在进行数据交换和存储时,常用JSON格式进行数据传输。下面是JSON格式转换的相关信息:
从JSON转换为对象:可以使用JavaScript中的JSON.parse()
方法将JSON格式的字符串转换为JavaScript对象。例如:
const jsonStr = '{"name": "Alice", "age": 25}';
const jsonObj = JSON.parse(jsonStr);
console.log(jsonObj.name); // 输出:Alice
console.log(jsonObj.age); // 输出:25
从对象转换为JSON:可以使用JavaScript中的JSON.stringify()
方法将JavaScript对象转换为JSON格式的字符串。例如:
const jsonObj = {name: "Alice", age: 25};
const jsonStr = JSON.stringify(jsonObj);
console.log(jsonStr); // 输出:{"name":"Alice","age":25}
在前后端数据交换时,通常会使用JSON格式进行数据传输。在前端中,可以使用fetch()或者axios等工具发送请求,并将数据以JSON格式发送到后端。在后端中,可以使用JSON.parse()方法将接收到的JSON格式的字符串转换为对象,进行数据处理,然后将处理结果以JSON格式返回给前端。
JSONP(JSON with Padding)是一种跨域数据请求的方法。它利用动态创建 Script 标签可以跨域访问并获取数据的特性,将需要获取的数据包裹在一个回调函数中返回,在前端页面中通过调用该回调函数来获取数据。
下面是一个简单的例子:
服务端代码(假设服务端地址为 http://example.com/data):
callbackFunction({"name": "John", "age": 30});
客户端代码:
function callbackFunction(data) {
console.log(data.name);
}
var script = document.createElement('script');
script.src = 'http://example.com/data?callback=callbackFunction';
document.body.appendChild(script);
服务端返回的数据会被包裹在 callbackFunction 中,由客户端的 script 标签加载并执行。当 script 标签加载完毕后,callbackFunction 就会被调用,得到返回的数据。
需要注意的是,服务端返回的数据必须是合法的 JSON 格式,否则将无法解析。同时,回调函数的名称应该在客户端代码中动态生成,避免被恶意利用。
Ajax(Asynchronous JavaScript and XML)是一种基于浏览器端和服务器端之间异步通信的技术,可以让网页在不刷新的情况下动态地加载新内容。Ajax通常使用XMLHttpRequest对象来发送HTTP请求和接收HTTP响应。除了XMLHttpRequest,现在也有许多基于Ajax的开源库和框架,如jQuery、AngularJS、React等。
使用Ajax可以做到以下操作:
使用Ajax的基本步骤如下:
1、在ajax发送请求前加上 xmlHttpRequest.setRequestHeader(“Cache-Control”,”no-cache”);
2、在服务端加 header(“Cache-Control: no-cache, must-revalidate”);
3、在ajax发送请求前加上 xmlHttpRequest.setRequestHeader(“If-Modified-Since”,”0″);
4、在 Ajax 的 URL 参数后加上 “?fresh=” + Math.random(); //当然这里参数 fresh 可以任意取了
5、第五种方法和第四种类似,在 URL 参数后加上 “?timestamp=” + new Date().getTime();
添加链接描述
V8 是谷歌开发的 JavaScript 引擎,它负责将 JavaScript
代码转换为机器代码并执行。在 V8 中,内存管理主要包括两个方面:分配和回收。
V8 的内存管理使用了一个叫做垃圾回收机制的算法来自动回收不再被引用的内存。在 V8 中,垃圾回收机制主要分为两种类型:新生代垃圾回收和老生代垃圾回收。
新生代垃圾回收主要针对年轻的对象,通常被认为是生命周期较短的对象。新生代内存空间的大小通常比老生代的空间要小很多。
在 V8 中,新生代垃圾回收采用了复制算法。它将内存空间分为两个相等的空间,每次只有其中一个空间是活动空间,存放当前正在使用的对象。当一个对象经过多次垃圾回收后仍然存活,那么它就会被移到老生代的空间中。
老生代垃圾回收主要针对生命周期较长或者占用内存空间较大的对象。老生代垃圾回收通常需要进行全堆垃圾回收,这个过程会暂停整个程序的执行。
在 V8 中,老生代垃圾回收采用了标记-清除算法。在标记阶段,垃圾回收机制会遍历堆中的所有对象,并标记所有还在使用的对象。在清除阶段,垃圾回收机制会将所有未标记的对象进行清除。
需要注意的是,在进行老生代垃圾回收的时候,由于需要暂停整个程序的执行,所以当程序内存占用过大,达到内存限制时,垃圾回收机制会进行强制性的垃圾回收,以防止程序出现内存泄漏等问题。
TCP/IP
是一组协议簇,包括以下协议:
IP
协议(Internet Protocol):负责网络层的数据传输和路由。TCP
协议(Transmission Control Protocol):负责传输控制,保证数据传输的可靠性。UDP
协议(User Datagram Protocol):与TCP协议类似,但不保证数据传输的可靠性。HTTP
协议(HyperText Transfer Protocol)是一种应用层协议,用于在Web浏览器和Web服务器之间传递信息,可通过TCP/IP
协议传输。
还有其他的协议,例如:
FTP
协议(File Transfer Protocol):用于在网络上进行文件传输。SMTP
协议(Simple Mail Transfer Protocol):用于电子邮件的传输。POP3
协议(Post Office Protocol version 3):用于从邮件服务器接收电子邮件。IMAP
协议(Internet Message Access Protocol):用于从邮件服务器接收和管理电子邮件。HTTP请求方式包括以下常见的5种方法:
GET
:用于请求指定资源,并且必须是安全和幂等的,即重复请求不会对服务器产生副作用。GET方法是最常用的方法,一般用于请求数据,如获取网页内容、图片等资源。POST
:用于向指定资源添加新的数据,并且可能会修改服务器上的数据内容。POST方法不能被缓存,也不具备安全和幂等的特性,即重复请求会对服务器产生副作用。PUT
:用于向指定资源更新或者替换数据,PUT方法也不具备安全和幂等的特性。DELETE
:用于删除指定的资源。HEAD
:用于请求指定资源的头部信息,HEAD方法和GET方法类似,但是它只返回请求头,不返回实际的资源内容。该方法主要用于客户端查看网页或者资源的元数据,如页面的标题、资源的大小、类型等。常用的 HTTP 响应头如下:
Content-Type
:指示响应中包含的内容的类型。Content-Length
:指示响应正文的长度。Content-Encoding
:指示响应正文的压缩编码方式。Cache-Control
:指示客户端和代理服务器如何缓存响应。Expires
:指示响应的过期时间。Last-Modified
:指示资源最后修改时间。Location
:指示客户端应该重定向到的 URL。Set-Cookie
:指示服务器要设置的 cookie。Server
:指示服务器软件的名称和版本号。X-Content-Type-Options
:指示浏览器是否应该在尝试嗅探响应内容类型时强制使用 Content-Type 头中指定的类型。Fetch是一种现代的Web API,它提供了一种使用JavaScript进行网络请求的简单方式。Fetch API使用简单和直观,并且在浏览器和Node.js中都有良好的支持。
Fetch API提供了两个主要方法:fetch()和fetchEvent()。这两个方法都可以用来发起HTTP请求,但使用的方式略有不同。
fetch()
方法用于请求网络资源,如图片、文本文件、JSON数据等。它返回一个Promise对象,该对象在请求完成后将被解析并返回一个Response对象。
fetch()方法的基本语法如下:
fetch(url, options)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
其中,url参数是要请求的资源的URL地址,options参数是一个可选的配置对象,用于指定请求的参数,如请求方法、请求头和请求体等。
2. fetchEvent()
fetchEvent()
方法用于在Service Worker
中处理网络请求事件。它是由Service Worker API提供的。
fetchEvent()方法的基本语法如下:
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request)
.then(response => console.log(response))
.catch(error => console.error(error))
);
});
其中,fetch(event.request)方法用于在Service Worker中发起网络请求,event.respondWith()方法用于将fetch()返回的Promise对象传递给浏览器,从而正确处理网络请求事件。
添加链接描述
CSRF(Cross-Site Request Forgery,跨站请求伪造)攻击是指攻击者通过设置恶意网站,在用户不知情的情况下,利用已经登录过的用户的身份,向目标站点发出恶意请求,造成一系列的危害。
攻击步骤如下:
例如,一家银行的网站已经登录成功,此时攻击者打开恶意网站,恶意网站中插入一个银行网站的转账请求链接,此时:
指的是在浏览器中,通过JavaScript从一个源(Origin)获取到数据或执行操作,而该源与当前页面的域(Domain)不同。由于安全原因,浏览器禁止通过JavaScript跨域访问其他源的资源,因为这可能会带来安全问题(如XSS攻击)。
jsonp
请求;jsonp的原理是利用script标签的跨域特性,可以不受限制地从其他域中加载资源,类似的标签还有img.CORS
;CORS背后的基本思想,就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是应该失败。window.postMessage
;window.postMessages是html5中实现跨域访问的一种新方式,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源。document.domain
;这种方式用在主域名相同子域名不同的跨域访问中window.name
;window的name属性有个特征:在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。Web Sockets
;web sockets原理:在JS创建了web socket之后,会有一个HTTP请求发送到浏览器以发起连接。取得服务器响应后,建立的连接会使用HTTP升级从HTTP协议交换为web sockt协议。CORS (Cross-Origin Resource Sharing) 是一种浏览器的安全策略,用于限制跨域请求访问其他域的资源。
当服务器配置了允许部分域跨域时,浏览器会在请求头中添加 Origin 字段,表示当前请求来自哪个域。如果服务端允许该请求的来源域,将在响应头中添加Access-Control-Allow-Origin
字段,表示允许该来源的请求访问资源。如果不允许,则会抛出一个跨域错误。
在请求跨域资源时,浏览器会发出一个 OPTIONS
请求,该请求称作“预检请求”,用于询问服务器是否允许跨域请求。只有在收到服务器允许的响应之后才会发出正式的请求。
对于跨域的 AJAX 请求,如果需要发送 Cookie
,则需要设置 withCredentials
属性为 true。同时,服务器端还需要设置 Access-Control-Allow-Credentials
字段为 true,表示允许跨域请求携带 Cookie。
需要注意的是,CORS 只是一种浏览器策略,如果使用非浏览器方式访问资源,依然可以跨域获取资源并携带 Cookie。因此,在进行敏感操作时,需要在服务器端进行更严格的鉴权和访问控制。
在GET请求中,请求参数通常是通过URL的查询字符串传递的。然而,URL的长度是有限制的,当一定数量的参数被添加到URL时,其长度会超过浏览器和服务器接受的最大长度限制,导致请求无法完成。这种情况称为请求参数“过长”。
一些人常常认为GET请求不能传输大量数据或者传输的数据要有限制,但实际上这种想法是有误的。虽然GET请求的URL长度是有限制的,但并不代表我们不能传输大量的数据,也不应该限制传输数据的数量。
事实上,根据HTTP标准规定,客户端发起的所有请求(包括GET和POST请求)都有长度限制,这个限制取决于实现服务器端和客户端的技术细节。在现代Web应用程序中,使用正常的GET请求传递数量巨大的数据或参数已经成为了常见的做法。
此外,HTTP 1.1标准对URL长度的限制进行了增加,使得对GET请求传参的数据处理变得更加容易和灵活。如果需要传输超大的数据,请考虑将数据分片,进行多次传输或者选择其他适合的数据传输方式,例如POST或WebSocket等。
因此,对于GET请求传参长度的误区,应该彻底否定。GET请求可以传输大量的数据,但是依赖于服务器和客户端技术的实际限制。建议在应用程序中使用适当的数据传输方案,并且预留一定的数据长度余量以防止出现传输失败的情况。
GET请求和POST请求在缓存方面有以下区别:
综上所述,GET请求的缓存相对于POST请求更加灵活和高效,但是在一些需要保证数据安全性的场景下,使用POST请求更加合适。
数据库缓存、服务器端缓存(代理服务器缓存、CDN 缓存)、浏览器缓存
浏览器缓存:HTTP 缓存、indexDB、cookie、Web Storage、WebSql、Application Cache、PWA
http缓存:当客户端向服务器请求资源时,会先抵达浏览器缓存,如果浏览器有“要请求资源”的副本,就可以直接从浏览器缓存中提取而不是从原始服务器中提取这个资源
Cache-Control
的max-age
没有过期或者Expires
的缓存时间没有过期),那么就会直接使用浏览器的缓存数据,不会再向服务器发送任何请求Cache-Control
和Expires
或者Cache-Control
和Expires
过期,或者它的属性设置为no-cache
时(即不走强缓存),那么浏览器第二次请求时就会与服务器进行协商,与服务器端对比判断资源是否进行了修改更新
使用HTTP缓存
在标签中嵌入标签,这种方式只对页面有效,对页面上的资源无效
// 其他主流浏览器识别的标签
// 仅有IE浏览器才识别的标签
概述
CDN的全称是Content Delivery Network,即内容分发网络。其目的是通过在现有的Internet中增加一层新的CACHE(缓存)层,将网站的内容发布到最接近用户的网络”边缘“的节点,使用户可以就近取得所需的内容,提高用户访问网站的响应速度。从技术上全面解决由于网络带宽小、用户访问量大、网点分布不均等原因,提高用户访问网站的响应速度
优化原理
CDN网络是在用户和服务器之间增加Cache层,主要是通过接管DNS实现,将用户的请求引导到Cache上获得源服务器的数据
常用的设计模式有以下几种:
工厂模式
:定义一个用于创建对象的接口,让子类决定实例化哪个类。把对象的实例化延迟到子类中进行, 以达到解耦的目的。单例模式
:保证一个类只有一个实例,并提供一个全局访问点。观察者模式
:定义对象间的一种一对多的依赖关系,从而当一个对象状态发生改变时,所有依赖于它的对象都得到通知并且自动更新。适配器模式
:将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作。装饰器模式
:动态地给一个对象添加一些额外的职责。就增加功能而言,装饰模式比生成子类更为灵活。策略模式
:定义一系列的算法,将每一种算法都封装起来,并且使它们之间可以互换。模板方法模式
:定义一个操作中的算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。迭代器模式
:提供一种方法,访问一个容器对象中的每个元素,而又不需暴露该对象的内部细节。代理模式
:为其他对象提供一种代理以控制对这个对象的访问。ES7(ECMAScript 2016)引入了以下内容:
Array.prototype.includes()
方法:检查数组中是否包含某个元素,返回布尔类型。(**)
:用于求幂运算,如2 ** 3表示2的3次方。ES8(2017年)
async/await
:一种新的异步编程模式,使异步操作更加清晰和易于理解。Object.getOwnPropertyDescriptors()
方法:用于获取对象属性的描述符。padStart()
和padEnd()
方法,用于向字符串填充指定的字符。Object.values()
和Object.entries()
方法:用于获取对象的值数组和键/值对数组。SharedArrayBuffer和Atomics
:提供了一种新的多线程编程方式,允许多个线程访问共享内存。ES9(2018年)
for-await-of
循环,使异步操作的迭代更加容易。Promise.finally()
方法:在Promise状态改变时,无论成功或失败,都会执行finally方法。Rest/Spread
属性:可以在对象和数组中使用…语法,用于展开对象的属性和数组的元素。ES10(2019年)
Array.prototype.flat()和Array.prototype.flatMap()
方法:用于将嵌套数组展开并转换为单个层数组。String.prototype.trimStart()和String.prototype.trimEnd()
方法:用于去除字符串的开头或结尾的空格。Object.fromEntries()
方法:将键值对数组转换为对象。Symbol.prototype.description
属性可以获取Symbol的描述字符串。ES11(也称为ES2020)的一些新特性包括:
Promise.allSettled()
方法:可以接受一组 Promise,返回每个 Promise 的状态和结果,而不管它们是否被解决。BigInt
类型:引入了一种新类型BigInt来表示任意精度的整数。BigInt类型在数值范围上比Number类型更加广泛。ES12(也称为ES2021)的一些新特性包括:
String.prototype.replaceAll()
方法:可以替换一个字符串中的所有匹配项,而不只是第一个,类似于正则表达式中的全局替换。Promise.any()
方法:接受一组 Promise,只要其中一个变为已解决状态,就返回该 Promise 的结果,而不是所有 Promise 的结果。WeakRefs
:允许创建对对象的弱引用,这些弱引用不会阻止垃圾回收器释放对象。这是一种有效地管理内存的方法。添加链接描述
MVVM:Model–View–ViewModel
(1)Model 层
Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对于前端来说就是后端提供的 api 接口。
(2)View 层
View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建 。
(3)ViewModel 层
ViewModel 是由前端开发人员组织生成和维护的视图数据层。
model数据层、view视图层、controller控制层、
单页面组件
缺点:
get
和set
方法,当变量改变时,vue收到通知,从而实现响应this.$set
来调用Object.defineProperty(obj, 'name', { // 参数分别为:对象名,对象的属性名,执行的函数
get: function(){
console.log('get',_name); //监听
return _name;
},
set: function(newVal){
console.log('set',newVal); //监听
_name = newVal;
}
})
当 Vue 监听一个数据对象时,Vue 会递归地遍历该对象的所有属性,从而使 Vue 能够监听到该对象的变化。但如果我们直接改变了一个对象的某个属性,而不是整个对象本身,Vue 并不会察觉到这个变化,从而也不会更新视图。
在两种情况下修改 数据,Vue 不会触发视图更新:
this.$set(对象,新属性,新属性值) //没有该属性,则是直接添加
this.$set(数组,下标,值)
原理:vue监听不到复杂类型数据的属性,使用this.$set()来进行强制更新,进而解决问题
Vue 的编译过程就是将 template
转化为 render
函数的过程,分为以下三步:
v-once
v-cloak
v-bind
( : )
v-on
(@)
v-html
v-text
v-model
v-if / v-else / v-else-if
v-show
v-for
v-pre
v-is
v-slot
computed和watch都是Vue.js中用于处理数据的关键字
get
(默认具有,获取计算属性)和set
(手动添加,设置计算属性)方法;handler
回调,deep
(深度监听),immediate
(立即监听)三个属性;computed
用于简单的计算属性,watch
用于监听复杂数据的变化commputed
本质是一个惰性观察者;当计算数据存在于data
或者props
会警告
Vue初次运行会对commputed
属性进行初始化处理,初始化的时候会对每一个cmmputed
属性watcher
包装起来,这里会生成一个dirty
属性值为true
,然后执行
Computed缓存原理就是在计算属性的值不发生变化的情况下,使用缓存的结果,减少重复的计算。
添加链接描述
添加链接描述
但是当数据量较大时,它们可能会导致性能问题。因此,在使用v-for
和v-if
时,需要注意它们的性能问题,避免循环嵌套和冗余的渲染。
v-for
优先级比 if
高v-if
和v-for
同时用在同一个元素上,带来性能方面的浪费(每次渲染都会先循环再进行条件判断)$route
对象表示当前的路由信息,包含了当前 URL 解析得到的信息。包含当前的路径,参数,query对象等。name
(当前路径名字)、mate
(路由元信息)、path
、hash
、query
、params
、fullPath
、matched
$router
全局路由实例,是router构造方法的实例,操作路由,push
、go
、replace
等//常规方法
this.$router.push("/login");
//使用对象的形式 不带参数
this.$router.push({ path:"/login" });
//使用对象的形式,参数为地址栏上的参数
this.$router.push({ path:"/login",query:{username:"jack"} });
使用对象的形式 ,参数为params 不会显示在地址栏
this.$router.push({ name:'user' , params: {id:123} });
$route.path 字符串,等于当前路由对象的路径,会被解析为绝对路径,如/home/ews
$route.params 对象,包含路由中的动态片段和全匹配片段的键值对,不会拼接到路由的url后面
$route.query 对象,包含路由中查询参数的键值对。会拼接到路由url后面
$route.router 路由规则所属的路由器
$route.name 当前路由的名字,如果没有使用具体路径,则名字为空
————————————————
版权声明:本段为CSDN博主「xunfengZ」的原创文章,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xunfengZ/article/details/109670979
Vue-Router 的实现原理涉及到以下几个核心部分:
总的来说,Vue-Router 的实现原理主要分为路由映射表的维护
、路由匹配
、路由入口的渲染
、状态管理
以及钩子函数的执行
等几个方面。在这些方面的细节实现中,Vue-Router 采用了一些 JavaScript 的技巧和浏览器 API,使得整个路由的实现逻辑较为清晰和高效。
beforeEach
、afterEach
(一般用于全局进行权限跳转)
beforeEach
: 每一个路由改变之后、页面加载之前执行,三个参数(to:将要进入的路由对象、from:即将离开的路由对象、next:跳转方法)next必须调用。afterEach
每一次路由改变之后、页面加载之后执行;beforeEnter
、beforeLeave
(路由内部钩子,一般在路由表里)
beforeEnter
:进入指定路由跳转时需要执行的逻辑beforeLeave
:离开指定路由跳转时需要执行的逻辑beforeRouterEnter
、beforeRouteLeave
、beforeRouteUpdate
const router = new VueRouter({
routes: [
{
path: '/dashboard',
name: 'dashboard',
component: Dashboard,
meta: { requiresAuth: true } // 添加 meta 字段
},
// ...
]
})
在上面的代码中,当用户尝试访问 /dashboard
路由时,路由元信息的 requiresAuth
字段被设置为 true。这表示该路由需要用户登录才能访问。
beforeEach
方法中检查是否需要用户登录:router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// 判断用户是否已经登录
if (!isAuthenticated()) {
// 如果未登录,则重定向到登录页面
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next()
}
})
在上面的代码中,beforeEach
方法会在每次路由切换之前被调用。它会检查将要访问的路由是否需要用户登录,如果需要,则会调用 isAuthenticated
方法判断用户是否已经登录。如果用户未登录,则会重定向到登录页面,并且将当前路由信息作为查询参数传递给登录页面(这样用户登录成功后可以重定向回当前路由)。如果用户已经登录,则允许访问该路由。
isAuthenticated
方法:function isAuthenticated() {
// 判断用户是否已经登录
// 例如,可以从 localStorage 中获取保存的用户信息
// 如果用户已经登录,则返回 true;否则返回 false
return localStorage.getItem('user') !== null
}
在上面的代码中,isAuthenticated
方法可以根据自己的实际需求进行实现。这里的示例实现是从 localStorage 中获取保存的用户信息,如果用户已经登录,则返回 true;否则返回 false。
通过以上步骤,就可以利用路由守卫判断用户是否已经登录。当用户访问需要登录才能访问的路由时,如果用户未登录,则会被重定向到登录页面。如果用户已经登录,则允许访问该路由。
VueRouter
:创建VueRouter实例;routes
:路由配置数组,包含一个或多个路由对象;router-link
:路由链接组件,用于在应用中生成导航链接;router-view
:路由视图组件,用于显示当前路由匹配的组件;router.beforeEach
:全局前置守卫,用于在路由跳转前进行拦截和处理;router.afterEach
:全局后置守卫,用于在路由跳转后进行处理;router.push
:路由跳转方法,用于通过编程方式进行路由跳转;router.replace
:路由替换方法,用于替换当前路由而不是添加新的历史记录;router.go
:路由跳转到历史记录中的指定位置方法;router.back
:路由返回上一个历史记录位置方法;router.forward
:路由前进到下一个历史记录位置方法;router.getMatchedComponents
:获取当前路由匹配的组件数组方法;router.resolve
:解析路由到对应的URL方法。在使用 Vue 构建的单页应用程序中,我们可能会遇到一个问题:如果我们使用了大量的组件,那么这些组件会一次性被打包到同一个 JS 文件中,这会导致用户需要等待很长时间才能看到页面。这时候,我们可以使用 Vue Router 的懒加载来解决这个问题。
懒加载是指只在需要的时候才加载某些组件,而不是将所有组件一次性加载。在 Vue Router 中,我们可以通过 webpack 的代码分割功能来实现懒加载。
import Vue from 'vue'
import Router from 'vue-router'
const Home = () => import('@/components/Home.vue')
const About = () => import('@/components/About.vue')
const Contact = () => import('@/components/Contact.vue')
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
},
{
path: '/contact',
name: 'Contact',
component: Contact
}
]
})
在这个例子中,我们使用了 import()
函数来实现懒加载。当需要访问某个组件时,才会加载该组件的代码。
需要注意的是,在使用 import()
函数时,需要确保使用了 webpack
的动态导入功能,即使用 @babel/plugin-syntax-dynamic-import
插件对代码进行转换。
通过使用懒加载,我们可以有效地减少页面加载时间,提高用户体验。
Hash 模式
Hash 模式下,路由路径的格式为 #/your/path,# 后面的内容就是路由路径。hash 模式的优点是兼容性较好,可以兼容到 IE8,缺点是 URL 中始终带有 # 符号,看起来不太美观。
History 模式
History 模式下,路由路径的格式为 /your/path,没有 # 符号。history 模式的优点是 URL 看起来更加美观,缺点是需要后端支持。
原理
Hash
模式的路由实现原理相对简单,它利用了浏览器的锚点机制,通过监听 window.location.hash 的变化来实现路由跳转,而不会重新加载页面,这种方式实现起来比较简单,也兼容所有浏览器。
History
模式的路由实现原理利用了 HTML5 标准中的 history API,在不刷新页面的情况下改变浏览器的 URL,但是这种方式需要服务器端的支持,需要在服务器配置一个 fallback,意味着如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是单页面应用的入口。
keep-alive
包裹的组件会新增两个生命周期函数:
activeted
:当 keep-alive 包含的组件再次渲染的时候触发deactiveted
:当 keep-alive 包含的组件销毁的时候触发在 Vue 组件实例中,this
包含了以下属性和方法:
data
:组件的数据对象,即所谓的响应式数据。props
:父组件传递给子组件的属性集合。computed
:计算属性对象。methods
:事件方法对象。watch
:观察属性对象。$el
:组件对应的 DOM 元素。$refs
:子组件或 DOM 元素的引用对象。$emit
:触发当前组件实例上的自定义事件。$on
:监听当前组件实例上的自定义事件。$nextTick
:在下次 DOM 更新循环结束之后执行回调。$destroy
:销毁当前组件实例。除此之外,如果你在组件中使用了 Vuex 状态管理库,则 this
还会包含 $store
属性,表示 Vuex 的 Store 实例。
添加链接描述
添加链接描述
vuex 是专门为 vue 提供的全局状态管理系统,用于多个组件中数据共享、数据缓存等。(无法持久化、内部核心原理是通过创造一个全局实例 new Vue)
五个属性:
state
(初始化数据)actions
(异步处理数据)mutations
(唯一能修改state的操作)getters
(从state中动态获取相关数据)modules
(模块化)actions和mutations区别
actions
可以异步,mutations
必须同步mutations
是唯一可以修改state
的方法(commit
)actions
修改state
需要经过mutations
(dispatch)Vuex 页面刷新数据丢失怎么解决
需要做 vuex 数据持久化,一般使用本地储存的方案来保存数据,可以自己设计存储方案,也可以使用第三方插件。
推荐使用 vuex-persist插件,它是为 Vuex 持久化储存而生的一个插件。不需要手动存取 storage,而是直接将状态保存至 cookie 或者 localStorage中。
Vuex 为什么要分模块并且加命名空间
模块:为了防止store变得过于臃肿
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能会变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。
命名空间: 让模块具有更高的封装度和复用性 添加 namespaced:true
默认情况下,模块内部的 action、mutation、getter是注册在全局命名空间的 — 这样使得多个模块能够对同一 mutation 或 action 做出响应。如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced:true 的方式使其成为带命名的模块。当模块被注册后,他所有 getter、action、及 mutation 都会自动根据模块注册的路径调整命名。
辅助函数
Vuex属性使用的语法糖
原始方法
this.$store
npm install vuex --save
store.js
文件,这个文件将会包含你的应用程序的状态和状态变更方法。在这个文件中,引入Vuex
和你需要使用的状态变更方法,然后创建一个store
实例。import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
count: 0, // 你的状态
},
mutations: {
increment(state) { // 你的状态变更方法
state.count++;
},
},
});
mapState
、mapGetters
、mapMutations
和mapActions
四种辅助函数来使用Vuex。<template>
<div>
<p>Count: {{ count }}</p>
<button @click="incrementCount">Increment</button>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex';
export default {
computed: mapState(['count']),
methods: mapMutations(['increment']),
name: 'MyComponent',
};
</script>
在上面的组件中,mapState
将store
中的count
映射到组件的count
计算属性,mapMutations
将increment
映射到组件的incrementCount
方法上。当用户点击按钮时,incrementCount
方法将会触发increment
方法,修改store
中的状态。
在 Vue 中,每次数据发生变化,DOM 都不会立即更新。Vue 会在下一个时间循环中更新 DOM,以避免频繁操作 DOM 对性能的影响。如果你需要在数据更新后立即操作 DOM,就需要使用 $nextTick 方法
。
是Vue
提供的一个全局API
,是在下次DOM
更新循环结束之后执行延迟回调,在修改数据之后立即使用$nextTick
,可以在回调中获取更新后的DOM
Vue在更新DOM时是异步执行的。只要侦听到数据变化,Vue
将开启1个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher
被多次触发,只会被推入到队列中。这种在缓冲时去除重复数据对于避免不必要的计算和DOM
操作非常重要。
nextTick
方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用
应用场景:需要在视图更新之后,基于新的视图进行操作
指令本质上是装饰器,是 vue 对 HTML 元素的扩展,给 HTML 元素添加自定义功能。vue 编译 DOM 时,会找到指令对象,执行指令的相关方法
生命周期(钩子函数):
1、bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
2、inserted:被绑定元素插入父节点时调用。
3、update:被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较前后的绑定值。
4、componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
5、unbind:只调用一次,指令与元素解绑时调用。
钩子函数参数
el
:指令所绑定的元素,可以用来直接操作 DOM。name
:指令名,不包括 v-
前缀。value
:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为 2
。oldValue
:指令绑定的前一个值,仅在 update
和 componentUpdated
钩子中可用。无论值是否改变都可用。expression
:字符串形式的指令表达式。例如 v-my-directive="1 + 1"
中,表达式为 "1 + 1"
。arg
:传给指令的参数,可选。例如 v-my-directive:foo
中,参数为 "foo"
。modifiers
:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为 { foo: true, bar: true }
。vnode
:Vue 编译生成的虚拟节点oldVnode
:上一个虚拟节点,仅在 update
和 componentUpdated
钩子中可用如何注册自定义指令
<template>
<div>
<input v-uppercase type="text" v-model="message">
<p>{{ message }}p>
div>
template>
<script>
export default {
directives: {
uppercase: {
bind: function(el) {
el.addEventListener('input', function() {
var val = el.value.toUpperCase();
el.value = val;
});
}
}
},
data() {
return {
message: '',
}
}
}
script>
在上面的示例中,我们定义了一个名为 uppercase 的自定义指令,它在 bind 钩子函数中注册了一个 input 事件监听器,监听用户在输入框中输入的内容,并将其转换为大写字母。最后,我们将自定义指令 v-uppercase 绑定到了一个文本输入框上。
Vue函数式组件是一种特殊的组件,它只有一个函数作为组件本身,而不是像普通组件那样的组件选项对象。
函数式组件被设计用来优化渲染性能和减少内存消耗,因为它们缺少实例和实例创建过程中的额外开销。
Vue.component('my-functional-component', {
functional: true,
render: function(createElement, context) {
// 函数式组件的实现
}
})
在这个示例中,functional
选项告诉Vue这是一个函数式组件。render
函数接收两个参数:createElement
和context
。createElement
是用来创建VNode的函数,context
包含了一些组件上下文信息。
需要注意的是,在函数式组件中,没有实例上下文,因此无法访问this
。如果需要访问父组件传递的props、data
等信息,可以使用context.props、context.children、context.data
等属性来获取。
由于函数式组件只是一个函数,它没有状态(无法访问this.state
),也就无法使用生命周期钩子函数。因此,函数式组件通常只用于简单的UI组件,如按钮、图标等,而不是用于复杂的业务逻辑组件。
与普通组件的区别:
this
,this
通过render函数的第二个参数来代替context.listeners.click
的方式调用外部传入的事件SSR :服务端渲染
Vue的SSR(Server Side Rendering)是一种在服务器端渲染Vue组件,生成HTML页面并将其发送到客户端的技术。
优点:
beforeCreate
和 created
两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于 Node.js 的运行环境。 服务器会有更大的负载需求添加链接描述
created
请求时,是在页面渲染出来之前做的事情,如果数据过大,可能造成页面白屏过久,因为created
还没有dom元素生成,并且也不能操作dom元素。mounted
请求时,是在页面渲染出来之后做的事情,此时可以操作dom元素添加链接描述
语法上的区别:
函数式组件是一个纯函数,它是需要接受props参数并且返回一个React元素就可以了。类组件是需要继承React.Component的,而且class组件需要创建render并且返回React元素,语法上来讲更复杂。
调用方式
函数式组件可以直接调用,返回一个新的React元素;类组件在调用时是需要创建一个实例的,然后通过调用实例里的render方法来返回一个React元素。
状态管理
函数式组件没有状态管理,类组件有状态管理。
使用场景
类组件没有具体的要求。函数式组件一般是用在大型项目中来分割大组件(函数式组件不用创建实例,所有更高效),一般情况下能用函数式组件就不用类组件,提升效率。
添加链接描述
添加链接描述
shuoldComponentUpdate(nextProps, nextState) {
if (nextProps === this.props && nextState === this.state) {
return flase;
}
return true;
}
// 可以使用react提供的组件PureComponent达到相同的目的。
class App exntends React.PureCompoennt {}
跟Vue一样,React 也存在diff算法,而元素key属性的作用是用于判断元素是新创建的还是被移动的元素,从而减少不必要的Diff,因此key的值需要为每一个元素赋予一个确定的标识。
如果列表数据渲染中,在数据后面插入一条数据,key作用并不大;前面的元素在diff算法中,前面的元素由于是完全相同的,并不会产生删除创建操作,在最后一个比较的时候,则需要插入到新的DOM树中。因此,在这种情况下,元素有无key属性意义并不大。
如果列表数据渲染中,在前面插入数据时,当拥有key的时候,react根据key属性匹配原有树上的子元素以及最新树上的子元素,只需要将元素插入到最前面位置,当没有key的时候,所有的li标签都需要进行修改
并不是拥有key值代表性能越高,如果说只是文本内容改变了,不写key反而性能和效率更高,主要是因为不写key是将所有的文本内容替换一下,节点不会发生变化,而写key则涉及到了节点的增和删,发现旧key不存在了,则将其删除,新key在之前没有,则插入,这就增加性能的开销
总结
良好使用key属性是性能优化的非常关键的一步,注意事项为:
可以接收两个参数:
useEffect(()=>{
// 使用自执行函数 IIFE
(async function fn(){
await otherFn();
})()
},[])
useEffect(()=>{
const fn=async ()=>{
// do something
await otherFn()
}
fn()
},[])
const fn=async ()=>{
// do something
await otherFn()
}
useEffect(()=>{
fn()
},[])
useMemo
:计算结果是 return
回来的值, 主要用于 缓存计算结果的值useCallback
:计算结果是 函数
, 主要用于 缓存函数Context来实现跨层级的组件数据传递
添加链接描述
Store
:保存数据的容器state
:store里存储的数据 (store里面可以拥有多个state)Action
:State
的变化,会导致View
的变化;但是,用户接触不到State
,只能接触到View
;所以,State
的变化是View
导致的;Action
就是View
发出的通知 ,表示State
应该要发生变化了Action Creator
(动作创建器):View要发送多少种消息,就会有多少种Actiondispatch
:是View发出Action的唯一方法Reducer
:Store收到Action以后 ,必须给出一个新的State,这样View才会发生变化。这种State的计算过程就叫做Rducer用高阶组件Connect
建立组件与redux的连接,Connect
的第一个参数是mapStateToProps,将redux的数据映射到组件props中
dispatch派发action,通知reducer同步更新state数据
vuex和redux的区别
含义:使用软件工程的技术和方法来进行前端的开发流程、技术、工具、经验等规范化、标准化
主要目的:为了提高效率和降低成本,即提高开发过程中的开发效率,减少不必要的重复工作时间
如何实现
模块化、组件化、规范化、自动化
TypeScript的设计目的是解决JavaScript的“痛点”:弱类型和没有命名空间,导致很难模块化,不适合开发大型程序
相同:都可以描述一个对象或者函数
不同:
宽泛的类型,通常用于类和函数
泛型的本质是参数化类型,通俗的将就是所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法的创建中,分别成为泛型类、泛型接口、泛型方法
函数在定义时,若指定返回值不为void或者any,则 在函数 内 必须 写 return
TypeScript可以使用泛型来创建可重用的组件。支持当前数据类型,同时也能支持未来的数据类型。扩展灵活。可以在编译时发现你的类型错误,从而保证了类型安全
解决的问题
函数传入不确定类型参数,传入什么类型就返回什么类型
例:判断字符串、数组的最小值,定义了泛型,可直接return一个值,函数复用,而不用去写两个函数
loadable
插件可以实现组价的懒加载,第三方插件React.lazy()
也可以实现组件的懒加载,官方提供<>>
或者是
空标签包裹display:none
属性去控制true ? :null
三元的方式去写useMemo
来对结果缓存useCallback
来对函数进行缓存