【前端面试算法题+场景题合集(持续更新中)】

以下这些面试题都是本人面试过程中遇到过的,作记录和分享用。

首先对于常见的手写题,见下方链接:

一篇搞定前端高频手撕算法题(36道)

接下来是面试当中遇到的一些手写题,其中promise相关的部分和CSS手写部分需要着重看看。

1.数组去重

数组去重的7种方法

利用map,时间复杂度为O(n):

function removeDuplicate(arr) {
  const map = new Map()
  const newArr = []

  arr.forEach(item => {
    if (!map.has(item)) { // has()用于判断map是否包为item的属性值
      map.set(item, true) // 使用set()将item设置到map中,并设置其属性值为true
      newArr.push(item)
    }
  })

  return newArr
}

const result = removeDuplicate(arr)
console.log(result) // [ 1, 2, 'abc', true, false, undefined, NaN ]

2.版本号排序

题目: 输入一组版本号,输出从大到小的排序
输入: [‘2.1.0.1’, ‘0.402.1’, ‘10.2.1’, ‘5.1.2’, ‘1.0.4.5’]
输出: [‘10.2.1’, ‘5.1.2’, ‘2.1.0.1’, ‘1.0.4.5’, ‘0.402.1’]

思路:先用split分割,遍历判断arr[i],有三种情况:
(1)其中一个arr[i]undefined说明前面判断的都相同,则直接判断arr.length
(2)arr[i]相同则continue
(3)不同则直接return arr2[i]-arr1[i] 不用转成number

function versionSort(arr) {
  return arr.sort((a, b) => {
    let i = 0;
    const arr1 = a.split(".");
    const arr2 = b.split(".");
    while (true) {
      // 取出相同位置的数字
      const s1 = arr1[i];
      const s2 = arr2[i];
      i++;
      // 若s1 或 s2 不存在,说明相同的位置已比较完成,接下来比较arr1 与 arr2的长度,长的版本号大
      if (s1 === undefined || s2 === undefined) {
        return arr2.length - arr1.length;
      }
      if (s1 === s2) continue;
      // 比较相同位置的数字大小
      return s2 - s1;
    }
  });
}

关键在于要对几个情况非常熟悉,手撕的时候快速准确的写出来,不要卡壳TT


3.map记数类型:

使用对象: 涉及到需要计算数组或字符串中某个元素的数量时。
步骤: 一般定义一个map,遍历数组或字符串,利用counter[s[i]] ? counter[s[i]]++ : (counter[s[i]] = 1); 计数

3.1存在重复元素

题目描述如下:

给定一个整数数组,判断是否存在重复元素。

如果存在一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false 。
思路: 用map。遍历数组,遇到数组元素判断是否map.has(arr[i]),是就return true,否则就map.set(arr[i])

function judge(num){
    let map=new Map();
    for(let i of num){
        if(map.has(i)){
            return true;
        }else{
            map.set(i,1);
        }
    }
    return false;
}

3.2:字符串第一个唯一字符

题目:给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
思路:用map。 定义一个对象。 遍历字符串,元素出现一次就+1 map[s] = (map[s] || 0) + 1;.最后再次遍历字符串,找到第一次map[str[i]] === 1

function firstUniqChar(str) {
  let map = {};
  for (let s of str) {
    map[s] = (map[s] || 0) + 1;
  }
  for (let i = 0; i < str.length; i++) {
    if (map[str[i]] === 1) return i;
  }
  return -1;
}
console.log(firstUniqChar("loveleetcode"));//输出2

3.3:有效的字母异位词

题目:给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。
思路:定义计数对象count={},遍历字符串,对于每个s[i],obj[s[i]]++,对于每个t[i],obj[t[i]]--
最后判断obj里的每个元素值是否为0, return Object.values(obj).every(v=>v===0);

function isAnagram(s, t) {
  let counter = {};
  let sl = s.length;
  let tl = t.length;
  if (sl !== tl) return false;
  for (let i = 0; i < sl; i++) {
    counter[s[i]] ? counter[s[i]]++ : (counter[s[i]] = 1);
    counter[t[i]] ? counter[t[i]]-- : (counter[t[i]] = -1);
  }
  return Object.values(counter).every((v) => v === 0);
}
let s = "rat",
  t = "tar";
console.log(isAnagram(s, t));//true

3.4:多数元素

题目:给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
思路: 和前面几题类似,定义一个计数对象count={},遍历数组,记录出现次数,最后遍历count,找到次数 大于 ⌊ n/2 ⌋ 的元素。

function multipul(arr) {
  const count = {};
  let n = arr.length / 2;
  for (let i = 0; i < arr.length; i++) {
    count[arr[i]] ? count[arr[i]]++ : (count[arr[i]] = 1);
    if (count[arr[i]] > n) return arr[i];
  }
}
console.log(multipul([2, 2, 1, 1, 1, 2, 2]));//输出2

4.map存储:

4.1:两数之和

题目:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。
思路:用map。 每遍历一个元素,看看map 中是否存在target-nums[i],存在则返回[map.get(element),i],不存在则加入当前元素到map

var twoSum = function(nums, target) {
    let map=new Map();
    for(let i=0;i<nums.length;i++){
        let element=target-nums[i];
        if(map.has(element)){
            return [map.get(element),i];
        }else{
            map.set(nums[i],i)
        }
    }
    return []
};

4.2:两数组交集

题目: 给定两个数组,编写一个函数来计算它们的交集。(交集里的元素互不相同)
思路:定义一个对象map={}。遍历arr1,设置map[arr1[i]]=true。遍历arr2,对每个map[arr2[i]]==true的元素记录,并设置当前map[arr2[i]]=false避免重复。

function intersection(arr1, arr2) {
  let map = {};
  let ret = [];
  for (let i = 0; i < arr1.length; i++) {
    map[arr1[i]] = true;
  }
  for (let i = 0; i < arr2.length; i++) {
    if (map[arr2[i]]) {
      ret.push(arr2[i]);
      map[arr2[i]] = false;
    }
  }
  return ret;
}

用filter:

function intersection(arr1, arr2) {
  const duplicates = arr1.filter(item => arr2.includes(item));
  return duplicates;
}

5.递归

适用对象: 一个大问题很复杂,但是可以拆分为很多个小问题,只要小问题
都解决了,那么大问题就解决了。搞个数组(一维、二维),从最小的问题一直
计算到最大的问题。
而递归是大问题到小问题且不需要记录过程值。
一般解题步骤:
第一步骤:定义数组元素的含义
第二步骤:找出数组元素之间的关系式 : 利用 dp[n-1],dp[n-2]……dp[1],
来推出 dp[n] ,一般找dp[i-1],dp[i-2]与dp[i]的关系
第三步骤:找出初始值 :dp[0]、dp[1]、dp[2]等
总结一下就是:写出递归公式,找到终止条件

递归算法优点:代码表达力强,写起来很简洁。
递归算法缺点:递归算法有 堆栈溢出(爆栈) 的风险、存在重复计算,过多的函数调用会耗时较多等问题(写递归算法的时候一定要考虑这几个缺点)、归时函数的变量的存储需要额外的栈空间,当递归深度很深时,需要额外的内存占空间就会很多,所以递归有 非常高的空间复杂度。

堆栈溢出是指在程序执行过程中,递归调用或函数调用层级过深导致系统栈空间不足,从而导致程序崩溃的情况。
以下是堆栈溢出可能对浏览器产生的影响:

  • 页面崩溃或冻结
  • 性能下降
  • 用户体验问题

5.1 爬楼梯

题目:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢。
思路:
(1)定义数组let dp=new Array(n),dp[n]代表爬到第n个台阶的方法数
(2)要走到第n步,前一步可能走1级到达这,可能2级,故dp[n]=dp[n-1]+dp[n-2]
(3)dp[0]=0,dp[1]=1,dp[2]=2

var climbStairs = function(n) {
    let dp = new Array(n);
    dp[0] = 0;
    dp[1] = 1;
    dp[2] = 2;
    for(let i = 3; i <= n; i++){
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n]
};

时间复杂度:O(n)

5.2数组拍平

当面试官问我这道题的时候,我当时脑子一抽,告诉他我的第一想法是把数组字符串化,然后判断有多少个’[',说完我俩都笑了…
基础的递归题,奈何我递归一直学得不好啊TT
问题1:计算嵌套层级
思路: 遍历数组,判断每个arr[i] instanceof Array否,是则ans++且递归传入arr[i]。注意ans是定义在函数外的。

var ans = 1;
function multiarr(arr) {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] instanceof Array) {
      ans++;
      let subarr = arr[i];
      multiarr(subarr);
    }
  }
  return ans;
}
console.log(multiarr([1, 2, [3, [4, [5]]]]));//输出4

问题2:将数组拍平
思路:遍历数组元素,如果是嵌套的则递归调用result = result.concat(flat(arr[i]));,否则直接 result.push(arr[i]);

function flat(arr) {
  let result = [];
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] instanceof Array) {
      result = result.concat(flat(arr[i]));
    } else {
      result.push(arr[i]);
    }
  }
  return result;
}
console.log(flat([1, [2, [3, [4]]], 5]));//[1,2,3,4,5]

使用stack实现的:

// 使用堆栈stack
Array.prototype.flat5 = function(deep = 1) {
    const stack = [...this];
    const result = [];
    while (stack.length > 0) {
        const next = stack.pop();
        if (Array.isArray(next)) {
            stack.push(...next);
        } else {
            result.push(next);
        }
    }
    // 反转恢复原来顺序
    return result.reverse();
}

5.3 对象格式化

问题:对象格式化这个问题,这种一般是后台返回给前端的数据,有时候需要格式化一下大小写等,一般层级不会太深。
思路:定一个新的formattedObj,如果元素不是obj则return。遍历obj的key,如果满足Object.hasOwnProperty.call(obj, key)则都转成小写,然后赋值 formattedObj[formattedKey] = formatObject(obj[key]);

function formatObject(obj) {
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }
  
    const formattedObj = {};
    for (let key in obj) {
        if (Object.hasOwnProperty.call(obj, key)) {
            const formattedKey = key.toLowerCase();
            formattedObj[formattedKey] = formatObject(obj[key]);
        }
    }
  
    return formattedObj;
}

// 测试例子
let obj = {
    a: '1',
    b: {
        c: '2',
        D: {
            E: '3'
        }
    }
};

大佬写的:

// 格式化对象 大写变为小写
let obj = {
    a: '1',
    b: {
        c: '2',
        D: {
            E: '3'
        }
    }
}
function keysLower(obj){
    let reg = new RegExp("([A-Z]+)", "g");
        for (let key in obj){
            if(Object.prototype.hasOwnProperty.call(obj,key)){
                let temp = obj[key];
                if(reg.test(key.toString())){
                    temp = obj[key.replace(reg,function(result){
                        return result.toLowerCase();
                    })]= obj[key];
                    delete obj[key];
                }
                if(Object.prototype.toString.call(temp)==='[object Object]'){
                    keysLower(temp);
                }
            }
        }
    return obj;
}
console.log(keysLower(obj));//输出结果 { a: '1', b: { c: '2', d: { e: '3' } } }

5.4 深拷贝

function cloneDeep2(source) {
    // 如果输入的为基本类型,直接返回
    if (!(typeof source === 'object' && source !== null)) {
        return source;
    }

    // 判断输入的为数组函数对象,进行相应的构建
    const target = Array.isArray(source) ? [] : {};

    for (let key in source) {
        // 判断是否是自身属性
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (typeof source === 'object' && source !== null) {
                target[key] = cloneDeep2(source[key]);
            } else {
                target[key] = source[key];
            }
        }
    }

    return target;
}

为了防止因循环引用而出现的爆栈(死循环),使用weakMap,在处理引用类型时,先判断该对象是否已经被处理过,如果是则直接返回已经处理过的对象即可:

function clone(target, map = new WeakMap()) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        if (map.get(target)) {
            return map.get(target);
        }
        map.set(target, cloneTarget);
        for (const key in target) {
            cloneTarget[key] = clone(target[key], map);
        }
        return cloneTarget;
    } else {
        return target;
    }
};

为什么要用 weakMap 而不直接用 Map 进行存储呢?

WeakMap 对象虽然也是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。
在计算机程序设计中,弱引用与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。 一个对象若只被 弱引用 所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收。
内存管理上:当一个键不再被引用时,会自动从 WeakMap 中删除,从而避免内存泄漏的发生。
垃圾回收上:WeakMap 中的对象如果不再被引用,会自动被垃圾回收器回收,从而释放内存。
安全性上:由于弱引用的特性,WeakMap 不能遍历和获取其键值对的数量,并且没有提供迭代方法(比如 keys()、values()、entries())。这样可以保护键的隐私,防止外部访问和修改。

在实现深拷贝时使用 WeakMap 的主要优点是它支持在对象键上使用弱引用,这样可以防止内存泄漏。而普通的 Map 是对键进行强引用,即使这个键没有被其他引用占用,Map 中仍然会持有它的引用,导致无法释放内存。

不过需要注意的是 WeakMap 的键必须是对象,而且是弱引用,没有其他数据类型的键,也不能直接遍历 WeakMap 中的键值对。

5.5 二分查找

function binarySearch(arr, target, low, high) {
  if (low > high) {
    return -1;
  }

  const mid = Math.floor((low + high) / 2);

  if (arr[mid] === target) {
    return mid;
  } else if (arr[mid] > target) {
    return binarySearch(arr, target, low, mid - 1);
  } else {
    return binarySearch(arr, target, mid + 1, high);
  }
}

// 示例用法:
const arr = [1, 3, 5, 7, 9];//数组必须是有序的
const target = 5;
const low = 0;
const high = arr.length - 1;
const index = binarySearch(arr, target, low, high);
console.log(index); // 输出:2

5.6多叉树查找

// 示例的多叉树对象,要求查找特定键对应的值
var tree = {
    value: 1,
    children: [
        {
            value: 2,
            children: [
                {
                    value: 5,
                    children: []
                },
                {
                    value: 6,
                    children: []
                }
            ]
        },
        {
            value: 3,
            children: [
                {
                    value: 7,
                    children: []
                }
            ]
        },
        {
            value: 4,
            children: [
                {
                    value: 8,
                    children: []
                },
                {
                    value: 9,
                    children: []
                }
            ]
        }
    ]
};

递归版:

// 递归函数来查找指定值
function findValue(node, target) {
    if (node.value === target) {
        return node;
    } else if (node.children && Array.isArray(node.children) && node.children.length > 0) {
        for (var i = 0; i < node.children.length; i++) {
            var result = findValue(node.children[i], target);
            if (result) {
                return result;
            }
        }
    }
    return null; // 如果未找到则返回null
}

非递归版,使用队列来实现广度优先搜索

function findValueNonRecursive(tree, target) {
    if (!tree) return null; // 树为空时返回null
    
    var queue = [tree]; // 创建一个队列,并将根节点入队
    
    while (queue.length > 0) {
        var currentNode = queue.shift(); // 出队队首节点
        
        if (currentNode.value === target) {
            return currentNode; // 找到目标值时返回节点
        }
        
        if (currentNode.children && currentNode.children.length > 0) {
            // 将当前节点的所有子节点入队
            for (var i = 0; i < currentNode.children.length; i++) {
                queue.push(currentNode.children[i]);
            }
        }
    }
    
    return null; // 未找到目标值时返回null
}

// 示例用法
var foundNodeNonRecursive = findValueNonRecursive(tree, 7);
if (foundNodeNonRecursive) {
    console.log("找到值为 7 的节点:", foundNodeNonRecursive);
} else {
    console.log("未找到值为 7 的节点。");
}

6.1 promise封装setTimeout,间隔打印1 1 1

function delayedLog(message, delay) {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(message);
      resolve();
    }, delay);
  });
}
function printOne() {
  delayedLog('1', 1000)
    .then(() => delayedLog('1', 1000))
    .then(() => delayedLog('1', 1000));
}
printOne();

async版:

function delayPrint(timeout) {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('1');
      resolve();
    }, timeout);
  });
}
async function printNumbers() {
  await delayPrint(1000);
  await delayPrint(1000);
  await delayPrint(1000);
}
printNumbers();

6.2 promsie实现每隔一秒输出1,2,3

定时器版:

  function timer() {
        for (let i = 1; i < 4; i++) {
            setTimeout(() => {
                console.log(`输出${i}`)
            }, 1000 * i)
        }
    }
    timer()

定时器+promise:

 async function timer() {
        for (let i = 1; i < 4; i++) {
            console.log('ww', await _promise(i))
            // setTimeout(() => {
            //     console.log(`输出${i}`)
            // }, 1000 * i)
        }
    }
    function _promise(interval) {
        return new Promise((resolve, reject) => {
            setTimeout(() => { resolve(interval) }, 1000)
        })
    }
    timer()

传递参数版,promsie不动,传递参数只在使用的地方传。类似于柯里化:

   async function timer(n) {
        for (let i = 1; i < n; i++) {
            console.log('ww', await _promise(i, i * 1000))
        }
    }
    function _promise(num, interval) {
        return new Promise((resolve, reject) => {
            setTimeout(() => { resolve(num) }, interval)
        })
    }
    timer(4)

.

6.3 多种方式实现Lazy Man

多种方式实现LazyMan


7.鸡兔同笼

function chickenRabbitCombination(legs) {
  const results = [];

  for (let chickens = 0; chickens <= legs / 2; chickens++) {
    const rabbits = (legs - 2 * chickens) / 2;
    if (Number.isInteger(rabbits) && rabbits >= 0) {
      results.push({ chickens, rabbits });
    }
  }

  return results;
}

const totalLegs = 10; // 例如,假设脚的总数为 10
const combinations = chickenRabbitCombination(totalLegs);

console.log(combinations);

6. 代码输出题

6.1 0.1 + 0.2

console.log(0.1 + 0.2)

在这里插入图片描述
原因:
在JS中,浮点数0.1和0.2首先转成64位二进制,转出的结果是无限循环的因此需要截断再相加,最后转成十进制。就变成0.30000000000000004,所以在计算时会产生误差。

算法:
0.1*2=0.2 ----------- 取出整数部分0
0.2*2=0.4 ----------- 取出整数部分0
0.4*2=0.8 ----------- 取出整数部分0
0.8*2=1.6 ----------- 取出整数部分1
0.6*2=1.2 ----------- 取出整数部分1

0.2*2=0.4 ----------- 取出整数部分0
0.4*2=0.8 ----------- 取出整数部分0
0.8*2=1.6 ----------- 取出整数部分1
0.6*2=1.2 ----------- 取出整数部分1

我们可以发现在无限循环
0.1的二进制结果 0.0 0011 0011 0011 .....  "0011的无限循环"
0.2的二进制结果 0.0011 0011 0011 0011 .... "0011的无限循环"52位进行计算
0.1  =  0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001
0.2 =   0.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011

计算的结果是: 0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 ...也是无限循环

将二进制转换为十进制:
浮点型数据存储转换为十进制的话 只会取前170.300000000000000044408920958 .... = 0.30000000000000004(四舍五入)
所以0.1+0.2的结果是等于0.30000000000000004

参考链接

解决:
1.将其先转换成整数,再相加之后转回小数。具体做法为先乘10相加后除以10

2.使用number对象的toFixed方法,toFixed方法可以指定运算结果的小数点后的指定位数的数字,使保留一位小数就是toFixed(1)。

//let x=(0.1+0.2).toFixed(1)
//因为使用toFixed方法将number类型转换成了字符串类型
//,所以使用parseFloat将字符串转回number类型
let x=parseFloat((0.1+0.2).toFixed(1));
console.log(x===0.3);

6.2 定时器settimeout()

当settimeout()的时间参数为0时

setTimeout(function(){
 console.log(1);
}, 0);
console.log(2);
console.log(3);
//输出: 2 3 1

JS 是单线程的,单线程就意味着所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。在JS中同时维护着一个任务队列,上面代码中当执行遇到setTimeout(fn,millisec)时,会把fn这个函数放在任务队列中,当JS引擎线程空闲时并达到millisec指定的时间时,才会把fn放到js引擎线程中执行。
setTimeout()只是将事件插入了"任务队列",必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。
例:

  1. 在for循环中使用 var 声明变量是全局变量,当for循环执行完毕,此时的 i 是5。再经过一秒钟后分别调用setTimeout函数,输出的 i 都是全局作用域中的 i ,即5。
for(var i=0; i<5; i++){
  setTimeout(() => {
    console.log(i);
  }, 1000);
}//输出5个5
  1. let 关键字使每一次for循环中都有一个独立作用域中的 i ,互不干扰,所以输出的都是各自作用域中的 i,而不是全局作用域中的 i
for(let i=0; i<5; i++){
  setTimeout(() => {
    console.log(i);
  }, 1000);
}  //输出 0 1 2 3 4
  1. 使用闭包解决
for(var i=0; i<5; i++){
  (function(j){
    setTimeout(() => {
      console.log(j);
    }, 1000)
  })(i)
}    //输出 0 1 2 3 4

每次循环都会传进一个i,如果没有setimeOut的话会直接输出,但settimeout把传入的这个i存起来,等循环结束再进行输出。对于for上的var,是全局变量,所以每次i改变就会把之前存起来的i改变了,但如果是let或者闭包,会把i保护起来,不会被改变


7.CSS手写题

7.1 原生JS实现弹窗和周围浅色,添加过度动画,实现弹窗从左边飘到右边

<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>原生的弹窗</title>
    <style>
        .modal {
        display:none;
        position:fixed;
        z-index:1;
        top:0;
        left:0;
        width:100%;
        height:100%;
        background:rgba(0,0,0,0.3)
      }
      .modal-content {
          background: #fefefe;
          margin: 15% auto; 
          padding: 20px;
          border: 1px solid #888;
          width: 80%; 
          height:20%;
          position:relative;
          display:flex;
          justify-content:center;
          align-items:center;
      }
      .close {
        position:absolute;
        top:0;
        right:10px;
        color:#aaa;
        font-size:28px;
        font-weight:bold;
      }
      .close:hover,
      .close:focus {
          color: black;
          text-decoration: none;
          cursor: pointer;
      }
      @keyframes slidein {/*动画 */
        from {
          margin-left: -80%; 
        }
        to {
          margin-left: 0;
        }
      }
      .modal-content.show {
        animation: slidein 0.5s;
      }
    </style>
</head>
<body>
  <button id="open">
    打开弹窗
  </button>
  <div id="myModal" class="modal">
    <div id="modalContent" class="modal-content">
    <span class="close">
        &times;
    </span>
    <p>弹窗中的文本...</p>
    </div>
  </div>
</body>
<script>
    // 获取节点
    var modal = document.getElementById('myModal');
    var btn = document.getElementById("open");
    var close = document.querySelector('.close');
    var modalContent = document.getElementById("modalContent");
    //创建点击事件
    btn.onclick = function() {
        modal.style.display = "block";
        modalContent.classList.add('show'); // 添加 .show 类名
    }
    close.onclick = function() {
        modal.style.display = "none";
        modalContent.classList.remove('show'); // 移除 .show 类名
    }
    // 在用户点击其他地方时(除content外的),关闭弹窗
    window.onclick = function(event) {
        if (event.target == modal) {
            modal.style.display = "none";
            modalContent.classList.remove('show'); // 移除 .show 类名
        }
    } 
</script>
</html>

7.2 CSS实现三角形

参考链接

1.border实现

原理

一开始是这样的:

#box{
  width:100px;
  height:100px;
  background:yellow;
  border-top: 20px solid red;
  border-right:20px solid black;
  border-bottom:20px solid green;
  border-left:20px solid blue;
}

在这里插入图片描述
当减小box的宽高时,会发生如下变化:
在这里插入图片描述

因此可以修改border值得到三角形:

#box{
  width:0px;
  height:0px;
  border-top: 20px solid red;
  border-right:20px solid transparent;
  border-bottom:20px solid transparent;
  border-left:20px solid transparent;
}

在这里插入图片描述

#box{
  width:0px;
  height:0px;
  border-top: 20px solid red;
  border-right:20px solid transparent;
  border-bottom:20px solid transparent;
  border-left:20px solid red;
}

在这里插入图片描述
其他的就不列举了,总之通过调整border的值为transparent就可以得到三角形。

2.linear-gradient

.triangle {
		width: 160px;
	  	height: 200px;
	  	outline: 2px solid skyblue;
	  	background-repeat: no-repeat;
		background-image: linear-gradient(32deg, orangered 50%, rgba(255, 255, 255, 0) 50%), linear-gradient(148deg, orangered 50%, rgba(255, 255, 255, 0) 50%);
		background-size: 100% 50%;
		background-position: top left, bottom left;
}

在这里插入图片描述
CSS 线性渐变可用于创建很多种形状。不过它是有缺点的,就是需要调试出合适的渐变角度。

3.clip-path

.triangle{
		margin: 100px;
		width: 160px;
  	    height: 200px;
  	    background-color: skyblue;
		clip-path: polygon(0 0, 0% 100%, 100% 50%);
}

在这里插入图片描述
clip-path 就是使用它来绘制多边形(或圆形、椭圆形等)并将其定位在元素内。实际上,浏览器不会绘制 clip-path 之外的任何区域,因此我们看到的是 clip-path 的边界。

使用 clip-path 可以为沿路径放置的每个点定义坐标。在这种情况下,就定义了三个点:top-left (0 0)、bottom-left (0% 100%)、right-center (100% 50%)。

它是最精简和最可具扩展性的。 不过目前其在浏览器兼容性不是很好,使用时要考虑浏览器是否支持。
在这里插入图片描述

7.3 CSS实现一个扇形

CSS实现任意角度扇形

7.4 CSS画圆、半圆、椭圆

CSS画圆、半圆、椭圆

7.5 随着输入变化的input,如何获取其内容宽度

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Get Input Width</title>
</head>
<body>

<input type="text" id="inputElement" oninput="getInputWidth()" placeholder="Type something...">

<script>
function getInputWidth() {
    var inputElement = document.getElementById('inputElement');
    var inputContent = inputElement.value; // 获取输入框的内容
    var inputWidth = getTextWidth(inputContent, window.getComputedStyle(inputElement).fontSize); // 获取内容宽度
    console.log("Input Width: " + inputWidth + "px");
}

function getTextWidth(text, font) {
    // 创建一个临时的span元素
    var span = document.createElement('span');
    // 设置span的字体样式
    span.style.font = font;
    // 设置span的文本内容
    span.textContent = text;
    // 将span添加到body中,这样可以获取其宽度
    document.body.appendChild(span);
    // 获取span的宽度
    var width = span.offsetWidth;
    // 移除临时span元素
    document.body.removeChild(span);
    return width;
}
</script>

</body>
</html>

定义了一个getInputWidth()函数,该函数通过调用getTextWidth()函数来获取输入框内容的宽度。getTextWidth()函数创建了一个临时的span元素,并设置了与输入框相同的字体样式和内容,然后获取该span元素的宽度,最后将该临时span元素移除。这样就可以得到输入框内容的宽度


8.函数实例方法、静态方法、原型方法

function Foo(){
    Foo.a = function(){
        console.log(1);
    }
    this.a = function(){
        console.log(2)
    }
}
 
Foo.prototype.a = function(){
    console.log(3);
}
 
Foo.a = function(){
    console.log(4);
}
 
Foo.a();
let obj = new Foo();
obj.a();
Foo.a();
//输出 4 2 1

解析:

  1. Foo.a() 这个是调用 Foo 函数的静态方法 a,虽然 Foo 中有优先级更高的属性方法 a,但 Foo此时没有被调用, 所以此时输出 Foo 的静态方法 a 的结果:4
  2. let obj = new Foo(); 使用了 new方法调用了函数,返回了函数实例对象,此时 Foo 函数内部的属性方法初始化,原型链建立。
  3. obj.a() ; 调用 obj实例上的方法 a,该实例上目前有两个 a 方法:一个是内部属性方法,另一个是原型上的方法。当这两者都存在时,首先查找 ownProperty ,如果没有才去原型链上找,所以调用实例上的 a 输出:2
  4. Foo.a() ; 根据第2步可知 Foo函数内部的属性方法已初始化,覆盖了同名的静态方法,所以输出:1

1.实例方法:定义在this中的变量和方法,只有实例才能访问到
2.静态方法: 构造函数本身上添加的成员 。如上述代码在构造函数外部添加的Foo.a。不能通过实例调用
3.原型方法: 原型中的方法实例和构造函数都可以访问到,比如上面的Foo.prototype.a

9.promise执行顺序

例1:

setTimeout(() => {
  console.log(1);
}, 0);
new Promise((resolve) => {
  console.log(2);
  resolve();
  console.log(3);
}).then(() => {
  console.log(4);
});
const promise2 = new Promise(async (resolve) => {
  console.log(await resolve(5));
  console.log(6);
});
async function test() {
  console.log(7);
  console.log(await promise2);
  console.log(8);
}
test();
console.log(9);

在这里插入图片描述
例2:

async function async1() {
	console.log('async1 start');
	await async2();
	console.log('asnyc1 end');
}
async function async2() {
	console.log('async2');
}
console.log('script start');
setTimeout(() => {
	console.log('setTimeOut');
}, 0);
async1();
new Promise(function (reslove) {
	console.log('promise1');
	reslove();
}).then(function () {
	console.log('promise2');
})
console.log('script end');

输出:

script start
async1 start
async2
promise1
script end
asnyc1 end
promise2
setTimeOut

详细原理解释:关于async/await、promise和setTimeout执行顺序


10.this指向

特别注意下面的var fn2 = obj.fn

function fn1() {
  console.log(this);
}
var obj = {
  fn() {
    console.log(this);
  },
  fn1: fn1,
};
var fn2 = obj.fn;
var fn3 = obj.fn1.bind(obj);

fn1();//window
fn2();//window 注意
fn3();//obj
obj.fn();//obj
obj.fn1();//obj

在这里插入图片描述

11. 情景题

11.1 手写弹窗组件

原理

web弹窗的实现方式很多,这里介绍最简单的一种方式。弹框其实就是在主页面上显示出一层内容,我们可以通过z-index属性来控制弹窗能够显示在页面的最高层,通过display:block 和 display:none 来控制弹窗是显示隐藏。
一个弹窗组件通常包含两个部分,分别是遮罩层和内容层。遮罩层是背景层,一般是半透明或不透明的黑色。内容层是放我们要展示的内容的容器。

react版

// components/Modal/index.jsx
import React, { ReactNode } from "react";
import classnames from "classnames";
import "./index.css";

export default function Modal(props) {
  const { visible, onClose, title, children } = props;
  return (
    <div className={classnames("modal", { "modal-visible": visible })}>
      {/* 弹窗内容部分 */}
      <div className="modal-body">
        <div className="modal-body__title">
          <span>{title}</span>
          <span onClick={onClose}>x</span>
        </div>
        <div className="modal-body__content">{children}</div>
        <div className="modal-body__footer">
          <button className="cancel-btn" onClick={onClose}>
            取消
          </button>
          <button className="ensure-btn">确定</button>
        </div>
      </div>
      {/* 遮罩 */}
      <div className="modal-mask"></div>
    </div>
  );

component/Modal/index.css
.modal {
  display: flex;
  align-items: center;
  justify-content: center;
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  transform: scale(0);
  transition: all 0.3s ease-in-out;
}

.modal-visible {
  transform: scale(1);
}

.modal-body {
  position: relative;
  width: 500px;
  height: 500px;
  background: #fff;
  border-radius: 4px;
  padding: 12px;
  z-index: 99;
}

.modal-body__title {
  padding: 8px 0;
  font-size: 18px;
  font-weight: 800px;
  color: #333;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.modal-body__content {
  padding: 8px 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.modal-body__footer {
  position: absolute;
  bottom: 12px;
  right: 12px;
  display: flex;
  gap: 8px;
  align-items: center;
  flex-direction: row-reverse;
}

.cancel-btn {
  background-color: rgb(170, 170, 170);
}

.ensure-btn {
  background-color: rgb(78, 78, 243);
}

.modal-mask {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.3);
}

// App.js
import React, { useState } from 'react';
import './App.css';
import Modal from './components/Modal';

function App() {
  const [visible, setVisible] = useState();
  return (
    <div className="app-body">
      <button className="ensure-btn" onClick={() => setVisible(true)}>打开弹窗</button>

      {/* 弹窗组件 */}
      <Modal
        visible={visible}
        title={'弹窗标题'}
        onClose={() => setVisible(false)} >
        弹窗内容
      </Modal>
    </div>
  );
}

export default App;

// App.css
.app-body {
  height: 100vh;
  width: 100vw;
  display: flex;
  align-items: center;
  justify-content: center;
}

.app-body button {
  border: none;
  padding: 8px 16px;
  border-radius: 4px;
  color: #fff;
  cursor: pointer;
}

Vue版

<template>
    <div class="modal-bg" v-show="show">
        <div class="modal-container">
            <div class="modal-header">
                {{ title }}
            </div>
            <div class="modal-main">
                <slot></slot>
            </div>
            <div class="modal-footer">
                <button @click="hideModal">取消</button>
                <button @click="submit">确认</button>
            </div>
        </div>
    </div>
</template>
<script>
export default {
    name: 'modal',
    props: {
        show: {
            type: Boolean,
            default: false
        },
        title: {
            type: String,
            default: ''
        },
    },
    methods: {
        hideModal() {
            this.$emit('hideModal')
        },

        submit() {
            this.$emit('submit')
        },
    }
}
</script>

调用:

<template>
<Modal :show="show" :title="title" @hideModal="hideModal" @submit="submit">
    <p>这里放弹窗的内容</p>
</Modal>
</template>

import Modal from './Modal.vue'
<script>
export default {
    data() {
      return {
        title: '弹窗标题',
        show: true,
      }
    },
    components: {
        Modal
    },
    methods: {
        hideModal() {
            // 取消弹窗回调
            this.show = false
        },

        submit() {
            // 确认弹窗回调
            this.show = false
        }
    }
}
</script>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一、资源详解 计算机类书籍:操作系统、计算机网络、计算机组成原理、汇编语言、C语言、C++、Java、Python、Go、前端算法与数据结构、大数据、人工智能、面试 实验报告:通过实际操作与数据记录,让您深入理解计算机内部的工作原理。每份实验报告都详细记录了实验步骤、结果及分析,助您巩固知识点。 学习笔记:由资深学者精心整理的学习笔记,重点突出,为您梳理课程脉络,把握核心内容。 复习资料与试卷:涵盖了各类复习资料和历年试卷,助您备战考试,查漏补缺,提高应试能力。 作业答案:提供完整的作业答案及解析,让您在完成课后作业时更有信心,确保理解每一个知识点。 一二、计算机组成原理:从基础到进阶,全面突破的必备资源 在信息爆炸的时代,计算机组成原理作为计算机科学的核心课程,显得尤为重要。为了帮助广大学子更好地掌握这一关键领域,我们特地整理了这一系列与计算机组成原理相关的资源,助力您的学术旅程。 三、适用场景广泛 无论是期末考试冲刺、计算机组成原理实验报告作业、还是复习、试、考研资料等需求,这些资源都能满足您的要求。全面覆盖理论要点与实践操作,让您在学习和备考过程游刃有余。 四、使用建议 系统学习:建议按照章节顺序进行系统学习,结合实验报告进行实践操作,以加深理解。 备考策略:利用复习资料与试卷资源,制定有效的备考计划,提高考试通过率。 持续反馈与改进:根据作业答案进行自我评估,找出薄弱环节,及时调整学习策略。 五、版权与安全提示 尊重知识产权:在使用这些资源时,请尊重原作者的权益,遵守版权法规。 安全使用:确保在使用过程不侵犯他人权益,避免任何形式的学术不端行为。 感谢您选择我们的计算机组成原理资源库!让我们一起深入探索计算机的奥秘,用知识武装自己,开启精彩的学术之旅!
目: 1. 请解释下HTML、CSS、JavaScript的作用分别是什么? 2. 请解释下盒模型Box Model是什么? 3. 请解释下CSS的优先级是什么? 4. 请解释下浏览器缓存机制是什么? 5. 请解释下跨域问是什么?如何解决? 6. 请解释下什么是事件冒泡和事件捕获? 7. 请解释下什么是闭包? 8. 请解释下什么是原型和原型链? 9. 请解释下如何实现一个Ajax请求? 10. 请解释下如何实现跨域请求? 答案: 1. HTML(超文本标记语言)负责页面结构和内容;CSS(层叠样式表)负责页面样式;JavaScript 负责页面交互和动态效果。 2. 盒模型Box Model指的是一个HTML元素由内容区域、内边距、边框和外边距组成。它是一个CSS的基本概念,对于页面布局和样式排版非常重要。 3. CSS的优先级是根据选择器的特殊性和位置来计算的。优先级从高到低依次为:!important(权重最高)、内联样式、ID选择器、类选择器、标签选择器和通配符选择器等。 4. 浏览器缓存机制指的是浏览器在请求某个资源时,会先检查本地缓存是否有该资源的副本,如果有就直接从缓存获取,否则才会从服务器请求。这样可以加速页面加载速度并减轻服务器负担。 5. 跨域问指的是浏览器出于安全考虑,禁止从一个域名的网页向另一个域名的资源发送请求。常见的解决方法有JSONP、CORS、反向代理等。 6. 事件冒泡指的是事件在DOM树从下往上逐级传播,直到被处理为止;事件捕获则是从上往下逐级传播,也是直到被处理为止。两者是互补的关系,事件既可以采用冒泡模式也可以采用捕获模式,也可以同时使用。 7. 闭包是指函数可以访问函数定义时所处的作用域,即使函数在其他地方调用也可以访问该作用域的变量。它可以用来创建私有变量和函数、实现函数记忆、实现模块化等。 8. 原型是指JavaScript每个对象都有一个原型对象,它包含了该对象的公共属性和方法。原型链是指对象在寻找属性和方法时会沿着原型链向上查找,直到找到为止。这样可以实现对象的继承和复用。 9. Ajax请求可以通过XMLHttpRequest对象来实现。首先创建一个XMLHttpRequest对象,然后调用open()方法设置请求方法和URL,再调用send()方法发送请求,并在回调函数处理响应数据。 10. 跨域请求可以通过JSONP、CORS、反向代理等方法来实现。其JSONP利用script标签的src属性可以跨域加载资源的特性,CORS则是通过服务器设置响应头来实现跨域请求的。反向代理则是在同一个域名下设置一个代理服务器,将跨域请求转发到目标服务器上。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值