1、猿辅导前端面试算法题
作者:互联网
1. 算法题
1、二叉树宽度
- 遍历二叉树,将非空的结点存到队列,其中一个元素表示为[结点,层次,结点编号]
- obj中记录当前的层次、当前层次的开始结点编号、结束结点编号[层次,开始编号,结束编号]
- 如果出队结点的层次与当前层次不同,则更新当前节点的层次。并根据obj中的开始、结束编号,记录上一层的宽度。与当前最大层次比较,取较大值
- 注意:每当遍历到下一层第一个节点时,才会记录上一层的宽度。因此最后需要单独计算最后一层的宽度。
注意: 答案在32位有符号整数的表示范围内。
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
//1.遍历二叉树,将非空的结点存到队列,其中一个元素表示为[结点,层次,结点编号]
//2.obj中记录当前的层次、当前层次的开始结点编号、结束结点编号[层次,开始编号,结束编号]
//3. 如果出队结点的层次与当前层次不同,则更新当前节点的层次。
// 并根据obj中的开始、结束编号,记录上一层的宽度。与当前最大层次比较,取较大值
//4. 注意:每当遍历到下一层第一个节点时,才会记录上一层的宽度。因此最后需要单独计算最后一层的宽度。
var widthOfBinaryTree = function(root) {
let res = 0;
if(!root) return res;
let que = [[root, 0, 1]]; //[节点,层次,编号]
let obj = { //当前的层次,开始编号,结束编号
level : 0,
start : 1,
end : 1
}
let can = Math.pow(2,32)-1;
while(que.length){
//1. 出队
let [node, level, idx] = que.shift();
//2. 判断层次是否变化
if(obj.level === level){//层次未变,更新当前层的结束节点编号
obj.end = idx;
}else{// 层次变化,计算当前最大宽度。更新层次,开始结束编号
res = Math.max(res, (obj.end-obj.start+1));
obj.level = level;
obj.start = idx; //当前层的第一个节点即为开始编号
obj.end = idx; //在遍历中更新 结束编号
}
//3. 将非空左右孩子入队
if(node.left) {que.push([node.left, level+1, (idx*2)%can])}
if(node.right) {que.push([node.right, level+1, (idx*2+1)%can])}
}
//4. 计算最后一层的宽度
res = Math.max(res, obj.end-obj.start+1);
return res;
};
2、手撕快排
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
* 将给定数组排序
* @param arr int整型一维数组 待排序的数组
* @return int整型一维数组
*/
function MySort( arr ) {
Quicksort(arr,0,arr.length-1);
return arr;
}
function Quicksort(arr, low, high){
let l = low;
let h = high;
if(l < h){
let p = arr[l];
while(l<h){
while(l<h && arr[h]>=p) h--;
if(l<h) arr[l] = arr[h];
while(l<h && arr[l]<=p) l++;
if(l<h) arr[h] = arr[l];
}
arr[l] = p;
Quicksort(arr,low,l-1);
Quicksort(arr,l+1,high);
}
}
module.exports = {
MySort : MySort
};
3、手撕冒泡排序
function BubbleSort(arr){
for(let i=arr.length-1;i>0;i--){
let isChange = false; //未交换为false
for(let j=0;j<i;j++){
if(arr[j]>arr[j+1]){
let temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
isChange = true;
}
}
if(!isChange) break; //本趟未交换,则直接退出
}
}
4、岛屿的最大面积
/**
* @param {number[][]} grid
* @return {number}
*/
var maxAreaOfIsland = function(grid) {
//深度优先遍历。每次遍历完将其置为0
let res = 0;
for(let i=0;i<grid.length;i++){
for(let j=0; j<grid[0].length; j++){
if(grid[i][j] === 1){
let count = dfs(grid, i, j);
res = Math.max(count,res);
}
}
}
return res;
};
function dfs(grid, x, y){
//1. 递归出口:下标越界 或 遇到水,即grid[x][y]===0,直接返回0
if(x<0 || x>=grid.length || y<0 || y>=grid[0].length || grid[x][y]===0){
return 0;
}
grid[x][y] = 0; //将访问过的陆地记位0,避免重复访问
let count = 1; //记录访问过的陆地数目
count+= dfs(grid, x+1, y);
count+= dfs(grid, x-1, y);
count+= dfs(grid, x, y+1);
count+= dfs(grid, x, y-1);
return count;
}
5、二叉树层序遍历
function levelOrder( root ) {
let res = [];
let que = [];
if(!root) return res;
que.push(root);
while(que.length>0){
let len = que.length;
let tem = [];
for(let i=0; i<len; i++){
let node = que.shift();
tem.push(node.val);
if(node.left) que.push(node.left);
if(node.right) que.push(node.right);
}
res.push(tem);
}
return res;
}
6、最长公共子串(子串是连续的)
思路:
- 公共子串肯定在较短的字符串中,用str1存储较短的字符串;
- 先从最大长度为1,str1中开始查找;
- 只要str2中存在当前子串,则最大长度+1,继续向后查找
function LCS( str1 , str2 ) {
let res = '';
//1. 确定最短字符串
if(str1.length > str2.length){
[str1, str2] = [str2, str1];
}
//2.最短字符串长度,当前的最大长度
let len = str1.length;
let maxlen = 0; //表当前的最大长度-1的值
//3.遍历最短字符串str1,寻找最长子串
for(let i=0; i<len; i++){
//4.获取当前终点为i,长度maxlen+1的子串
let s = str1.slice(i-maxlen, i+1);
//5. 为str2的子串,继续将最大长度+1
if(str2.indexOf(s)!=-1){
maxlen++;
res = s;
}
}
return res;
}
7、 找出连续的最长递增子序列
- count 为当前元素峰值,ans为最大峰值
- 初始化 count = 1
- 从 0 位置开始遍历,遍历时根据前后元素状态判断是否递增,递增则 count++,递减则 count=1
- 如果 count>ans,则更新 ans
- 直到循环结束
(1)找出连续的最长递增子序列的元素个数
var findLengthOfLCIS = function(nums) {
//1. 数组长度<=1
if(nums.length<=1){
return nums.length;
}
let res = 0;
let count = 1;
//2. 遍历数组。下一个元素递增,则count+1;否则count=1,重新计数
for(let i=0;i<nums.length-1;i++){
if(nums[i]<nums[i+1]){
count++;
}else{
count=1;
}
res = Math.max(count,res);
}
return res;
};
(2)找出连续的最长递增子序列:添加一个开始和结束下标
var findLengthOfLCIS = function(nums) {
//1. 数组长度<=1
if(nums.length<=1){
return nums.length;
}
let res = 0;
let resArr = []; //记录最长连续序列
let count = 1;
let start = 0; //当前连续序列的开始下标
let end = 0; //当前连续序列的结束下标
//2. 遍历数组。下一个元素递增,则count+1;否则count=1,重新计数
for(let i=0;i<nums.length-1;i++){
if(nums[i]<nums[i+1]){ //2.1 连续
count++;
end++; //连续序列的结束下标+1
}else{//2.2 不连续。重置当前连续序列的开始和结束下标
start = i;
end = i;
count=1;
}
if(count>res){ //3. 更新当前最长的连续序列
res = count;
resArr = nums.slice(start,end+1);
}
}
return resArr;
};
8、找出不连续的最长递增子序列(思路)
思路:
一共需要两个辅助数组和一个辅助变量:
dp数组:用来存储位置i对应的最长子序列的长度
end数组:有多个长度为i的子序列,存储最后一个元素最小的子序列,有更大的机会来扩展递增子序列的元素
len:用来记录当前找到的最长子序列的长度
举个例子
[3,2,5,8,6,7]
end数组:
i=0: 3 长度为1的子序列为:3
end=[3]
i=1:2<3 长度为1的子序列为:2(用2替换3,因为这是是最容易使子序列扩展为长度为2的子序列的值)
end=[2]
i=2:5>2 所以长度为1的子序列可以扩展成一个长度为2的子序列
长度为1的子序列为:2
长度为2的子序列为:2 5
end=[2 5]
i=3:8>5 所以长度为2的子序列可以扩展成一个长度为3的子序列
长度为1的子序列为:2
长度为2的子序列为:2 5
长度为3的子序列为:2 5 8
end=[2 5 8]
i=4:6>5&&6<8 所以长度为2的子序列可以扩展成一个长度为3的子序列
长度为1的子序列为:2
长度为2的子序列为:2 5
长度为3的子序列为:2 5 6(用6替换8,因为只是最容易使子序列扩展为长度为4的子序列的值)
end=[2 5 6]
i=5:7>6 所以长度为3的子序列可以扩展成一个长度为4的子序列
长度为1的子序列为:2
长度为2的子序列为:2 5
长度为3的子序列为:2 5 6
长度为4的子序列为:2 5 6 7
end=[2 5 6 7]
笔记:我们用end[i]来存储长度为i+1的子序列的最后一个元素的最小值,因为这是最有希望使子序列扩展的值
当a[x]>end[i]时,那么长度为i+1的子序列必定可以扩展成一个i+2的子序列,如果(old=end[i+1])>a[x],那么就令
end[i+1]=a[x],他的含义是只要a[y]>a[x]即使a[y]<old,那么长度为i+2的子序列也必然可以扩展为一个长度为i+3的子序列。
所有最终结束时end数组为[2,5,6,7]即end[i]表示长度为(i+1)的子序列的最后一个元素值。
遍历结束后也会生成 dp数组:
arr:[3,2,5,8,6,7]
dp :[1 1 2 3 3 4]
len=4
dp[i]表示到arr[i]为止最长的递增子序列,得到字典序最小的元素只需要倒过来遍历dp,依次输出最先遇到的长度为len,len-1,len-2....1的arr元素即可。
res用来存储字典序最小的最长子序列。
即: len=4 dp[5]==4 ==>res[3]=arr[5]=7
len=3 dp[4]==3 ==>res[3]=arr[4]=6
dp[3]==3 pass因为需要的是字典序最小的
len=2 dp[2]==2 ==>res[3]=arr[2]=5
len=1 dp[1]==1 ==>res[3]=arr[1]=2
dp[0]==1 pass因为需要的是字典序最小的
结果:res=[2,5,6,7]
笔记:设dp[x]==dp[y]&&x<y那么a[x]必定大于a[y],因为如果a[x]<a[y],那么dp[x]对应的子序列必定可以扩展为长度dp[x]+1的子序列即dp[y]=dp[x]+1>dp[x],矛盾,所以a[y]<a[x]
9、链表反转,分别用遍历与递归实现
(1)遍历
function ReverseList(pHead){
if(pHead==null || pHead.next==null){
return pHead;
}
let h = null;
let p = null
while(pHead){
p = pHead.next;
pHead.next = h;
h = pHead;
pHead = p;
}
return h;
}
(2) 递归
终止条件:链表为空,或者 链表只有一个节点时,直接返回。
if (head == null || head.next == null)
return head;
思路:
1. 若链表为空,或者 链表只有一个节点时,直接返回
2. 翻转当前head节点后面的节点,即递归翻转head.next
3. 再将head节点连接到 翻转后的head.next的尾部
function ReverseList(pHead){
if(pHead==null || pHead.next==null){
return pHead;
}
// reverse指向翻转后的第一个节点
let reverse = ReverseList(pHead.next);
// pHead.next指向翻转后的最后一个节点
pHead.next.next = pHead;
pHead.next = null;
return reverse;
}
10、链表求和 (只能使用递归,不准提前反转链表,也不借用其他的数据结构如栈)
var addTwoNumbers = function(l1, l2) {
// 存储相加结果的链表
let head = new ListNode(0);
getRes(head, l1, l2, 0)
return head.next;
};
/*
head: 结果链表;l1、l2链表相加;carry:进位
* */
function getRes(head, l1, l2, carry){
//1. 递归退出条件
if(l1==null && l2==null && carry===0){
return;
}
//2. 计算当前节点相加的和
let sum = (l1!=null ? l1.val:0)+(l2!=null ? l2.val:0)+carry;
head.next = new ListNode(sum%10);
carry = Math.floor(sum/10);
//3. 递归计算下一节点的值
getRes(head.next, l1!=null ? l1.next:null, l2!=null?l2.next:null,carry);
}
11、是否是回文链表
(1)使用栈。时间复杂度O(n),空间复杂度O(n)
var isPalindrome = function(head) {
//1. 先将节点压入栈中
let sta = [];
while(head){
sta.push(head.val);
head = head.next;
}
//2. 分别从栈头和栈尾开始比较
let start = 0, end = sta.length-1;
while(start<end){
if(sta[start]!== sta[end]){
return false;
}
start++;
end--;
}
return true;
};
(2)使用快慢指针。时间复杂度O(n), 空间复杂度O(1)
public boolean isPalindrome(ListNode head) {
if(head == null || head.next == null) {
return true;
}
ListNode slow = head, fast = head;
ListNode pre = head, prepre = null;
//1. 寻找中点,同时反转前半部分的链表
while(fast != null && fast.next != null) {
pre = slow;
slow = slow.next;
fast = fast.next.next;
pre.next = prepre; //头插法反转链表
prepre = pre;
}
//2. 元素个数为奇数时,使slow指向后半部分的第一个节点
if(fast != null) {
slow = slow.next;
}
//3.判断回文子串。pre指向前半部分第一个节点,slow指向后半部分的第一个节点
while(pre != null && slow != null) {
if(pre.val != slow.val) {
return false;
}
pre = pre.next;
slow = slow.next;
}
return true;
}
12、找数组中重复数字
(1) 使用set。 空间复杂度O(n)
var findRepeatNumber = function(nums) {
let set = new Set();
for(let i=0;i<nums.length;i++){
if(set.has(nums[i])){return nums[i];}
set.add(nums[i]);
}
};
(2)原地交换 。空间复杂度O(1)
var findRepeatNumber = function(nums) {
let i = 0;
while(i<nums.length){
//1. 下标i 与其存储的值相等
if(nums[i] === i){
i++;
continue;
}
//2. 出现重复的值。下标nums[i] 和 i处存储的元素相等(nums[i]和i不相等)
if(nums[nums[i]] === nums[i]){
return nums[i];
}
//3. 未出现重复值,将当前元素交换到与其值对应的下标处
let temp = nums[i];
nums[i] = nums[temp];
nums[temp] = temp; // 相当于 nums[nums[i]] = nums[i]. 即nums[a] = a,将元素存到对应下标的位置
}
return -1;
};
13、根据前序、中序,重建二叉树
var buildTree = function(preorder, inorder) {
// 根据前序,可确定根节点。由根节点,将前序和中序序列分为左右两部分,递归创建二叉树
let n = preorder.length;
return myTree(preorder,0,n-1,inorder,0,n-1);
};
// 参数:前序序列,当前的开始结束下标;中序序列,当前的开始结束下标
function myTree(preorder,l1,h1,inorder,l2,h2){
if(l1>h1){
return null;
}
//1. 确定根节点
let rootVal = preorder[l1];
let root = new TreeNode(rootVal);
//2. 寻找根节点在中序中的位置
let mid = 0;
for(let i=l2; i<=h2; i++){
if(inorder[i] === rootVal){
mid = i;
break;
}
}
//3. 递归的创建根节点的左右孩子
let leftNum = mid-l2; //左节点数目
let rightNum = h2-mid; //右结点数目
//3.1 左孩子
root.left = myTree(preorder,l1+1,l1+leftNum,inorder,l2,mid-1);
//3.2 右孩子
root.right = myTree(preorder,h1-rightNum+1,h1,inorder,mid+1,h2);
return root;
}
14、二叉树的右视图
力扣:https://leetcode-cn.com/problems/binary-tree-right-side-view/
思路:层次遍历,记录每层的最后一个元素。时间和空间复杂度均为O(n)
var rightSideView = function(root) {
if(!root){
return [];
}
let res = [];
let que = [root];
while(que.length){
let len = que.length;
for(let i=0; i<len; i++){
let node = que.shift();
if(node.left){que.push(node.left);}
if(node.right){que.push(node.right);}
if(i==len-1) res.push(node.val);
}
}
return res;
};
15、二分查找
var search = function(nums, target) {
if(nums.length < 1) return -1;
let l = 0, h=nums.length-1;
while(l<=h){ //要写<=
let mid = Math.floor((l+h)/2);
if(nums[mid]===target){
return mid;
}else if(nums[mid] < target){
l = mid+1;
}else{
h = mid-1;
}
}
return -1;
};
16、求二叉树的深度,不使用递归
(1) 递归
var maxDepth = function(root) {
if(!root) return 0;
return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
};
(2) 非递归。使用层次遍历,每遍历一层,计数器+1。
var maxDepth = function(root) {
if(!root) return 0;
let que = [root];
let res = 0;
while(que.length){
let len = que.length;
for(let i=0; i<len; i++){
let node = que.shift();
if(node.left) {que.push(node.left)}
if(node.right) {que.push(node.right)}
}
res++;
}
return res;
};
17、两个栈实现队列
var s1 = [];
var s2 = [];
//1. 实现入队num
function push(num){
s1.push(num);
}
//2. 实现出队
function pop(){
if(!s2.length){
while(s1.length){
s2.push(s1.pop());
}
}
return s2.pop();
}
18、全排列
const permute = (nums) => {
const res = []; //结果集
const used = {}; //访问标记数组
function dfs(path) {
if (path.length == nums.length) { // 个数选够了
res.push(path.slice()); // 拷贝一份path,加入解集res
return; // 结束当前递归分支
}
for (const num of nums) { // for枚举出每个可选的选项
// if (path.includes(num)) continue; // 别这么写!查找的时间是O(n),增加时间复杂度
if (used[num]) continue; // 使用过的,跳过
path.push(num); // 选择当前的数,加入path
used[num] = true; // 记录一下 使用了
dfs(path); // 基于选了当前的数,递归
path.pop(); // 上一句的递归结束,回溯,将最后选的数pop出来
used[num] = false; // 撤销这个记录
}
}
dfs([]); // 递归的入口,空path传进去
return res;
};
19、排序时间复杂度
除了堆排序、二路归并排序的时间复杂度为O(nlog2n),
其余都为O(n^2),如:插入、冒泡、选择、快速。
20、螺旋矩阵
function spiralOrder( matrix ) {
let res = [];
if(matrix.length ==0 ){
return res;
}
let top=0, bottom=matrix.length-1, left=0, right=matrix[0].length-1;
while(top < Math.floor((matrix.length+1)/2) && left < Math.floor((matrix[0].length+1)/2))
{ //1. 上面:左-》右
for(let i=left; i<=right; i++){
res.push(matrix[top][i]);
}
//2. 右面:上-》下
for(let i=top+1; i<=bottom; i++){
res.push(matrix[i][right]);
}
//3. 下面:右-》左
for(let i=right-1; i>=left && top!=bottom; i--){
res.push(matrix[bottom][i]);
}
//4. 左面:下-》上
for(let i=bottom-1; i>=top+1 && left!=right; i--){
res.push(matrix[i][left]);
}
++top;
++left;
--bottom;
--right;
}
return res;
}
用链表实现一个队列
合并两个有序链表,不能使用循环
反转一个字符串中的单词顺序,中间的空格如果有多个则合并为一个(filter
最长的有效括号(难)
滑动窗口最大值(难)
实现sqrt函数,结果保留两位小数
给一个有序整数数组A[0...n]
,和一个整数target
.返回A中与target
的差的绝对值最小的元素的下标,若存在多个则全都要输出.
给一个数组,找出数组中任意三个数的最大乘积
ES6一般常用考点是 Promise
, let/const
, ()=>
, async/await
栈和队列的区别,在操作系统的应用?
vue中v-for的key值是做什么的,仅仅使用数组下标作为key值有什么缺点
0.1+0.2 !== 0.3问题,为什么,怎么解决
1、二维数组回行打印
3、给你一个二叉树,从上往下看,然后左往右顺序输出你能看到节点,同一个竖直方向上上面的节点把下面的节点遮挡住了
4、链表反转,分别用遍历与递归实现
6、是否是回文链表
7、错位的全排列(第一位不能是1,第二位不能是2)
2. 其它
1. 手写Promise.all()
全部成功才会成功;只有一个失败,就失败。
核心思路:
- Promise.myAll()返回的肯定是一个promise对象,所以可以直接return new Promise((resolve, reject) => {});
- 然后遍历传入的Promise数组,用Promise.resolve()将参数"包一层",使其变成一个promise对象;
- 如果当前的Promsie[i]成功,则将其成功结果存入结果数组res,计数器+1(计数器记录当前成功的Promise数量);
- 通过比较计数器count和Promise总数,判断当前所有的Promise是否全部成功;
- 若全部成功,则resolve;遇到一个不成功,则reject;
- 一些细节:
- 官方规定Promise.all()接受的参数是一个可遍历的参数,所以未必一定是一个数组,所以用Array.from()转化一下
- 使用for…of进行遍历,因为凡是可遍历的变量应该都是部署了iterator方法,所以用for…of遍历最安全
// 一、手写promise.all方法。 参数promiseArr: Promise数组
Promise.myAll = function (promiseArr) {
//1. 用于计数,表示已成功的Promise对象个数。若等于len,则成功,就resolve
let count = 0;
//2. Promise数组的长度
let len = promiseArr.length;
let res = []; //存放成功结果
//3. Promise.myAll()返回的是一个promise对象,故直接return Promise
return new Promise((resolve, reject) => {
//4. 遍历Promise数组
for (let i in promiseArr) {//i为索引
Promise.resolve(promiseArr[i]).then( //使其变成一个promise对象
//5. 如果 promiseArr[i] 成功
result => {
res[i] = result;
//6. Promise数组中全部成功,返回所有元素的计算结果
if (++count === len) {
resolve(res);
}
}).catch(err => { //7. 如果 promiseArr[i] 失败,则直接reject
reject(err);
})
}
})
}
//二、测试
var promise1 = Promise.resolve(1);
var promise2 = new Promise((resolve, reject) => {
resolve('hello');
});
var promise3 = 42;
// 打印结果:[1, "hello", 42]
Promise.myAll([promise1, promise2, promise3]).then(result => { console.log(result) })
2、手写Promise.race()
const p = Promise.race([p1, p2, p3]);
只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。
核心思路:
谁先决议那么就返回谁,所以将all的计数器和逻辑判断全部去除掉就可以了。
// 一、手写promise.race方法。 参数promiseArr: Promise数组
Promise.myRace = function(promiseArr){
return new Promise((resolve, reject) => {
//1. 遍历promiseArr数组
for(let i in promiseArr){
Promise.resolve(promiseArr[i]).then( // 使其变成一个promise对象
result => { //2. 成功
resolve(result);
}).catch( err => { //3. 失败
reject(err);
})
}
})
}
//二、测试
var promise1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, 'one'); // 500ms后,resolve('one')
});
var promise2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'two'); // 100ms后,resolve('two')
});
// 打印结果:two
Promise.myRace([promise1, promise2]).then(result => { console.log(result) })
3、用非递归的方式实现数组扁平化:some+展开运算符
function flatten(arr) {
while(arr.some( item => Array.isArray(item))){ //数组arr中有一项是数组
arr = [].concat(...arr);
}
return arr;
}
4、用递归的方式实现数组扁平化
function flatten(arr) {
let res = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) { //1.是数组,递归
res = res.concat(flatten(arr[i]));
} else { //2. 不是数组,直接存入结果集
res.push(arr[i]);
}
}
return res;
}
5、⭐解析url中的字符串a=1&b=2&b=3&c=1....,转化为对象
问题:解析URL:"a=1&b=2&b=3&c=4&d=&d=&e=%E4%BD%A0%E5%A5%BD&b=8"
解析结果:
a: "1"
b: ["2", "3", "8"]
c: "4"
d: ""
e: "你好"
解决:用decodeURIComponent方法进行解析
function getUrlObject(url) { //url:"a=1&b=2&b=3&c=1...."
let res = {}; //存放解析url后对应的对象
let items = url.length ? url.split("&") : []; //按&将字符串拆分为数组
let item=null, name=null, value=null;
for(let i=0; i<items.length; i++){ //遍历字符数组
item = items[i].split("="); //将每一项拆分。例:a=1拆分为[a,1]
if(item[0].length){//键不为空
name = decodeURIComponent(item[0]); //键。解析item[0]
value = decodeURIComponent(item[1]); //值。解析item[1]
if(res[name]){ //该键出现多次,将该键对应的值合并为数组
res[name] = [...res[name]].concat(value);
}else{ //该键第一次出现
res[name] = value;
}
}
}
return res;
}
console.log(getUrlObject("a=1&b=2&b=3&c=4&d=&d=&e=%E4%BD%A0%E5%A5%BD&b=8"));
/*
解析结果:
a: "1"
b: ["2", "3", "8"]
c: "4"
d: ""
e: "你好"
*/
6、用js手动实现一个new操作符
首先要知道 new 操作符都做了什么事,即构造函数的内部原理:
- 创建一个新对象;
- 链接到原型(将构造函数的 prototype 赋值给新对象的 __proto__);
- 绑定this(构造函数中的this指向新对象并且调用构造函数);
- 返回新对象;
这样我们就可以手动实现一个 new 方法了:
//一、手动实现new
function myNew(){
//1. 创建一个新对象
let obj = {};
//2. 获取构造函数。即arguments的第一项
let Con = [].shift.call(arguments);
//3.将新对象链接到原型链
obj.__proto__ = Con.prototype;
//4.绑定构造函数的this,使this执行obj对象。并传参,为obj的属性赋值
let res = Con.apply(obj,arguments);
//5. 确保最后得到的是一个对象
return typeof res==="object" ? res : obj;
}
测试:
//二、测试
function Person (name,age){
this.name = name;
this.age = age;
this.say = function () {
console.log("I am " + this.name)
}
}
//通过new创建构造实例
let person1 = new Person("Curry",18);
console.log(person1.name); //"Curry"
console.log(person1.age); //18
person1.say(); //"I am Curry'
//通过myNew()方法创造实例
let person2 = myNew (Person,"Curry",18);
console.log(person2.name); //"Curry"
console.log(person2.age); //18
person2.say(); //"I am Curry'
详细解释:
我们实现的 myNew() 方法需要传入的参数是:构造函数 + 属性
1、首先我们创建一个新对象;
2、然后通过 arguments 类数组我们可以知道参数中包含了构造函数以及我们调用myNew时传入的其他参数;
3、然后得到 构造函数和其他的参数,由于arguments是类数组,没有直接的方法可以供其使用,我们可以有以下两种方法获取构造函数:
- Array.from(arguments).shift(); 转换成数组 使用数组的方法 shift 将第一项弹出
- [].shift().call(arguments) ; 通过 call() 让arguments能够借用shift()方法
4、绑定this的时候需要注意:
- 给构造函数传入属性,注意构造函数的this属性
- 参数传进 Con 对 obj 的属性赋值,this要指向 obj 对象
- 在 Con 内部手动指定函数执行时的this ,使用call、apply实现
5、最后我们需要返回一个对象
7、用setTimeout实现一个setInterval
(1)setTimeout和setInterval的区别:
- setInterval:每隔1s打印一次,执行多次。
const timer1 = setInterval(() => {
console.log(1)
}, 1000)
- setTimeout:2s之后打印,只执行依次。
const timer1 = setTimeout(() => {
console.log(2)
}, 2000)
(2)用setTimeout实现一个setInterval(用递归)
//1. 用setTimeout实现一个setInterval
function mysetInterval() {
let timer1 = setTimeout(() => {
console.log(2);
mysetInterval(); //通过递归,重复调用
}, 1000)
}
//调用。每隔2s,打印一次
mysetInterval();
8、手写原生call
- 先判断this是不是函数
- 将函数设为对象的属性;
- 指定this到函数,并传入给定参数执行函数;
- 执行之后删除这个函数;
//一、手写原生call方法
Function.prototype.mycall = function (obj, ...args) {
// 1. 判断this是否函数,否返回typeerror
if (typeof this !== 'function') {
throw new TypeError('不是函数');
}
//2. 绑定this。(obj.fn为要执行的函数,例person)
obj.fn = this;
//3. 执行函数,获取结果。
const res = obj.fn(...args);
//4. 获取到结果后,删除函数
delete obj.fn;
//5. 返回结果
return res;
}
// 二、测试
function person() {
console.log(this.name); //打印this的name属性值,即mycall绑定的this
if (arguments.length < 1) return;
for (var i = 0; i < arguments.length; i++) {
console.log(arguments[i])//打印参数
}
}
var p = { name: "小明" };
//通过mycall方法,执行person方法,绑定其this为p,并传参
person.mycall(p, '我', '爱', '中国');
9、手写原生apply
10、手写原生bind
手写原型链继承、以及其它的继承方式
10、手写防抖节流实现
标签:function,return,面试,res,length,辅导,算法,let,Promise 来源: https://blog.csdn.net/sunnnnh/article/details/118033707