1.new 一个函数,都会发生什么?
// 构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.speak = function() {
console.log(`${this.name} is speaking!`);
};
// 创建实例
var person1 = new Person("Alice", 30);
var person2 = new Person("Bob", 25);
console.log(person1.name); // 输出: Alice
console.log(person2.age,person1.speak()); // 输出: 25
1.创建了一个对象,可以获取function的原型方法,函数体存在一个局部作用域。可以用this来修改新对象的属性和方法。
2.如果构造函数返回return一个对象,那么就是返回的对象,否则就是person1的新创建对象。
3.可以原型上的对象调用方法
2.延伸了一下class创建对象
//类似于构造函数语法糖,保留了普通构造函数的使用
//增加了继承,多态,
class Car {
constructor(make, model) {
this.make = make;
this.model = model;
}
getFullName() {
return `${this.make} ${this.model}`;
}
}
const myCar = new Car("Toyota", "Camry");
console.log(myCar.getFullName()); // 输出: Toyota Camry
3.数据类型
string number boolean null undefined symbol object bigint
一般基本类型用typeof判断,引用类型基于原型链 instanceof
const arr = [1, 2, 3];
const obj = { a: 1, b: 2 };
console.log(arr instanceof Array); // 输出: true
console.log(obj instanceof Object); // 输出: true
console.log(arr instanceof Object); // 输出: true,因为数组也是对象的实例
console.log(obj instanceof Array); // 输出: false,因为普通对象不是数组的实例
但是存在一个问题,数组也是对象的实例,所以Object.prototype.toString.call(data)可以区分任意data类型
4.闭包
//作用:1,避免全局污染。 2,提供对局部变量的间接访问。3,维持变量,使其不被垃圾回收。4,回收就是:null
访问外部函数变量: 内部函数可以访问外部函数的变量,即使外部函数已经执行完毕。作用域
持久化变量: 通过闭包,外部函数的变量在内部函数的作用域中仍然存在,不会被垃圾回收机制回收
5.js在浏览器中的事件循环机制
在浏览器中,js主要是为了处理用户和浏览器之间的交互,以及操作dom,这决定了他是单线程的。但是这样如果出现耗时长的任务就会阻塞后面的任务执行,所以js引入了异步任务,js的异步实现
靠的是浏览器的多线程。(nodejs中是根据libuv 库来实现的)
浏览器中异步任务可以分为宏任务和微任务
宏任务:浏览器规定的,包括setTimeout,setInterval,ajax,dom事件等web api,I/O操作,script代码,UI rendering
微任务:es6语法规定的,包括promise.then中的回调函数,async函数中await下一行开始的代码,mutationObserver
在同一个上下文中,执行顺序为:同步代码-->微任务-->宏任务,微任务队列有微任务就先执行微任务,执行完再执行宏任务。
执行全局同步代码,将全局函数调用推入执行栈。
当发生异步事件时,将相应的回调函数推入事件队列。
当执行栈为空,事件循环检查事件队列中是否有待处理的回调函数。
如果有,将回调函数推入执行栈执行,然后移出事件队列。
重复步骤3和步骤4,直到事件队列为空。
存在执行栈:先进后出,会挨个把全局函数塞入,遇到异步就是推入事件队列(先进先出),然后用事件循环,调控栈和队列。
经典使用:当渲染和代码执行存在先后顺序,比如你希望点击弹框后,聚焦一下弹框里面的输入框,你会使用,focus()
setTimeout(() => document.querySelector('#edittagName input')?.focus(), 100),//让宏任务最后执行
6,es6新特性
(1)let,const const需在声明时赋值,并且不可改变,let可以再次改变
(2)箭头函数
(3)字符串的模板语法
(4)解构赋值
(5)展开运算符(...)
(6)新增的set map
(7)Promise
7,数组的遍历方法
(1)some() 测试数组中是不是至少有 1 个元素通过了被提供的函数测试。它返回的是一个 Boolean 类型的值。 return boolean
(2)map() 创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成,不会改变原数组。 return 新数组
(3)foreach() 数组的每个元素执行一次给定的函数。 return undefined
(4)filter() 创建给定数组的浅拷贝,过滤出复合条件的元素,不会改变原数组。 re turn 新数组
(5)find() 遍历数组,返回数组中满足提供的测试函数的第一个元素的值。return 符合条件的元素的值,如果没有返回undefined
(6)findIndex() 遍历数组,返回数组中满足提供的测试函数的第一个元素的索引。return 符合条件的元素的索引,如果没有返回-1
(7)every() 遍历数组,测试一个数组内的所有元素是否都能通过某个指定函数的测试,每一个都满足条件返回true,否则返回false
(8)reduce() 对数组中的每个元素按序执行一个由您提供的 reducer 函数 reduce((prevValue, currentValue, currentIndex) => {}, initialValue),通常用于n--->1的情况,可以用reduce实现filter,map等。
8,数组的操作:排序,合并,筛选,插入
(1)排序:sort() 接收一个比较函数compareFn(a, b) 如果返回值大于0,a在b后。如果返回值小于0,a在b前。如果返回值等于0,保持a和b的顺序。返回排序后的数组
(2)合并:concat() 用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组 。 返回新的数组
(3)筛选:filter()
(4)插入:push() 从尾部插入。 unshift() 从头部插入。splice() 通过删除或替换现有元素或者原地添加新的元素来修改数组。返回值为由被删元素组成的数组,如果没有,则返回空数组。
9.promise
promise对象主要应用于异步操作,它能够拿到异步操作最终的成功返回值或失败原因,相当于一个不知道已知值的代理。使用promise使得异步方法可以像同步方法那样返回值。
一个promise一般有三种状态:
(1)pending(待定):初始状态,既没有被兑现,也没有被拒绝
(2)fulfilled(已兑现):意味着操作成功完成
(3)rejected(已拒绝):意味着操作失败
一般使用.then(),.catch(),.finally()等方法来进行进一步操作
.then() 可以接收一个成功状态的回调函数(resolve()),也同时接收失败状态的回调函数(reject())。一般用于接收成功状态的回调函数
.catch() 接收失败状态的回调函数(reject())
Promise.all() 接收一个数组,该数组可以是多个promise请求,他会把所有promise请求变成一个promise请求,在所有请求都成功时,执行这个promise的resolve。
如果传入的参数是一个空的可迭代对象,返回一个已完成的Prmoise
如果传入的参数不包含任何promise,则返回一个异步完成的Promise
其他情况,如果全部promise完成,就返回所有完成状态的数组,如果有一个失败,就返回失败。
Promise.race() 和Promise.all() 类似,不同的时候,race只要有一个失败或成功,就采用第一个promise的值作为他的值。 (race ---> 竞速)
Promise.allSettled() 和 Promise.all()类似, 都接收数组形式的Promise,Promise.all()一旦数组中的Promise之一拒绝,就会拒绝。 Promise.allSettled永远不会拒绝,适合处理
不想关的并发请求。并且他们返回值的形式也不同,Promise.all()直接返回值的数组,Promise.allSettled() 返回status和value的对象形式的数组。
10.补充一下promise的手写简单版
// 自定义 Promise 实现
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onResolvedCallbacks.forEach(callback => callback());
}
};
const reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(callback => callback());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
return promise2;
}
resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
if (x instanceof MyPromise) {
x.then(
value => this.resolvePromise(promise2, value, resolve, reject),
reason => reject(reason)
);
} else if (typeof x === 'object' || typeof x === 'function') {
if (x === null) {
return resolve(x);
}
let then;
try {
then = x.then;
} catch (error) {
return reject(error);
}
if (typeof then === 'function') {
let called = false;
try {
then.call(
x,
value => {
if (called) return;
called = true;
this.resolvePromise(promise2, value, resolve, reject);
},
reason => {
if (called) return;
called = true;
reject(reason);
}
);
} catch (error) {
if (called) return;
reject(error);
}
} else {
resolve(x);
}
} else {
resolve(x);
}
}
catch(onRejected) {
return this.then(null, onRejected);
}
static resolve(value) {
return new MyPromise(resolve => resolve(value));
}
static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason));
}
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let completedCount = 0;
const processResult = (index, value) => {
results[index] = value;
completedCount++;
if (completedCount === promises.length) {
resolve(results);
}
};
for (let i = 0; i < promises.length; i++) {
promises[i].then(value => processResult(i, value), reject);
}
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(resolve, reject);
}
});
}
}
// 使用自定义 Promise
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("Success!");
}, 1000);
});
promise.then(value => {
console.log(value); // 输出: Success!
}).catch(error => {
console.error(error);
});
11.防抖和节流
防抖:一般用于重复操作,输入框搜索。 定义:当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,
如果设定时间到来之前,又触发了事件,就重新开始延时。也就是说当一个用户一直触发这个函数,且每次触发函数的间隔小于既定时间,那么防抖的情况下只会执行一次。
节流:按钮点击等会触发多次请求的情况。当持续触发事件时,保证在一定时间内只调用一次事件处理函数,意思就是说,假设一个用户一直触发这个函数,
且每次触发小于既定值,函数节流会每隔这个时间调用一次。
区别:防抖是将多次执行变为最后一次执行,节流是将多次执行变为每隔一段时间执行.
const debounceHandler = (func, delay) => {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => func.apply(this, args), delay);
};
};
//这边的lastExecutedTime会修改存值复新
const throttleHandler = (func, delay) => {
let lastExecutedTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastExecutedTime >= delay) {
func.apply(this, args);
lastExecutedTime = now;
}
};
};
12.e.stopPropapation()和e.preventDefault()
这个方法用于停止事件的传播,阻止事件继续向父元素和祖先元素传播。如果在事件处理函数中调用了e.stopPropagation(),那么事件将不再触发父级元素的同类型事件。
//阻止事件冒泡,相当于return
element.addEventListener('click', function(e) {
e.stopPropagation(); // 阻止点击事件向上层元素传播
console.log('Clicked inside element');
});
// 如果点击 element,只会输出 "Clicked inside element",而不会触发父元素的点击事件。
链接和表单提交时会用一下
element.addEventListener('click', function(e) {
e.preventDefault(); // 阻止链接的默认跳转行为
console.log('Link clicked, but default behavior prevented');
});
// 当点击链接时,不会跳转到链接的URL,而是输出 "Link clicked, but default behavior prevented"。
13 在浏览器地址输入url到显示页面的步骤是什么
用户输入URL: 用户在浏览器地址栏中输入网页的URL(统一资源定位符)。
DNS解析: 浏览器需要将URL中的域名解析为IP地址,以便能够找到服务器。浏览器会向DNS服务器发送解析请求,获取域名对应的IP地址。
建立TCP连接: 浏览器使用解析得到的IP地址,与服务器建立TCP连接。这涉及通过三次握手建立连接,确保浏览器和服务器之间能够通信。
发起HTTP请求: 浏览器向服务器发送HTTP请求,请求网页的资源(例如HTML、CSS、JavaScript文件等)。请求的内容取决于页面上所需的资源。
服务器处理请求: 服务器收到请求后,根据请求的内容和URL,处理请求并返回相应的资源。这可能涉及动态生成内容或者返回静态文件。
接收响应: 浏览器接收到来自服务器的HTTP响应。这个响应包含了状态码、响应头和响应体。
解析响应: 浏览器对响应进行解析,根据响应头判断响应内容的类型(如HTML、CSS、JavaScript等)。
渲染页面: 浏览器开始解析HTML并构建DOM(文档对象模型),然后解析CSS构建CSSOM(CSS对象模型),最后将DOM和CSSOM合并为渲染树,进行页面的布局和绘制。
JavaScript执行: 如果页面中包含JavaScript代码,浏览器会执行这些脚本。JavaScript执行可能会修改DOM、CSSOM,或者发起额外的网络请求。
页面加载完成: 页面的所有资源都被加载、解析、渲染和执行完毕后,页面加载完成,可以与用户进行交互。
14 http缓存方案有哪些?
强缓存: http1.0提供Expires,值为一个绝对时间表示缓存日期,Last-Modified。 http1.1提供Cache-Control: max-age= ,值为以秒为单位的最大缓存时间。 Etag
协商缓存:一般指强缓存到期的时候,这个缓存是否需要更换(If-None-Match),如果需要保留,就返回304,如果要更新就返回200(浏览器询问)
http1.0 Expires(如果用户时间错乱,就不好使)
15,浏览器的缓存方案有哪些?
主要有4种,Cookie,session,localStorage,sessionStorage
Q:Cookie 和 LocalStorage
AN:Cookie会被发送到服务器,localStorage不会。 Cookie一般最大为4k,localStorage可以用5m甚至10m
Q:LocalStorage 和 SessionStorage
AN:LocalStorage一般不会自动过期(除非用户主动清除),SessionStorage在会话结束时过期(比如关闭浏览器时)
Q: Cookie 和 Session
AN:Cookie存放在浏览器文件,Session存放再服务器文件中,Session基于Cookie实现,具体做法是将SessionId存放在cookie中
16,http和https和tcp
1,http是明文传输的,不安全,https是加密传输的,比较安全
2,http默认端口是80,https默认端口是443
3,http比较快,https比较慢(加密了)
4,https的证书一般需要购买,http不需要证书
17,前端性能优化
主要分为加载时优化和运行时优化
加载时优化:
1,减少http请求
2,使用http2代替http1.1,http1.1如果发起多个请求,就得建立多个tcp链接,http2多个请求可以公用一个tcp链接,称为多路复用。http2提供了首部压缩
3,使用服务端渲染, 优点:首屏优化好,seo好。 缺点:配置麻烦,增加了服务器的计算压力。客户端渲染的网站会直接返回 HTML文件,而服务端渲染的网站则会渲染完页面再返回这个 HTML文件
4,静态资源使用CDN-- 内容分发网络(CDN)是一组分布在多个不同地理位置的 Web 服务器。
5,将css放在文件头部,javascript文件放在底部-- 所有放在 head 标签里的 CSS 和 JS 文件都会堵塞渲染
6,使用字体图标iconfont代替图片图标
7,善用缓存,不重复加载相同的资源
8,压缩文件,压缩文件可以减少文件下载时间,让用户体验性更好。 可以用webpack的一些插件,使用gzip压缩,可以通过向 HTTP 请求头中的 Accept-Encoding 添加 gzip 标识来开启这一功能,gzip 是目前最流行和最有效的压缩方法
9,图片懒加载
运行时优化:
1,减少重绘重排 重排:当改变 DOM 元素位置或大小时,会导致浏览器重新生成渲染树,这个过程叫重排。 重绘:当重新生成渲染树后,就要将渲染树每个节点绘制到屏幕,这个过程叫重绘(修改颜色)。
什么操作会导致重排:1,添加或删除可见的 DOM 元素 2,元素位置改变 3,元素尺寸改变 4,内容改变 5,浏览器窗口尺寸改变
2, 使用事件委托
3,使用web worker
4,使用transform和opacity属性更改来实现动画 在 CSS 中,transforms 和 opacity 这两个属性更改不会触发重排与重绘
18,string的一些好用的方法。
(1) string.charAt() 接收一个索引值,返回目标字符串,用来从一个字符串中返回指定字符。
(2) string.concat(first, second) 将一个或多个字符串与原字符串合并,返回新的字符串。
(3) string.endsWith() 判断当前字符串是否是以给定字符为结尾,根据判断结果返回true或false。
(3.5) string.startsWith() 判断当前字符串是否是以给定字符开始,根据判断结果返回true或false。
(4) string.includes(string, position) 判断当前字符串是否含有给定字符串(会区分大小写),第二个参数是字符串开始搜索的位置,默认为0,根据判断结果返回true或false。
(5) string.indexOf(string, position) 查找当前字符串中给定字符串的位置,第二个人参数表示在大于或等于给定位置的第一次出现的索引,如果给定字符串存在,返回给定字符串第一次出现的索引,
如果不存在,返回-1。
(6) string.lastIndexOf(string, position) 类似string.indexOf() 返回给定字符串最后一次出现的索引值,如果不存在,返回-1。
(7) string.match() 接收一个正则表达式,如果使用g标志,返回与正则表达式匹配的所有结果,否则仅返回第一个结果。
(8) string.replace(regexp | substr, newSubStr | function) 返回一个由替换值替换后的新字符串, 如果是第一个参数是字符串,那么只会替换第一个。
(9) string.replaceAll(regexp | substr, newSubStr | function) 返回一个由替换值替换后的新字符串,会全部替换。
(10) string.slice(beginIndex, endIndex) 提取字符串的一部分,并返回一个新的字符串,不会改动原字符串。
(11) string.split(separator) 使用指定的分隔符将字符串分割成子字符串数组,传空字符串会将原字符串每个拆分成数组。
(12) string.substring(indexStart, indexEnd) 返回当前字符串在开始索引和结束索引的子集。
(13) string.toLowerCase() 会将调用该方法的字符串值转化为小写形式。
(14) string.toUpperCase() 会将调用该方法的字符串值转化为大写形式。
(15) string.trim() 从字符串的两端清除空格,返回一个新字符串,不修改原始字符串。
(16) string.trimStart() 删除字符串开头的空白字符
(17) string.trimEnd() 删除字符串末尾的空白字符
(18) string.valueOf() 返回string对象的原始值
虚拟dom的原理是什么?
//目的是为了用JS对象来模拟Dom节点,然后将其渲染成真实的dom节点,轻量级展示以及减少真实dom的操作次数
虚拟dom就是虚拟节点,react用JS对象来模拟Dom节点,然后将其渲染成真实的dom节点。
第一步就是模拟,用jsx语法写出来的div其实就是一个虚拟节点,因为jsx语法会被转译成React.createElement()函数调用
第二步就是将虚拟节点渲染成真实节点,通过一个render函数,如果是字符串或数字,创建一个文本节点,然后创建真实dom,设置属性,遍历子节点,创建真实dom,并且将真实dom缓存到
虚拟dom中,如果节点发生变化,并不直接把虚拟节点渲染到真实节点上,而是会先经过diff算法得到一个patch再更新到真实节点上。
解决了什么问题?
dom操作性能问题,通过虚拟dom和diff算法减少不必要的dom操作,保证性能不会太差。
dom操作不方便时,以前要记各种dom API,现在只需要通过setState
优点
为react带来了跨平台能力,虚拟节点除了渲染为真实节点,还可以渲染为其他东西。
让dom操作的整体性能更好,能通过diff减少不必要的dom操作。
缺点
react为虚拟dom创造了合成事件
所有react事件都绑定到了根元素上,自动实现事件委托。
如果混用合成事件和原生dom事件,可能会出bug
1,用js对象模拟dom节点
2,对比两棵虚拟dom树直接的差异
3,对真实dom进行最小化的修改
diff算法
diff算法就是对比两颗虚拟dom的算法,当组件变化时,会render出一个新的虚拟dom,diff算法对比新旧虚拟dom之后,会得到一个patch,然后react用patch来更新真实的dom。
首先对比两棵树的根节点类型有没有变化,然后检查属性,然后类似的检查子节点。
1.唯一key,从而可以在更新时重用旧节点,减少重新生成,如果key不对应,有时会出现列表的内容有重复的数据生成
2、
插入: 如果新的虚拟DOM树中有节点,而旧的虚拟DOM树中没有相应的节点,则进行插入操作。
移动/重排: 如果新旧虚拟DOM树中有相同类型的节点,但在同级位置发生了变化,则会进行移动操作,而不是重新创建节点。
更新: 如果同级节点的类型相同,但内容发生了变化,则进行更新操作。
删除: 如果新的虚拟DOM树中没有相应的节点,而旧的虚拟DOM树中有相应的节点,则进行删除操作。
3.批量更新: React会对需要更新的节点进行批量更新,而不是每次发生变化都立即更新。这样可以减少DOM操作的次数。
bfc
BFC指的是块级格式化上下文,独立的渲染区域,里面的元素不会影响到外面的元素,形成BFC的条件----> position为absoulte或fixed,overflow不是visible,display为inline-block或flex的元素。
因为存在一些特性:块级盒子,外边距折叠,浮动元素,阻止元素重叠,防止文字环绕。为了去除这些做的处理
async和await
可以让我们用一种更简洁的方式写出基于Promise的异步行为,而无需刻意的链式调用Promise,避免回调地狱。
注意点:
1.await 只能在 async 函数内部使用,如果在非 async 函数中使用会导致语法错误。
2.一般需要try catch捕获一下异常:健壮性
3.返回时promise,可以用变量存取,也可以用then获取返回值
4.可以想象,await的后面的代码,其实是写在then里面的,因此会存在阻塞。
useState 和 useEffect
useState使用了数组解构的方式,他会返回一对值,当前状态和一个让你更新他的函数,通过调用他来给组件添加一些内部的state,React会在重复渲染时保留这个state。
useEffect来模拟类组件的生命周期。如初始化,卸载时,以及实时更新状态
redux是一个全局的状态管理库
它主要有以下几个核心概念,
state:我们要访问的全局数据
store:store是一个仓库,通常把state放到store中,包括reducer等
connect:用来链接组件和state,他是一个高阶组件,提供两个接口,读和写操作,用MapStateToProps封装读,用MapDispatchToProps封装写。
dispatch:用来触发state的更改,他接收一个action,action包括type和payload,type描述要进行的操作,payload就是载荷。
reducer:用来规范创建state的过程,接收state和action,根据传入的旧state和action,返回新的state。(action等于type + payload)
//规则是这些,思想上是作为全局的状态管理,你肯定希望是随处可用,并且不受污染,所以需要一个独立的空间,分发修改数据,展示。
什么是模块化?
模块化是从代码的角度分析的,把一些可复用的代码抽成一个单独的模块,便于项目开发和维护。
什么是组件化?
组件化是从ui元素的角度分析的,把一些可复用的ui元素抽成一个单独的组件,便于项目的维护和开发。
react中keys的作用是什么?
在开发过程中,我们要保证某个元素的key在其同级元素中具有唯一性,
什么是ssr,服务端渲染?
ssr指的是服务端渲染,把ajax请求放到服务端,页面加载到浏览器或客户端前就已经把数据填充到页面模板形成完成的页面。
优势:减少首次http请求,在服务端请求首屏数据,直接渲染html。 利于seo,网络爬虫可以抓取到完整的页面信息。
劣势:服务端压力大,需要掌握前端到服务端的开发
什么是客户端渲染?
客户端渲染指的是客户端通过ajax请求获取数据,然后在客户端生产dom插入到html
优势:1,前后端分离,各司其职。 2,局部刷新,无需刷新页面。 3,服务器压力小。 4,更好的实现前端各种效果。
劣势:1,首屏渲染慢,需要下载js和css文件。 2,不利于seo
useCallback和usememo的区别是什么?
useCallback和usememo都是为了优化性能的,避免重复渲染。
他们都接收一个函数和一个依赖项,不同的地方是usememo通常用于缓存一个值,useCallback通常用来缓存函数。
useCallback搭配React.memo使用,React.memo通过监听子组件的props和state是否变化来对子组件进行渲染。
usememo:相当于vue的computed,有缓存的作用,是一个值的展示,当然也可以是函数
useCallback:使用需谨慎,容易产生bug,
他的目的是为了缓存函数,避免在每次渲染时都创建新的函数实例。一般使用场景就是子组件传递和依赖项优化
function ParentComponent() {
//不包裹一下,永远是新生成的
const handleButtonClick = useCallback(() => {
console.log('Button clicked');
}, []);
return <ChildComponent onClick={handleButtonClick} />;
}
function ChildComponent({ onClick }) {
// 在这里使用缓存的回调函数
return <button onClick={onClick}>Click me</button>;
}
const fetchData = useCallback(async () => {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
setData(result);
}, []);
//依赖项是函数,要不然会eslint
useEffect(() => {
fetchData();
}, [fetchData]); // 传递缓存的 fetchData 函数作为依赖项
什么时候会触发函数组件更新
状态变化: 当使用 useState 或 useReducer 创建的状态发生变化时,React 会重新执行函数组件,并在新的渲染中使用新的状态值。
属性变化: 父组件向子组件传递的属性(props)发生变化时,子组件会被重新渲染。函数组件会使用新的属性值进行渲染,以反映属性的变化。
父组件重新渲染: 当函数组件的父组件发生重新渲染时,函数组件也会被重新执行,从而触发更新。
所以子组件使用 React.memo: 如果使用 React.memo 包裹函数组件,React 会在新的属性值传入时比较旧的和新的属性值,仅在属性值发生变化时触发更新。
rfc
useEffect和useLayoutEffect的区别是什么?
首先他们的使用方式都一样,接收一个函数和一个依赖性数组。
不同的地方在于useEffect是异步执行的,useLayoutEffect是同步执行的。 useEffect的执行时机是浏览器完成渲染之后,useLayoutEffect的执行时机 是浏览器把内容真正渲染到界面之前,和componentDidMount等价。也就是说最好把操作dom的相关操作放到useLayoutEffect中。
总结:1,优先使用useEffect,他是异步执行的,不会阻塞渲染。 2,会影响到渲染的操作尽量放到useLayoutEffect中,避免闪烁问题。 3,不要将大量的计算
放到useLayoutEffect中,会造成页面卡顿。大部分场景使用useEffect来解决,当useEffect无法解决时,使用useLayoutEffect处理。
call,apply,bind的区别?
比如一个常用的场景,调用函数的时候,我们用.来调用这个函数,此时函数的this就指向.前面的调用者,首先这三者都可以改变this的指向,
call的第一个参数就是this的指向,随后的参数作为函数的参数来使用,并且调用。
apply和call的区别是,apply的第二个参数为调用函数时的参数构成的数组,如果不给函数传参数的话,call和apply的作用就是一样的。
bind和他们的区别比较大,他是用来绑定this指向的,他返回一个原函数被绑定this后的新函数。
apply,类似Array.prototype.push.apply(arr1, arr2)
map和set的区别是什么?
1,map对象是键值对的集合,他的key不仅可以是字符串,还可以是其他类型
2,set对象类似于数组,并且他的值都是唯一的,我们可以用set来进行数组去重
css选择器的优先级
1,选择器越具体,优先级越高
2,相同优先级,出现在后面的,覆盖前面的。
3,属性后面加!important的优先级最高。
4.内联style,id,class,元素选择伪元素权重
两种盒模型的区别(box-sizing)?
默认是content-box
第一种盒模型是content-box,width指定的是content区域的宽度,而不是实际宽度。实际宽度就是width+padding+border
第二种盒模型是border-box,width指定的是实际宽度
如何解决跨域?
1,JSONP
(1)甲站点利用script标签可以跨域的特性,向乙站点发送get请求。
(2)乙站点通过后端改造js文件的内容,将数据传进回到函数。
(3)甲站点通过回调函数拿到乙站点的数据。
2,CORS
(1)在响应头中添加Access-Control-Allow-origin:
(2)
3,Nginx代理
axios请求拦截器和响应拦截器
1,请求拦截器,在请求发送前进行必要操作处理,例如统一添加cookie,请求体加验证,设置请求头等,相当于对每个接口里相同操作的一个封装。
2,响应拦截器,在请求得到响应之后,对响应体的一些处理,通常是数据统一处理,也常来判断登录失效等。
为什么要对axios进行二次封装?
1,如果要统一设置请求头,怎么办,如果要针对接口拦截返回值并处理数据,怎么做,如果要统一设置loading动画,怎么做,对axios的二次封装就是为了做到统一处理。
CommonJS和ES Modules的一些区别
1,CommonJS是运行时加载模块,ESMoudle是在静态编译期间就确定模块的依赖
2,CommonJS加载的是整个模块,将所有接口全部加载进来,ESModule可以单独加载其中的某个接口
3,CommonJs导出的是一个值拷贝,会对加载结果进行缓存,一旦内部再修改这个值,则不会同步到外部。ESModule是导出的一个引用,内部修改可以同步到外部
yarn和npm的区别是什么? 为什么选择yarn
1,yarn的安装速度更快,因为他是异步执行安装依赖的,npm是同步执行安装依赖的。
2,yarn安装后是有个yarn.lock文件,这个文件是会锁定你安装的版本,别人安装时会直接读取yarn.lock文件,
这样可以保证安装的依赖的版本是一样的,npm在5.x.x的版本也引入了这个机制,它的文件叫package-lock.json。
umi项目中dva的hooks使用方法
1,useDispatch() 获取dispatch
2,useSelector() 获取部分数据
3,useStore() 获取store
umi和dva是什么?
1,umi和dva都是基于React的框架,Umi主要以路由为主,Dva主要管理数据流
2,umi以路由为基础,支持约定式路由。
3,dva是基于redux和redux-saga的数据流方案,
前端工程化你怎么理解?
1,前端工程化首先要做的就是支持各种代码的编译,打包和构建(webpack,vite)。
2,代码的规范,比如ESLint,
3,代码上线的话,需要进行构建和部署,我们可以通过 jenkins来组织构建规范
4,前端工程化是指围绕代码处理的一系列工具链,他们把代码当作字符串处理,并不运行代码,包括编译构建、静态分析、格式化、CI/CD 等等
5,rollup,webpack,vite这些打包工具
react中父组件怎么调用子组件的方法?
类组件中:
1,React.createRef(),用ref指向子组件实例,获取子组件方法。
2,使用props回调一个函数
函数组件中:
1,可以useImperativeHandle 和 forwardRef结合使用,useImperativeHandle有两个参数,一个是ref,对组件实例的引用,第二个是自定义公开哪些方法和属性的对象。
const Index: React.FC<Props> = forwardRef((props,ref)=>{})
useImperativeHandle(ref, () => ({
返回出来的东西
}))
在外面就是ref获取
(45)说一下react-router
简介: react-router是基于react的路由库,他可以帮助开发者在react中实现路由功能,react-router包含了多个组件,可以用来构建单页面应用的路由功能,
例如BrowserRouter,Route,Link等。
模式:
(1) Hash 路由是一种通过在 URL 中使用 # 符号来实现页面切换和路由跳转的方式,他是通过window.onhashchange的方式来监听hash的改变,hash模式下所有页面的跳转都是在客户端进行操作,不会提交到客户端,优点是兼容性好,缺点是URL中会包含#符号,可能会对seo和用户体验造成一些影响,页面刷新时,浏览器不重新发起请求
(2) History 路由是一种使用浏览器历史记录 API 来实现路由跳转的方式,history API是H5提供的新特性,主要使用history.pushState和history.replaceState改变URL,使用 history 模式时,在对当前的页面进行刷新时,此时浏览器会重新发起请求,需要通过服务端来配置(将所有路径重定向到index.html),允许地址访问。
主要工具:
(1)Router:负责管理应用的路由状态,一般使用BrowserRouter或HashRouter组件来创建。
(2)Route:定义一个路由规则,根据URL的匹配情况渲染对应的组件
(3)Link:用来定义路由之间的链接,可以自动生成相应的URL
(4)Routes:接受Route作为子路由,匹配当前路径下的所有子路由
(5)Redirect:用来重定向路由,可以指定重定向的URL或者重定向到另一个Route
(6)Outlet:类似于vue中的router-view,用来占位的,展示子路由
(7)useLocation: 这个hooks返回当前的location对象
(8)useParams:返回当前的路由参数信息
(9)useRoutes:接收一个数组,类似于Routes,useRoutes可以根据路由配置对象来动态地嵌套路由
(10)useNavigate: 返回一个函数,可以进行路由的跳转
1rem,1em,1vh,1px各自代表什么?
1rem表示根元素(html元素)的字体大小,相对于根元素的字体大小进行计算,例如:如果根元素的字体大小为16px,则1rem等于16px
1em表示当前元素的字体大小,相对于其父元素的字体大小进行计算,例如:如果父元素字体大小为16px,子元素的字体大小为1em,则子元素的字体大小为24px
1vh表示视口高度的1%,视口指的是浏览器窗口的可见区域。例如,如果视口高度为1000px,则1vh等于10px
1px表示一个像素点的大小,像素点是显示器或设备上显示图像的最小单位,
fixed定位是相对于窗口的,如果父元素加上了transform,fixed定位会根据父元素来定位。
dom事件中的target和currentTarget的区别
<div id="outer">
<button id="inner">Click me</button>
</div>
document.getElementById("outer").addEventListener("click", event => {
console.log(event.target.id); // 输出 "inner"
});
document.getElementById("outer").addEventListener("click", event => {
console.log(event.currentTarget.id); // 输出 "outer"
});
1.存在事件冒泡,可能不是同一个元素
2.当都是inner,那么event.target===event.currentTarget,当在外面,target触发事件的真实元素,currentTarget是事件绑定的元素,谁绑了是谁
3.用来处理事件委托,和事件冒泡,看到底是谁触发了点击事件,然后用stopevent
深拷贝的实现原理是什么,说一下深拷贝和浅拷贝,怎么实现他们?
浅拷贝会复制原始数据结构的一层内容,但不会递归复制深层嵌套的数据。如果拷贝后的对象中包含对象引用,那么原始对象和拷贝对象将共享这些引用指向的实际数据。
浅拷贝的实现方法包括:Object.assign()、展开运算符 ...、Array.prototype.slice()(用于数组)、Array.prototype.concat()(用于数组)等。这些方法都会创建一个新的对象或数组,但只会复制一层内容
function deepCopy(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj; // 基本类型直接返回
}
const copy = Array.isArray(obj) ? [] : {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopy(obj[key]); // 递归拷贝嵌套对象
}
}
return copy;
}
}
说一下原型链,对象,构造函数之间的关系
对象: 在 JavaScript 中,几乎所有的事物都是对象,包括基本数据类型(如数字、字符串等)。对象是属性的集合,每个属性都由键(字符串)和值组成。对象可以通过对象字面量、构造函数或其他方式创建。
构造函数: 构造函数是一种特殊的函数,用于创建和初始化对象。它是创建同一类型对象的模板。通过构造函数可以定义对象的属性和方法。在 ES6 之前,构造函数通常以大写字母开头,但实际上这只是一种约定,不是必须的。
原型链: 在 JavaScript 中,每个对象都有一个原型(prototype),它是一个指向另一个对象的引用。这个原型对象本身也有一个原型,以此类推,形成了一个原型链。原型链是一种对象之间继承关系的表示方式,用于在对象之间共享属性和方法。
关系解释:
每个构造函数都有一个 prototype 属性,这是一个普通对象,其中包含构造函数创建的实例共享的属性和方法。构造函数通过 new 关键字创建对象时,实际上是将新对象的原型指向构造函数的 prototype。
每个对象都有一个 __proto__ 属性,指向其原型对象。这个属性在一些浏览器中可见,但是在标准中不建议直接使用。取而代之,可以使用 Object.getPrototypeOf(obj) 来获取对象的原型。
原型链就是通过 __proto__ 属性连接起来的一系列原型对象,使得一个对象可以通过原型链访问其他对象的属性和方法。如果某个对象上没有找到某个属性或方法,JavaScript 将会在原型链上继续查找。
构造函数通过原型链与它们创建的实例对象相连接。实例对象可以访问构造函数的原型上的属性和方法,因此实现了继承。
总之,原型链是 JavaScript 中实现对象之间继承关系的一种机制,构造函数和对象通过原型链相互关联,实现了属性和方法的共享和继承
webpack的入口文件怎么配置,多个入口怎么分割等
module.exports = {
entry: './src/index.js', // 单个入口文件
entry: {
main: './src/main.js',
vendor: './src/vendor.js'
},
// ...
};
描述最难的问题
1,没有遇到非常难的,只要遇到问题一定会解决。
2,如果遇到,我要怎么解决,二分法,debug,查哪一行出现了问题,然后去调试。
3,遇到一个问题,先去搜,没搜到,去翻博客,没找到,然后去看github一些isure。(stackoverflow),找到了方案,然后去实施,然后发现了一个新的问题,
react受控组件和非受控组件
例如一个FInput组件,他接收一个value 和一个onchange 函数,那么他的输入和输出都受使用者来操作,那么他就是一个受控组件
如果一个FInput组件,他只接受一个defaultValue,那么使用者没法完全控制这个组件,那么他就是一个非受控组件
function ControlledComponent() {
const [inputValue, setInputValue] = useState('');
const handleInputChange = event => {
setInputValue(event.target.value);
};
return (
<input type="text" value={inputValue} onChange={handleInputChange} />
);
}
function UncontrolledComponent() {
const inputRef = useRef(null);
const handleButtonClick = () => {
alert(`Input value: ${inputRef.current.value}`);
};
return (
<>
<input type="text" ref={inputRef} />
<button onClick={handleButtonClick}>Get Value</button>
</>
);
}
有哪些常见的loader和plugin
loader:
css:css-loader,postcss-loader,scss-loader,less-loader,style-loader
js: babel-loader,eslint-loader,
plugin:
htmli-webpack-plugin
提高构建速度
1,开发环境不做无意义的操作。
2,使用DllPlugin,提供了把代码分离的方式,提高构建速度。将不常变化的代码提前打包,并复用
3,压缩代码,提取公共代码(CommonsChunkPlugin插件)
webpack和vite的区别是什么?
1,开发环境的区别
a,vite自己实现serve,不对代码打包,充分利用浏览器对<script type=module>的支持
2,生产环境区别
a,vite使用rollup + esbuild来打包js代码
b,webpack使用babel来打包js代码,比esbuild慢很多
3,文件处理时机
a,vite只会在你请求某个文件的时候处理该文件
b,webpack回提前打包好main.js,等你请求的时候直接输出打包好的js给你