1、猿辅导前端面试算法题
2021/6/19 22:57:03
本文主要是介绍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、手写防抖节流实现
这篇关于1、猿辅导前端面试算法题的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-05-30毕设私活神器
- 2024-05-30html
- 2024-05-09一定要避坑:关于微信H5分享,温馨提示你不要再踩坑了!!!
- 2024-05-09本地项目放到公网访问!炒鸡煎蛋!
- 2024-04-07金融企业区域集中库的设计构想和测试验证
- 2024-03-11前端CSS的工程化——掌握Sass这四大特性就够了
- 2024-02-21h5关联css样式(html怎么和css关联)-icode9专业技术文章分享
- 2024-02-07Firefox禁止远程字体加速网页加载及图标字体补充安装
- 2024-02-07一个菜鸡前端的3年总结-「2023」
- 2024-01-18最火前端Web组态软件(可视化)