前言
陆陆续续也是快面了十场来了,也是到了周末,也得好好沉淀一下,把最近遇到的面试题梳理一下吧!
面试题
git的一些用法吧
基础用法
-
git add .
: 将当前目录下的所有修改和新增文件添加到暂存区。这意味着你准备将这些更改包含在下一个提交中。 -
git status
:示工作目录和暂存区的状态。它会告诉你哪些文件已被修改但未被暂存,哪些文件已经被暂存准备进行下一次提交。 -
git commit -m "写入备注"
:提交暂存区的所有更改到本地仓库,并附上提交信息"写入备注"。这是对代码库状态的一个快照,提交信息应该清晰地描述此次提交的内容或目的。 -
git log
:会列出所有的提交记录,默认情况下按照时间倒序排列(最新的提交在前),每条记录包括提交哈希值、作者、日期以及提交信息。 -
git push origin <branch_name>
:这个操作会将你的本地提交推送到远程仓库对应的分支上,使得团队其他成员可以看到你的更改。 -
git checkout -b feature2
:创建并切换到一个名为feature2的新分支。这个命令通常用于开始开发新功能或特性,确保新功能的开发不会影响主项目的稳定性。 -
git checkout feature2
:如果feature2分支已经存在,这条命令用于切换到该分支。这允许你在不同分支间切换,以便专注于特定的任务或功能开发。 -
git branch
:列出所有本地分支,并高亮显示当前所在的分支。这是一个很有用的命令,可以帮助你确认自己当前处于哪个分支,以及项目中有哪些分支。 -
git merge main
:在当前分支(在这个例子中是feature2)中执行此命令,将会把main分支的更改合并进来。这样做是为了确保feature2分支包含了最新的更新,从而避免与其他开发者的工作产生冲突,同时也能让你的功能基于最新的代码进行开发。
git stash
在场景中:当你的上司说,公司重要的业务出现了bug,需要你去调试一下,而你却还是开发另一个项目的小组件,还未完成测试等,这时能git add. git commit git push origin main
吗?显然是不行的,这时候就需要用到git stash
-
Stash 当前更改 :在你的工作目录中,如果你对文件进行了修改但还不想提交这些更改,可以使用
git stash
命令。这会将所有未提交的更改(包括已修改和新增的文件)保存到一个临时区域,并恢复工作目录到最近一次提交的状态。perlgit stash git stash push -m "备注"
-
查看 Stash 列表:如果你想查看已经存储的所有 stashes,可以使用:
git stash list
-
应用 Stash:当你完成了紧急的任务并返回来继续之前的工作时,可以通过以下命令将最近一次 stash 的内容重新应用到工作目录中:
git stash apply
git stash apply
不会自动删除最近一次stash的内容,而git stash pop
会同时应用并删除最近一次的stash。
如果有多个 stashes,你可以通过指定 stash 名称来应用特定的 stash,例如 git stash apply stash@{1}
。
- 删除 Stash :如果你不再需要某个 stash,可以在应用之后通过
git stash drop
删除它。如果你确定要一次性删除所有的 stashes,可以使用git stash clear
。
后悔药 git reset --soft
git reset --soft <commit>
命令会将当前分支的 HEAD 指针移动到指定的 <commit>
,但是不会改变暂存区和工作目录。这意味着:
- 暂存区:保持不变。即所有在暂存区中的更改都会保留。
- 工作目录:也保持不变。也就是说,你的文件改动依然存在,并且这些改动未被撤销。
场景: 假设你已经做了三次提交,但突然意识到有一次提交(HEAD)中包含了一个不应该存在的错误
而你想修正这个错误但同时不想丢失自上次提交以来的工作,你可以这样做:
-
首先,查看提交日志以确定你要回退到哪个提交:
bashgit log
-
然后,使用
git reset --soft
回退到目标提交(例如,倒数第二次提交):cssgit reset --soft HEAD~1
或者直接指定要回退到的提交哈希值:
cssgit reset --soft <commit-id>
-
此时,你的工作目录和暂存区看起来就像你在做最后一次提交之前一样,但所有的修改仍然保留在暂存区中。现在,你可以编辑这些修改,添加更多的更改,或者直接进行新的提交。
- 最后,当你准备好后,可以再次执行
git commit
来创建一个新的提交。
更强大的后悔药git reflog
场景: 当你提交了三次,然后你想回退到第二次时,却回退到了第一次,就像上面这个案例一样,那么如何挽救呢?
- 首先用
git reflog
查看历史记录,把错误提交的那次 commitHash 记下。
- 然后勒,再使用
git reset --soft
回倒过去~~~
- 最后检查提交,发现2回来了
算法题:快排 and 冒泡优化
有些厂就爱考快排,那么来看看怎么拿捏吧。
快排
概念: 快速排序(QuickSort)是一种高效的、基于分治法的排序算法。其基本思想是通过一个称为"基准"(pivot)的元素,将待排序数组分为两部分:一部分的所有元素都比另一部分的所有元素小,然后递归地对这两部分进行排序。
手写:
js
function quickSort(arr) {
// 基准情况:如果数组长度为0或1,则直接返回该数组
if (arr.length <= 1) {
return arr;
}
// 基准值选择中间位置的元素
const pivot = arr[Math.floor(arr.length / 2)];
// 分区:分别收集小于、等于和大于基准值的元素
const left = arr.filter(x => x < pivot);
const middle = arr.filter(x => x === pivot); // 收集等于pivot的元素
const right = arr.filter(x => x > pivot);
// 递归调用quickSort,并连接结果
return [...quickSort(left), ...middle, ...quickSort(right)];
}
这时面试官又问,那当数组基本有序时,还需要排一下序,使用快排就接近 O(n^2),你会怎么处理呢?
为什么当序列接近有序时复杂度更高
在理想情况下,快速排序的平均时间复杂度为O(n log n)。然而,快速排序的性能高度依赖于基准值的选择。如果序列已经接近有序,并且每次都选择最差的基准值(比如总是选择第一个或最后一个元素作为基准),会导致不平衡的分区:
- 在最坏的情况下(例如,对于一个完全有序的数组,如果每次都选择第一个或最后一个元素作为基准),每次分区只会把数组分成一个大小为n-1和一个大小为0的两个子数组。这意味着每次递归只能减少一个元素,导致递归树深度达到n层,每层的工作量总和为n,因此总体的时间复杂度退化为O(n^2)。
冒泡:
概念: 想必冒泡的思想大家都比较熟悉了,它重复地遍历要排序的列表,比较相邻元素并根据需要交换它们。这个名字来源于较小的元素逐渐"冒泡"到列表顶端的过程。尽管冒泡排序不是最有效的算法之一,特别是对于大数据集,但它易于理解和实现。
手写:
js
function optimizedBubbleSort(n) {
const len = arr.length;
for(let i=0;i<len-1;i++){
let swapped = false; // 标志是否发送交换
for(let j=0;j<len-1-i;j++){
if(arr[j]>arr[j+1]){
[arr[j],arr[j+1]] = [arr[j+1],arr[j]];
swapped = true;
}
}
if(!swapped){
break; // 如果没有发生交换,提前终止循环
}
}
return arr;
}
这时面试官,还是不满意,问你还能优化一下时间复杂度吗?可是你明明已经做了优化,还有什么办法?
优化冒泡:
js
function advancedBubbleSort(arr) {
const len = arr.length;
// 记录最后一次交换的位置
let lastSwapPos = len - 1;
// 记录本轮需要比较到的位置
let swapBorder = len - 1;
for (let i = 0; i < len - 1; i++) {
let swapped = false;
lastSwapPos = 0;
for (let j = 0; j < swapBorder; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j+1]] = [arr[j+1], arr[j]];
swapped = true;
lastSwapPos = j;
}
}
swapBorder = lastSwapPos
if (!swapped) {
break;
}
}
return arr;
}
lastSwapPos
和swapBorder
的使用 :这两个变量是这个优化版冒泡排序的核心。lastSwapPos
记录了在当前轮次中最后一次交换发生的位置,而swapBorder
则用于限制下一轮比较的范围。因为一旦某一轮次中最后一个交换发生在位置k
,那么从k
到数组末尾的所有元素就已经是有序的了,不需要再对这部分进行比较。
对象声明有哪些方式?
1.对象字面量:
对象字面量由一组键值对组成,键和值之间用冒号 :
分隔,每对键值对之间用逗号 ,
分隔,并且整个对象被包裹在一对花括号 {}
内。键通常是字符串(尽管引号通常是可选的),而值可以是任何有效的 JavaScript 表达式,包括其他对象、数组、函数等。
js
let person = {
firstName: "John",
lastName: "Doe",
age: 25,
isStudent: false,
fullName: function() {
return this.firstName + " " + this.lastName;
}
};
2.Object.create
Object.create
是 JavaScript 中用于创建新对象的一个方法,它允许你通过指定一个原型对象来创建一个新的对象。
用法:
js
let animal = {
speak() {
console.log(`${this.name} makes a noise.`);
}
};
let dog = Object.create(animal);
dog.bark = function() {
console.log(`${this.name} barks.`);
};
let myDog = Object.create(dog);
myDog.name = 'Rex';
myDog.speak(); // 输出: Rex makes a noise.
myDog.bark(); // 输出: Rex barks.
// 原型链
console.log(myDog.__proto__===dog)
console.log(myDog.__proto__.__proto__===animal)
内部执行机制
js
Object.create2 = function(proto, propertiesObject = undefined) {
// 检查 proto 是否为对象或函数
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError('Object prototype may only be an Object: ' + proto);
}
// 错误检查:propertiesObject 不应为 null
if (propertiesObject === null) {
throw new Error('this is a bug');
}
// 创建一个临时构造函数 F,并将其原型设置为 proto
function F() {}
F.prototype = proto;
// 使用 F 构造函数创建新对象
const obj = new F();
// 如果提供了 propertiesObject,则使用 Object.defineProperties 定义属性
if (propertiesObject !== undefined) {
Object.defineProperties(obj, propertiesObject); // 注意:原代码中的propertyObject应为propertiesObject
}
// 如果 proto 是 null,设置 obj.__proto__ 为 null
if (proto === null) {
obj.__proto__ = null;
}
return obj;
}
3.NEW
new
关键字用于创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一。
手写new 源码分析
- 第一版写法:
js
function shouxienew() {
var obj = new Object();
var Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype; // 修改这里,设置正确的原型链
var ret = Constructor.apply(obj, arguments); // 注意:这里的arguments已经被shift移除了第一个参数
// 检查ret是否为对象或数组,注意排除null
return (typeof ret === "object" && ret !== null) || typeof ret === "function" ? ret : obj;
}
- 第二版写法:
js
function myNew(Constructor, ...args) {
if (typeof Constructor !== 'function') {
throw new TypeError('Constructor must be a function');
}
// 创建一个空对象,并将其原型设置为构造函数的 prototype 属性
const obj = Object.create(Constructor.prototype);
// 使用 apply 方法调用构造函数,指定 this 指向新创建的对象,并传入参数
const result = Constructor.apply(obj, args);
// 检查构造函数是否返回了一个对象或 null,如果是则返回该对象;否则返回新创建的对象
return (result !== null && (typeof result === 'object' || typeof result === 'function')) ? result : obj;
}
4.Object.assign
Object.assign()
是一种用于 JavaScript 的方法,它允许你将一个或多个源对象的可枚举属性的值复制到目标对象中。这个方法使用的是浅拷贝而非深拷贝,意味着如果属性值是对象,那么将引用相同的对象
展示用法
js
// 创建对象 Object.assign
const target ={ a:1,b:2}
const source ={ b:4,c:5}
// 合并对象
const result1 = Object.assign(target,source)
console.log(result1,target) // { a: 1, b: 4, c: 5 } { a: 1, b: 4, c: 5 }
我们可以发现,使用该方法,生成了新的对象result1,同样也使target发生了改变,如何可以生成新的对象,又不改变target呢?
js
// 创建对象 Object.assign
const target ={ a:1,b:2}
const source ={ b:4,c:5}
// 合并对象
const result2= Object.assign({},target,source)
console.log(target,source,result2) { a: 1, b: 2 } { b: 4, c: 5 } { a: 1, b: 4, c: 5 }
我们将第一个参数传入一个空对象即可。
END
码字不易,对你帮助的话也请点个赞,祝愿大家都能找到心仪的工作