自我介绍

面试官您好,我是***,本科毕业于****,硕士毕业于****信息管理专业。我一开始是希望从事量化策略研究,但是我后来觉得不太适合自己,所以决定从事前端开发的工作。我自学了操作系统和计算机网络,然后也开发了一个yelp-camp的项目,用于分享露营地点。我也使用了react框架来实现了复杂的用户界面和流畅的用户体验。在项目中我用到了包括状态管理、组件化开发以及使用Redux进行全局状态的管理。我也非常希望能加入美团,来从事前端开发的工作。

Deep copy

var o = { name: "MiTaoEr", info: { address: "天津", color: "red" } };
var t = JSON.parse(JSON.stringify(o));
o.info.address = "北京";
console.log(t);
/* { name: 'MiTaoEr', info: { address: '天津', color: 'red' } } */

JSON 数据中没有函数和 undefined 类型,因此在进行序列化的过程中,对象中的这部分数据会被直接过滤掉,此外正则类型的数据也会被处理为空对象。

        /* 深拷贝实现函数 */
        let deepClone = (val, wm = new WeakMap) => {
            if (val == null) return val;
            if (typeof val !== "object") return val;
            if (val instanceof Date) return new Date(val);
            if (val instanceof RegExp) return new RegExp(val);
            if (wm.has(val)) return wm.get(val);
            let _instance = new val.constructor;
            wm.set(val, _instance);

            for (let key in val) {
                if (val.hasOwnProperty(key)) _instance[key] = deepClone(val[key], wm);
            }
            return _instance;
        }

层序遍历

var levelOrder = function(root) {
    //二叉树的层序遍历
    let res = [], queue = [];
    queue.push(root);
    if(root === null) {
        return res;
    }
    while(queue.length !== 0) {
        // 记录当前层级节点数
        let length = queue.length;
        //存放每一层的节点
        let curLevel = [];
        for(let i = 0;i < length; i++) {
            let node = queue.shift();
            curLevel.push(node.val);
            // 存放当前层下一层的节点
            node.left && queue.push(node.left);
            node.right && queue.push(node.right);
        }
        //把每一层的结果放到结果数组
        res.push(curLevel);
    }
    return res;
};

前序遍历

var preorderTraversal = function(root) {
 let res=[];
 const dfs=function(root){
     if(root===null)return ;
     //先序遍历所以从父节点开始
     res.push(root.val);
     //递归左子树
     dfs(root.left);
     //递归右子树
     dfs(root.right);
 }
 //只使用一个参数 使用闭包进行存储结果
 dfs(root);
 return res;
};

中序遍历

var inorderTraversal = function(root) {
    let res=[];
    const dfs=function(root){
        if(root===null){
            return ;
        }
        dfs(root.left);
        res.push(root.val);
        dfs(root.right);
    }
    dfs(root);
    return res;
};

后序遍历

var postorderTraversal = function(root) {
    let res=[];
    const dfs=function(root){
        if(root===null){
            return ;
        }
        dfs(root.left);
        dfs(root.right);
        res.push(root.val);
    }
    dfs(root);
    return res;
};

快速排序

快速排序使用分治法open in new window(Divide and conquer)策略来把一个序列分为较小和较大的 2 个子序列,然后递归地排序两个子序列。具体算法描述如下:

  1. 从序列中随机挑出一个元素,做为 “基准”(pivot);
  2. 重新排列序列,将所有比基准值小的元素摆放在基准前面,所有比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个操作结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  3. 递归地把小于基准值元素的子序列和大于基准值元素的子序列进行快速排序。

插入排序

1从第一个元素开始,该元素可以认为已经被排序;

2取出下一个元素,在已经排序的元素序列中从后向前扫描;

3如果该元素(已排序)大于新元素,将该元素移到下一位置;

4重复步骤 3,直到找到已排序的元素小于或者等于新元素的位置;

5将新元素插入到该位置后;

重复步骤 2~5。


著作权归JavaGuide(javaguide.cn)所有 基于MIT协议 原文链接:https://javaguide.cn/cs-basics/algorithms/10-classical-sorting-algorithms.html

著作权归JavaGuide(javaguide.cn)所有 基于MIT协议 原文链接:https://javaguide.cn/cs-basics/algorithms/10-classical-sorting-algorithms.html

function quickSort(arr) {
    // 递归结束条件:数组长度小于或等于1
    if (arr.length <= 1) {
        return arr;
    }

    // 选择基准元素(pivot),这里选择数组中间的元素
    const pivotIndex = Math.floor(arr.length / 2);
    const pivot = arr.splice(pivotIndex, 1)[0]; // 从数组中取出基准元素,并从原数组中移除

    let left = [];
    let right = [];

    // 遍历数组,根据与基准元素的比较结果分配到左边或右边数组
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] < pivot) {
            left.push(arr[i]);
        } else {
            right.push(arr[i]);
        }
    }

    // 递归对左右两部分数组进行快速排序,然后将排序好的数组和基准元素合并
    return quickSort(left).concat([pivot], quickSort(right));
}

// 示例数组
const array = [6, 3, 8, 5, 2, 7, 4, 1];
console.log("Original array:", array);
const sortedArray = quickSort(array);
console.log("Sorted array:", sortedArray);

事件循环

可以看到,Eventloop 在处理宏任务和微任务的逻辑时的执行情况如下:

  1. JavaScript 引擎首先从宏任务队列中取出第一个任务;
  2. 执行完毕后,再将微任务中的所有任务取出,按照顺序分别全部执行(这里包括不仅指开始执行时队列里的微任务),如果在这一步过程中产生新的微任务,也需要执行,也就是说在执行微任务过程中产生的新的微任务并不会推迟到下一个循环中执行,而是在当前的循环中继续执行。
  3. 然后再从宏任务队列中取下一个,执行完毕后,再次将 microtask queue 中的全部取出,循环往复,直到两个 queue 中的任务都取完。

作者:CUGGZ 链接:https://juejin.cn/post/6992167223523541023 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

filter(e=>e.id!==id)

删除id的函数

手写promise.all

function promiseAll(promises) {
    return new Promise((resolve, reject) => {
        if (!Array.isArray(promises)) {
            return reject(new TypeError("Argument must be an array of promises."));
        }
        let results = [];
        let completed = 0;
        for (let i = 0; i < promises.length; i++) {
            Promise.resolve(promises[i]) // 处理非Promise值的情况,确保一致性
                .then(value => {
                    results[i] = value;  // 保持原数组的顺序
                    completed += 1;
                    if (completed === promises.length) {
                        resolve(results); // 当所有promises都解决时,resolve这个新Promise
                    }
                })
                .catch(reject); // 任何一个Promise失败了,新的Promise立即reject
        }
        if (promises.length === 0) {
            resolve(results); // 如果传入的数组为空,直接resolve一个空数组
        }
    });
}

// 使用示例
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, 'foo');
});

promiseAll([promise1, promise2, promise3]).then(values => {
    console.log(values);  // 输出: [3, 42, 'foo']
}).catch(error => {
    console.log('Failed:', error);
});

render

setstate and re-render and get new state value

side-effects

effects

AJAX changing parts of DOM unrelated to render

useEffect by default runs every time render

useEffect(fn,[])

only the first time

empty state and I wanna fetch data

cookies

Domain 属性

Domain 指定了哪些主机可以接受 Cookie。如果不指定,该属性默认为同一 host 设置 cookie,不包含子域名。如果指定了 Domain,则一般包含子域名。因此,指定 Domain 比省略它的限制要少。但是,当子域需要共享有关用户的信息时,这可能会有所帮助。

例如,如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如 developer.mozilla.org)。

Path 属性

Path 属性指定了一个 URL 路径,该 URL 路径必须存在于请求的 URL 中,以便发送 Cookie 标头。以字符 %x2F (“/”) 作为路径分隔符,并且子路径也会被匹配。

例如,设置 Path=/docs,则以下地址都会匹配:

但是这些请求路径不会匹配以下地址:

SameSite 属性允许服务器指定是否/何时通过跨站点请求发送(其中站点由注册的域和方案定义:http 或 https)。这提供了一些针对跨站点请求伪造攻击(CSRF)的保护。它采用三个可能的值:StrictLaxNone

使用 Strict,cookie 仅发送到它来源的站点。Lax 与 Strict 相似,只是在用户导航到 cookie 的源站点时发送 cookie。例如,通过跟踪来自外部站点的链接。None 指定浏览器会在同站请求和跨站请求下继续发送 cookie,但仅在安全的上下文中(即,如果 SameSite=None,且还必须设置 Secure 属性)。如果没有设置 SameSite 属性,则将 cookie 视为 Lax

useRef useState

useRef

  1. 保持引用不变useRef返回一个可变的ref对象,其.current属性被初始化为传递给useRef的参数。这个对象在组件的整个生命周期中保持不变。
  2. 不触发重新渲染:更新ref对象的.current属性不会触发组件的重新渲染。这使得useRef非常适合用于跟踪组件内的变量和状态,而不需要触发视图更新。
  3. DOM引用useRef常用于获取组件的DOM节点,例如,当你需要直接操作DOM时,可以将ref对象赋给元素的ref属性。
  4. 保存任何可变值:除了DOM引用外,useRef也常用于保存任何其他数据,比如一个计时器的ID或任何其他实例,其值可能在组件的多次渲染之间变化但不需要触发重新渲染。

useState

  1. 状态管理useState用于在函数组件中添加状态。它返回一个状态变量和一个用于更新这个状态的函数。这个状态在组件的重新渲染间是持久的。
  2. 触发重新渲染:当你通过useState的更新函数更新状态时,它会触发组件的重新渲染,从而反映状态的更新。
  3. 用于数据绑定useState通常用于那些当数据变化时需要更新UI的场景。每次状态改变都会导致组件重新渲染,确保用户界面与状态数据保持同步。
  4. 支持函数更新useState的更新函数支持函数式更新,这对于依赖于前一个状态来计算新状态的场景非常有用。

示例对比

这里有一个简单的示例来展示useRefuseState的区别

ueEffect 模拟生命周期

在React中,useEffect 钩子提供了一种方式来模拟类组件的生命周期方法。通过适当的使用依赖项数组(第二个参数),可以在函数组件中实现与componentDidMountcomponentDidUpdate、和componentWillUnmount相似的行为。下面将详细介绍如何使用 useEffect 来模拟这些生命周期方法:

1. 模拟 componentDidMount

要模拟 componentDidMount 的行为,可以传递一个空的依赖项数组给 useEffect。这意味着useEffect 中的代码只会在组件首次渲染后执行一次。

useEffect(() => {
    // 这里的代码只会在组件首次渲染后执行一次,类似于 componentDidMount
    console.log('Component did mount');

    return () => {
        // 这里的代码会在组件卸载时执行,类似于 componentWillUnmount
        console.log('Component will unmount');
    };
}, []);

2. 模拟 componentDidUpdate

要模拟 componentDidUpdate,可以在 useEffect 的依赖项数组中指定需要观察的状态或属性。这样,只要这些依赖项发生变化,useEffect 就会被重新执行。

useEffect(() => {
    // 这段代码会在依赖项中的状态或属性更新后执行,类似于 componentDidUpdate
    console.log('Component did update');

    return () => {
        // 这里不会执行任何清理操作,因为它每次更新都会调用
    };
}, [dependency1, dependency2]); // 只有当 dependency1 或 dependency2 改变时,useEffect 才会运行

3. 模拟 componentWillUnmount

要模拟组件卸载时的行为,可以在 useEffect 的清理函数中编写代码。这个清理函数会在组件卸载前执行,或者在依赖项改变导致旧的 useEffect 清理前执行。

useEffect(() => {
    // 设置定时器、订阅事件、或执行某些只需要运行一次的效果

    return () => {
        // 清理定时器、取消订阅事件等,这里的代码会在组件卸载前执行,类似于 componentWillUnmount
        console.log('Component will unmount');
    };
}, []); // 依赖项为空,表示这个 effect 只在挂载和卸载时运行

总结

使用 useEffect 钩子来模拟类组件的生命周期方法是一种非常强大的技术,它使得在函数组件中处理副作用成为可能。通过合理使用依赖项数组,可以精确控制副作用的触发时机,以及何时进行必要的清理工作。这提高了代码的可维护性和性能,是React推荐的函数组件中处理副作用的方式。

跨域

180页

useConext

在React中,useContext 是一个钩子(Hook),它允许你在组件树中访问跨层级的状态和函数,无需通过逐层传递 props。这个钩子是用来简化跨组件的数据传递,特别是对于一些需要在多个层级中使用的公共数据,如用户认证状态、主题设置、语言偏好等。

使用 useContext

useContext 钩子需要与 React.createContext 配合使用。首先,你需要使用 React.createContext 创建一个上下文(Context)对象。这个对象将包括一个 Provider 组件和一个 Consumer 组件。Provider 用于封装那些需要访问上下文数据的组件,并通过 value 属性提供上下文数据。useContext 钩子使得函数组件可以订阅这个上下文的变化,并读取上下文的值。

步骤示例

下面是如何使用 useContext 的一个基本步骤:

  1. 创建上下文

    import React, { createContext } from 'react';
    
    const MyContext = createContext(null);
    
  2. 提供上下文数据 在组件树的合适位置使用 Provider 来封装子组件,传递需要跨组件共享的数据。

    import React from 'react';
    import { MyContext } from './MyContext';
    
    function App() {
      return (
        <MyContext.Provider value={{ sharedData: "Hello, Context!" }}>
          <ChildComponent />
        </MyContext.Provider>
      );
    }
    
  3. 消费上下文数据 在需要使用上下文数据的组件内,使用 useContext 钩子访问这些数据。

    import React, { useContext } from 'react';
    import { MyContext } from './MyContext';
    
    function ChildComponent() {
      const context = useContext(MyContext);
    
      return <p>{context.sharedData}</p>; // 输出:Hello, Context!
    }
    

优势

使用 useContext 可以大大简化组件之间的通信,特别是在需要将数据传递给深层嵌套组件时。与传统的通过 props 逐层传递相比,useContext 提供了一种更清洁、更直接的方式来共享数据。

注意事项

总之,useContext 是React提供的一种强大工具,能够帮助开发者在组件间共享数据和状态,同时保持代码的整洁和组织。

原型继承和class继承

image-20240424142420783

组合式继承

如何优化性能

React性能优化是一个重要的主题,特别是当你的应用规模扩大、组件数量增多时,合理的优化措施可以显著提高应用的响应速度和用户体验。下面是一些常见的React性能优化策略:

1. 使用不可变数据

2. 优化渲染列表

3. 组件懒加载

4. 避免匿名函数和对象字面量

5. 使用shouldComponentUpdateReact.memo

6. 合理使用useCallbackuseMemo

7. 状态提升和拆分

8. 减少DOM操作

9. 使用Key属性

10. 监控和分析性能

通过实施上述策略,可以有效地提升React应用的性能。始终记住,优化工作应该是基于性能瓶颈的具体分析结果来进行的,避免过早优化。

useMemo useCallbaclk有什么区别

在React中,useMemouseCallback 都是钩子(Hooks),用于优化性能,主要通过缓存来避免不必要的计算或重新渲染。尽管它们在功能上有所重叠,但它们各自有特定的用途和适用场景。

useMemo

useMemo 用于缓存复杂函数的返回值。当你有一个计算成本较高的函数时,你可以使用 useMemo 来存储这个函数的结果,直到其依赖项发生变化。只有当依赖项改变时,函数才会重新执行,并缓存新的返回值。

用途

示例

const heavyComputation = expensiveValue => {
  // 模拟一个重计算过程
  console.log("Computing...");
  return expensiveValue * 2;
};

const Component = ({ expensiveValue }) => {
  const computedValue = useMemo(() => heavyComputation(expensiveValue), [expensiveValue]);

  return <div>{computedValue}</div>;
};

在上述示例中,只有当 expensiveValue 改变时,heavyComputation 函数才会重新执行,并更新 computedValue

useCallback

useCallback 用于缓存函数实例。当你将函数传递给子组件作为prop时,每次父组件渲染都会创建新的函数实例,即使函数体完全相同。这会导致接收该函数的子组件认为props发生了变化,从而触发不必要的重新渲染。通过 useCallback,你可以保证只有当函数的依赖项改变时,函数实例才会更新。

用途

示例

const Component = ({ id, onFetch }) => {
  const handleClick = useCallback(() => {
    onFetch(id);
  }, [id, onFetch]);

  return <button onClick={handleClick}>Fetch Data</button>;
};

在上述示例中,handleClick 会被缓存,并只有当 idonFetch 发生变化时才会更新。

区别

理解和正确使用这两个钩子可以显著提高应用的性能,特别是在处理大量数据和复杂更新时。

webpack和vite

WebpackVite都是现代前端开发中非常流行的工具,它们用于优化前端资源的加载和打包。虽然两者都服务于类似的目的,它们的工作方式和优势有所不同。

Webpack

概述

核心特点

Vite

概述

核心特点

区别

总结来说,Webpack是一个成熟的解决方案,适用于需要复杂配置和细粒度优化的大型应用。而Vite则更适合于现代开发,它利用ESM和更快的构建性能,为开发者提供了更快的开发体验。选择哪一个工具取决于项目需求、团队习惯以及对构建速度和灵活性的需求。

图片懒加载

2. 使用Intersection Observer API(推荐方法)

Intersection Observer API提供了一种方式,可以配置地监听元素是否进入了视口区域,适用于实现图片懒加载。这种方法比传统的监听scroll事件性能更好,因为它由浏览器直接支持,不需要进行复杂的计算或频繁的DOM访问。

document.addEventListener("DOMContentLoaded", function() {
  const images = document.querySelectorAll('img[data-src]'); // 所有带data-src属性的图片

  const imageObserver = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        img.src = img.getAttribute('data-src');
        img.removeAttribute('data-src');
        observer.unobserve(img); // 图片加载后取消观察
      }
    });
  }, {
    rootMargin: '0px 0px 50px 0px', // 触发提前量,可以根据需要调整
    threshold: 0.01
  });

  images.forEach(image => {
    imageObserver.observe(image);
  });
});

最大的难点

最有难度的点:细粒度的权限控制系统

在你的实习项目中,开发因子管理系统的一个关键难点可能是实现细粒度的权限控制系统。权限控制系统需要确保只有授权用户可以访问特定的数据和功能,例如防止普通员工查看其他人的成果展示。这要求前端不仅要正确显示数据和功能,还需要确保安全性和数据的访问控制。

解决方法:使用角色基权限管理(RBAC)和前端路由守卫

为了解决权限控制的问题,可以实施角色基权限管理(Role-Based Access Control, RBAC)系统,并结合前端路由守卫来控制用户访问。

实施步骤:

  1. 定义角色和权限

  2. 后端权限验证

  3. 前端路由守卫

import React from 'react';
import { Route, Redirect } from 'react-router-dom';

const ProtectedRoute = ({ component: Component, roles, ...rest }) => (
  <Route {...rest} render={props => {
    const currentUser = authenticationService.currentUserValue;
    if (!currentUser || !roles.includes(currentUser.role)) {
      // 不符合要求的用户尝试访问这个路由时被重定向到登录页面
      return <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
    }

    // 授权所需角色的用户可以访问
    return <Component {...props} />
  }} />
);
  1. 在应用中实施

总结

通过在后端实施强大的权限验证,并在前端利用路由守卫来防止未授权访问,可以有效地实现细粒度的权限控制。这不仅提高了系统的安全性,还确保了用户体验的一致性和合法性。

Number结果

为什么 Number('') 是 0

当你使用Number函数转换一个空字符串''时,结果是0。根据ECMAScript规范,如果字符串仅包含空白字符(或者根本没有字符),则将其转换为0。空字符串被视为缺乏任何数值,因此按照规范被赋予了最自然的数值表示——即0。这类似于在逻辑上认为”没有值”可以等价于”零值”。

为什么 Number(undefined) 是 NaN

对于undefined,情况则完全不同。undefined在JavaScript中是一个表示缺少值的数据类型。当你试图将undefined转换为一个数字时,按照ECMAScript规范,这个转换操作无法找到一个合理的数值表示,因此结果是NaN(Not a Number),表示这不是一个有效的数字。

堆和栈的关系

在JavaScript中,堆(Heap)和栈(Stack)是两种数据结构,用于存储变量和管理执行上下文中的数据。尽管JavaScript开发者不需要手动管理这些内存结构,了解它们的工作原理有助于更好地理解性能问题、内存使用和垃圾回收等概念。

栈(Stack)

栈是一种线性的数据结构,遵循后进先出(LIFO,Last In First Out)的原则。在JavaScript的执行上下文中,栈用于存储原始数据类型(Undefined, Null, Boolean, Number, String, Symbol, BigInt)的值和函数调用的记录。

堆(Heap)

堆是一种用于存储对象(及其他可能的复杂数据结构)的非结构化的内存区域。相对于栈,堆的结构更自由,容量更大,但访问速度较慢。

栈和堆的主要区别

  1. 数据结构

  2. 存储内容

  3. 内存分配

  4. 性能

  5. 大小限制

了解堆和栈的这些基本差异有助于优化性能,特别是在处理大量数据和深层递归函数调用时。通过有效地管理存储原始类型和对象的方式,可以减少内存使用和避免性能瓶颈。

在JavaScript中,闭包(closure)是指一个函数与其词法环境的组合。当函数在其定义的环境外被调用时,仍然能够访问该环境中的变量和函数。这种特性使得闭包在JavaScript中非常强大和灵活。

闭包

闭包的定义与基本概念

闭包是指函数能够记住并访问其词法作用域,即使函数在其词法作用域之外执行。闭包是在创建函数时被捕获的,而不是在执行函数时。

闭包的作用

  1. 数据隐藏与封装:闭包可以创建私有变量,防止外部直接访问和修改。
  2. 函数工厂:闭包可以用来创建带有不同环境的函数,便于重用。
  3. 回调函数和事件处理:闭包可以保持对某些变量的引用,便于在异步操作中使用。

闭包的实例

以下是一些使用闭包的典型示例:

示例1:数据隐藏与封装

function createCounter() {
    let count = 0;
    return {
        increment: function() {
            count++;
            return count;
        },
        decrement: function() {
            count--;
            return count;
        },
        getCount: function() {
            return count;
        }
    };
}

const counter = createCounter();
console.log(counter.increment()); // 输出: 1
console.log(counter.increment()); // 输出: 2
console.log(counter.getCount());  // 输出: 2
console.log(counter.decrement()); // 输出: 1

在这个例子中,count 变量是 createCounter 函数的局部变量,通过返回的对象中的函数可以访问和修改这个变量。这些函数形成了一个闭包,使得 countcreateCounter 函数执行完后仍然存在。

示例2:函数工厂

function createGreeting(greeting) {
    return function(name) {
        return greeting + ", " + name;
    };
}

const sayHello = createGreeting("Hello");
const sayHi = createGreeting("Hi");

console.log(sayHello("Alice")); // 输出: Hello, Alice
console.log(sayHi("Bob"));      // 输出: Hi, Bob

在这个例子中,createGreeting 函数返回一个新的函数,该函数记住了 greeting 参数的值。不同的函数实例可以记住不同的 greeting 值,从而创建不同的问候语。

示例3:回调函数和事件处理

function setupClickHandler(message) {
    document.getElementById("myButton").addEventListener("click", function() {
        alert(message);
    });
}

setupClickHandler("Button was clicked!"); 

在这个例子中,匿名函数作为事件处理函数,形成了一个闭包,记住了 setupClickHandler 调用时的 message 参数的值。当按钮被点击时,这个闭包中的 message 会被正确地访问和显示。

闭包的常见问题

  1. 内存泄漏:不当使用闭包可能会导致内存泄漏,因为闭包会保持对外部环境的引用。
  2. 性能问题:在某些情况下,大量使用闭包可能会影响性能,因为闭包会占用更多内存和计算资源。

总结

闭包是JavaScript中的一个强大概念,它允许函数访问其定义时的作用域中的变量和函数,即使在其执行环境之外。通过正确使用闭包,可以实现数据隐藏、函数工厂和复杂的回调机制,从而提高代码的灵活性和可维护性。

防抖节流

Debounce

function debounce(func, ms) {
  let timeout;
  return function() {
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(this, arguments), ms);
  };
}

详细解释

  1. 参数
  2. 内部变量
  3. 返回的函数
  4. 函数执行

为什么这个可以实现防抖

这个实现的核心在于每次调用防抖函数时都会重新设置一个定时器,并清除前一个定时器。只有在指定的时间间隔(ms)内没有新的调用时,定时器才会到期并执行 func。这样可以确保 func 只在事件结束后的特定时间内执行一次,而不会因为频繁触发事件而多次执行。

使用示例

// 防抖函数的示例
function onResize() {
  console.log('Window resized');
}

window.addEventListener('resize', debounce(onResize, 300));

在这个示例中,当窗口大小调整事件频繁触发时,onResize 函数只会在调整结束后的 300 毫秒后执行一次,而不是每次触发事件时都执行。

节流

function throttle(func, ms) {

  let isThrottled = false,
    savedArgs,
    savedThis;

  function wrapper() {

    if (isThrottled) { // (2)
      savedArgs = arguments;
      savedThis = this;
      return;
    }
    isThrottled = true;

    func.apply(this, arguments); // (1)

    setTimeout(function() {
      isThrottled = false; // (3)
      if (savedArgs) {
        wrapper.apply(savedThis, savedArgs);
        savedArgs = savedThis = null;
      }
    }, ms);
  }

  return wrapper;
}
  1. 在第一次调用期间,wrapper 只运行 func 并设置冷却状态(isThrottled = true)。
  2. 在冷却状态下,所有调用都被保存在 savedArgs/savedThis 中。请注意,上下文(this)和参数(arguments)都很重要,应该被保存下来。我们需要它们来重现调用。
  3. 经过 ms 毫秒后,setTimeout中的函数被触发。冷却状态被移除(isThrottled = false),如果存在被忽略的调用,将使用最后一次调用保存的参数和上下文运行 wrapper

异步

异步解决⽅案

同步操作:顺序执⾏,同⼀时间只能做⼀件事情。缺点是会阻塞后⾯代码的执⾏。 异步:指的是当前代码的执⾏作为任务放进任务队列。当程序执⾏到异步的代码时,会将该异步的代码作为任务放 进任务队列,⽽不是推⼊主线程的调⽤栈。等主线程执⾏完之后,再去任务队列⾥执⾏对应的任务。优点是:不会 阻塞后续代码的运⾏。

异步场景

  1. 定时任务:setTimeout、setInterval

  2. ⽹络请求:ajax请求、动态创建img标签的加载

  3. 事件监听器:addEventListener

回调

回调函数就是我们请求成功后需要执⾏的函数。 实现了异步,但是带来⼀个⾮常严重的问题——回调地狱。 事件发布/订阅

Promise

const promise = new Promise((resolve, reject) => {
resolve('a');
});

.then((arg) => {
console.log(`执⾏resolve,参数是${arg}`)
})
.catch((arg) => {
console.log(`执⾏reject,参数是${arg}`)
})
.finally(() => {
console.log('结束promise')
});
Promise.reject(2)
//.catch(err=>console.log("err1,",err))
.then(null, err => console.log("err1,", err)) //因为是rejected状态,执⾏then的第⼆个
callback,改变状态为fulfilled
.then(res => {
console.log("then1", res)
}, null) //因为是fulfilled,于是执⾏第⼀个回调,不会去到下⼀步catch
//.catch(err=>console.log("err2,",err))
.then(null, err => console.log("err2,", err))

then实现链式操作减低代码复杂度,增强代码可读性。 Promise对象的错误具有“冒泡”性质,会⼀直向后传递,直到被捕获为⽌。 每个Promise都会经历的⽣命周期是: 进⾏中(pending) - 此时代码执⾏尚未结束,所以也叫未处理的(unsettled) 已处理(settled) - 异步代码已执⾏结束 已处理的代码会进⼊两种状态中的⼀种: 已完成(fulfilled) - 表明异步代码执⾏成功,由resolve()触发 已拒绝(rejected)- 遇到错误,异步代码执⾏失败 ,由reject()触发 ⽅法

  1. Promise.all 传⼊多个异步请求数组,若all成功,进⼊fulfilled状态,若⼀个失败,则⽴即进⼊rejected状态。
  2. Promise.allSettled 传⼊多个异步请求数组,⽆论失败还是失败,都会进⼊fulfilled状态。
  3. Promise.race 以⼀个Promise对象组成的数组作为参数,只要当数组中⼀个Promsie状态变成resolved或者 rejected时,就调⽤.then⽅法。 事件循环 Generator promise

callapply 都是 JavaScript 中常用的方法,用于改变函数执行时 this 的指向。它们之间的主要区别在于传递参数的方式:

  1. call 方法:

  2. apply 方法:

总结

实际应用场景

  1. call

    function Product(name, price) {
      this.name = name;
      this.price = price;
    }
    
    function Food(name, price) {
      Product.call(this, name, price);
      this.category = 'food';
    }
    
    console.log(new Food('cheese', 5));
    // 输出: Food { name: 'cheese', price: 5, category: 'food' }
    
  2. apply

    const numbers = [5, 6, 2, 3, 7];
    
    const max = Math.max.apply(null, numbers);
    const min = Math.min.apply(null, numbers);
    
    console.log(max, min);
    // 输出: 7 2
    

希望这些解释和示例能帮助你理解 callapply 之间的差异。

在 CSS 中,px, rem, 和 em 是常用的长度单位,它们用于定义元素的尺寸、间距、字体大小等。它们各自有不同的特性和应用场景:

px (像素)

em

rem

总结

使用建议

这些单位各有优缺点,选择适合的单位可以让你的设计更灵活和响应式。

前端监控报错是一项重要的工作,能够帮助开发者及时发现和解决线上问题,提高应用的稳定性和用户体验。以下是几种常用的前端监控报错方法:

1. window.onerror 事件

window.onerror 是一个全局事件处理程序,用于捕获未处理的 JavaScript 错误。它可以捕获运行时错误的信息,包括错误消息、URL、行号和列号。

window.onerror = function(message, source, lineno, colno, error) {
  console.error(`Error: ${message}, Source: ${source}, Line: ${lineno}, Column: ${colno}, Error object: ${error}`);
  // 发送错误信息到服务器
  sendErrorToServer({ message, source, lineno, colno, error });
  return false; // 阻止浏览器默认错误提示
};

2. try...catch 语句

使用 try...catch 可以捕获代码块中的同步错误,并处理这些错误或将其发送到服务器。

try {
  // 可能会抛出错误的代码
} catch (error) {
  console.error('Caught an error:', error);
  // 发送错误信息到服务器
  sendErrorToServer(error);
}

3. Promise 错误处理

使用 .catch 方法处理 Promise 中的错误。

somePromiseFunction()
  .then(result => {
    // 处理结果
  })
  .catch(error => {
    console.error('Promise rejected:', error);
    // 发送错误信息到服务器
    sendErrorToServer(error);
  });

4. 全局未捕获的 Promise 错误

可以通过监听 unhandledrejection 事件捕获未处理的 Promise 错误。

window.addEventListener('unhandledrejection', function(event) {
  console.error('Unhandled rejection:', event.reason);
  // 发送错误信息到服务器
  sendErrorToServer(event.reason);
});

5. 前端监控工具

使用第三方前端监控工具,如 Sentry、LogRocket、New Relic、TrackJS 等。这些工具提供了强大的错误捕获、日志记录和分析功能,可以更方便地监控和处理前端错误。

// 示例使用 Sentry
Sentry.init({ dsn: 'https://example@sentry.io/123456' });

// 捕获一个异常
Sentry.captureException(new Error('Something went wrong'));

6. 自定义日志记录

实现自定义的日志记录系统,将错误信息发送到服务器端以进行存储和分析。

function sendErrorToServer(error) {
  fetch('/log', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      message: error.message,
      stack: error.stack,
      // 可以添加更多信息
    })
  });
}

7. Performance API

Performance API 提供了一种捕获和分析前端性能问题的方法,这对于识别和解决性能相关的错误非常有用。

// 记录页面加载时间
window.addEventListener('load', function() {
  const performanceTiming = window.performance.timing;
  const loadTime = performanceTiming.loadEventEnd - performanceTiming.navigationStart;
  console.log('Page load time:', loadTime);
  // 发送性能数据到服务器
  sendPerformanceDataToServer(loadTime);
});

8. 监控网络请求

通过拦截和监控网络请求,可以捕获与服务器交互相关的错误。

// 拦截 fetch 请求
const originalFetch = window.fetch;
window.fetch = function() {
  return originalFetch.apply(this, arguments)
    .then(response => {
      if (!response.ok) {
        console.error('Fetch error:', response.statusText);
        // 发送错误信息到服务器
        sendErrorToServer({ message: response.statusText });
      }
      return response;
    })
    .catch(error => {
      console.error('Fetch error:', error);
      // 发送错误信息到服务器
      sendErrorToServer(error);
      throw error;
    });
};

9. 用户行为追踪

通过捕获用户行为(如点击、输入等)来重现错误,帮助开发者更好地理解问题的根源。

document.addEventListener('click', function(event) {
  const target = event.target;
  console.log('User clicked on:', target);
  // 记录用户行为
  logUserAction('click', target);
});

通过结合以上方法,可以构建一个全面的前端监控系统,有效捕获和处理前端错误,提高应用的稳定性和用户体验。

事件委托(Event Delegation)是指将事件监听器添加到父元素上,而不是直接添加到子元素上。当事件被触发时,事件会从事件目标元素开始,沿着 DOM 树向上传播(冒泡),从而可以在父元素上检测到子元素的事件。这种方法在处理大量子元素的事件时非常有效,减少了内存占用和事件绑定的开销。

事件委托的工作原理

事件委托依赖于事件冒泡机制。事件冒泡是指事件从目标元素向上冒泡到父元素、祖父元素,直到根元素(通常是 document)。

事件委托的优势

  1. 减少内存使用:只需要在父元素上添加一个事件监听器,而不是在每个子元素上都添加一个监听器。
  2. 动态元素处理:能够处理动态添加或删除的子元素,不需要重新绑定事件。
  3. 简化代码:避免重复的事件绑定代码,使代码更简洁。

事件委托的实现

以下是一个使用事件委托的示例,展示如何在父元素上监听子元素的点击事件。

HTML 结构

<ul id="parent">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>

JavaScript 实现

document.getElementById('parent').addEventListener('click', function(event) {
  // 检查事件目标是否是我们关心的元素
  if (event.target && event.target.nodeName === 'LI') {
    console.log('List item clicked:', event.target.textContent);
    // 可以在这里添加更多逻辑,比如高亮点击的项等
  }
});

在这个例子中,我们只在 ul 元素(id="parent")上绑定一个点击事件监听器,然后通过检查 event.target 确定实际被点击的 li 元素。

事件委托的注意事项

  1. 事件冒泡:确保使用的事件是支持冒泡的,例如 clickfocusinkeydown 等。
  2. 性能:虽然事件委托能减少内存占用,但在某些情况下(例如非常深的嵌套结构或频繁的事件触发),也可能带来性能问题。
  3. 事件目标:需要小心处理 event.target,确保它是我们期望的元素类型。可以使用 matches 方法来进行更复杂的匹配。
  4. 停止冒泡:某些情况下可能需要使用 event.stopPropagation() 来防止事件继续冒泡。

进阶示例

如果我们需要在多个不同类型的子元素上处理不同的事件,可以使用更复杂的条件判断或使用 matches 方法:

<div id="parent">
  <button class="btn">Button 1</button>
  <button class="btn">Button 2</button>
  <a href="#" class="link">Link 1</a>
  <a href="#" class="link">Link 2</a>
</div>
document.getElementById('parent').addEventListener('click', function(event) {
  if (event.target.matches('.btn')) {
    console.log('Button clicked:', event.target.textContent);
  } else if (event.target.matches('.link')) {
    event.preventDefault(); // 阻止链接的默认行为
    console.log('Link clicked:', event.target.textContent);
  }
});

通过这种方式,可以在一个父元素上处理多种类型的子元素事件,使代码更加简洁和高效。

在 JavaScript 中,千分化(也称为数字分组)是指将一个长数字格式化为带有千分位分隔符的字符串。以下是几种常用的方法来实现千分化:

方法一:使用 toLocaleString

toLocaleString 方法是最简单和推荐的方法,因为它不仅支持千分位,还支持根据不同的区域设置进行格式化。

const number = 1234567.89;
const formattedNumber = number.toLocaleString();
console.log(formattedNumber); // 输出: "1,234,567.89"(根据区域设置可能不同)

你可以指定区域设置和格式选项:

const number = 1234567.89;
const formattedNumber = number.toLocaleString('en-US'); // 美式英语
console.log(formattedNumber); // 输出: "1,234,567.89"

const formattedNumberDE = number.toLocaleString('de-DE'); // 德语
console.log(formattedNumberDE); // 输出: "1.234.567,89"

方法二:正则表达式

如果你想自己实现千分化,可以使用正则表达式进行字符串替换:

function formatNumberWithCommas(number) {
  return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}

const number = 1234567.89;
const formattedNumber = formatNumberWithCommas(number);
console.log(formattedNumber); // 输出: "1,234,567.89"

方法三:自定义函数

你也可以编写自定义函数来实现千分化,适用于更多自定义需求:

function formatNumber(number, separator = ',') {
  const [integerPart, decimalPart] = number.toString().split('.');
  const formattedIntegerPart = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
  return decimalPart ? `${formattedIntegerPart}.${decimalPart}` : formattedIntegerPart;
}

const number = 1234567.89;
const formattedNumber = formatNumber(number);
console.log(formattedNumber); // 输出: "1,234,567.89"

方法四:国际化 API(Intl.NumberFormat

Intl.NumberFormat 是现代浏览器中提供的国际化 API,能够更灵活地进行数字格式化:

const number = 1234567.89;
const formatter = new Intl.NumberFormat('en-US', {
  style: 'decimal',
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
});

const formattedNumber = formatter.format(number);
console.log(formattedNumber); // 输出: "1,234,567.89"

你可以根据需要自定义格式化选项:

const number = 1234567.89;
const formatter = new Intl.NumberFormat('en-US', {
  style: 'decimal',
  useGrouping: true,
  minimumFractionDigits: 0,
  maximumFractionDigits: 2
});

const formattedNumber = formatter.format(number);
console.log(formattedNumber); // 输出: "1,234,567.89"

总结

选择合适的方法可以根据具体的需求和浏览器支持情况。

为跨国客户检测网页端的报错需要一个全面的前端监控和日志收集系统。以下是一些常用的策略和工具,可以帮助你实现这一目标:

使用第三方前端监控工具

使用专业的前端监控和错误跟踪工具是最简单且有效的方式。这些工具通常提供详细的错误报告、用户会话回放、性能监控等功能,并且支持全球分布的用户。

常用工具:

  1. Sentry

    // 安装 Sentry
    npm install @sentry/browser
    
    // 初始化 Sentry
    import * as Sentry from '@sentry/browser';
    Sentry.init({ dsn: 'YOUR_DSN_HERE' });
    
    // 捕获错误
    Sentry.captureException(new Error('Something went wrong'));
    
  2. LogRocket

    // 安装 LogRocket
    npm install logrocket
    
    // 初始化 LogRocket
    import LogRocket from 'logrocket';
    LogRocket.init('YOUR_APP_ID');
    
    // 捕获错误
    LogRocket.captureException(new Error('Something went wrong'));
    
  3. New Relic

  4. TrackJS

    // 安装 TrackJS
    npm install trackjs
    
    // 初始化 TrackJS
    import TrackJS from 'trackjs';
    TrackJS.install({ token: 'YOUR_TRACKJS_TOKEN' });
    
    // 捕获错误
    TrackJS.track(new Error('Something went wrong'));
    

自定义前端监控

如果你希望实现更定制化的监控解决方案,可以自行开发前端错误捕获和上报机制。

错误捕获与上报

  1. 全局错误捕获

    window.onerror = function(message, source, lineno, colno, error) {
      console.error(`Error: ${message}, Source: ${source}, Line: ${lineno}, Column: ${colno}, Error object: ${error}`);
      sendErrorToServer({ message, source, lineno, colno, error });
      return false; // 阻止浏览器默认错误提示
    };
    
    window.addEventListener('unhandledrejection', function(event) {
      console.error('Unhandled rejection:', event.reason);
      sendErrorToServer({ message: event.reason });
    });
    
  2. 捕获特定区域的错误

    try {
      // 可能会抛出错误的代码
    } catch (error) {
      console.error('Caught an error:', error);
      sendErrorToServer(error);
    }
    
  3. 自定义日志上报函数

    function sendErrorToServer(error) {
      fetch('https://your-server.com/log', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          message: error.message,
          stack: error.stack,
          userAgent: navigator.userAgent,
          url: window.location.href,
          timestamp: new Date().toISOString()
        })
      }).catch(console.error);
    }
    

捕获性能数据

除了捕获错误,监控性能数据也很重要,尤其是对于跨国用户,可以通过 Performance API 捕获性能数据:

window.addEventListener('load', function() {
  const performanceTiming = window.performance.timing;
  const loadTime = performanceTiming.loadEventEnd - performanceTiming.navigationStart;
  sendPerformanceDataToServer(loadTime);
});

function sendPerformanceDataToServer(loadTime) {
  fetch('https://your-server.com/performance', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      loadTime,
      userAgent: navigator.userAgent,
      url: window.location.href,
      timestamp: new Date().toISOString()
    })
  }).catch(console.error);
}

分析和处理日志

无论你使用第三方工具还是自定义方案,日志的分析和处理都至关重要。以下是一些分析和处理日志的策略:

  1. 数据存储:将错误日志存储在数据库中,以便后续分析和查询。
  2. 报警系统:设置报警机制,当错误频率达到某个阈值时,自动发送警报邮件或通知。
  3. 定期报告:生成定期的错误和性能报告,分析趋势和关键问题。
  4. 错误重现:利用用户会话重放功能,重现用户遇到的问题,帮助定位和修复错误。

通过结合以上策略和工具,你可以构建一个全面的前端监控系统,有效地检测和处理跨国客户的网页端错误,提高应用的稳定性和用户体验。

在 JavaScript 中,Promise 是一种用于处理异步操作的机制。Promise 对象的 then 方法和 catch 方法都可以用于处理异步操作中的错误,但它们的用法和行为有所不同。具体来说,then 的第二个参数和 catch 方法在处理错误时有一些关键区别。

then 方法

then 方法用于在 Promise 成功和失败时分别执行不同的回调函数。它接受两个参数:

  1. 第一个参数:一个回调函数,在 Promise 成功(resolved)时执行。
  2. 第二个参数:一个回调函数,在 Promise 失败(rejected)时执行。
const promise = new Promise((resolve, reject) => {
  // 异步操作
  if (/* 成功条件 */) {
    resolve('成功的结果');
  } else {
    reject('失败的原因');
  }
});

promise.then(
  (result) => {
    console.log('成功:', result);
  },
  (error) => {
    console.log('失败:', error);
  }
);

catch 方法

catch 方法用于处理 Promise 失败(rejected)时的情况。它实际上是 then 方法的一个简化版本,专门用于处理 Promise 的失败情况。

const promise = new Promise((resolve, reject) => {
  // 异步操作
  if (/* 成功条件 */) {
    resolve('成功的结果');
  } else {
    reject('失败的原因');
  }
});

promise
  .then((result) => {
    console.log('成功:', result);
  })
  .catch((error) => {
    console.log('失败:', error);
  });

关键区别

  1. 语义上的不同

  2. 链式调用的行为

  3. 代码可读性

示例对比

使用 then 的第二个参数

const promise = new Promise((resolve, reject) => {
  // 异步操作
  reject('失败的原因');
});

promise.then(
  (result) => {
    console.log('成功:', result);
  },
  (error) => {
    console.log('失败:', error);
  }
).then(() => {
  console.log('后续操作');
});

使用 catch 方法

const promise = new Promise((resolve, reject) => {
  // 异步操作
  reject('失败的原因');
});

promise
  .then((result) => {
    console.log('成功:', result);
  })
  .catch((error) => {
    console.log('失败:', error);
  })
  .then(() => {
    console.log('后续操作');
  });

在这个例子中,使用 catch 方法使错误处理逻辑更加清晰,同时保证后续操作仍然会执行。推荐在处理 Promise 的错误时使用 catch 方法,这样代码更具可读性和维护性。

数组拍平

方法 1:使用 Array.prototype.flat()

从ES2019(ES10)开始,JavaScript引入了flat()方法,它可以拍平数组。默认情况下,它会拍平一层嵌套数组,但你可以通过传递深度参数来控制拍平的层数。

javascript复制代码let arr = [1, [2, [3, [4, 5]]]];
let flatArr = arr.flat(Infinity); // 传递 Infinity 来拍平所有嵌套数组
console.log(flatArr); // 输出: [1, 2, 3, 4, 5]

方法 2:使用递归

const flatten = function(arr){
  while(arr.some(v=>Array.isArray(v))){
    arr = [].concat(...arr);
  }
  return arr;
}
let arr = [1,2,[null,undefined],3,4,[4,5,[1,2,3],6]];
console.log(flatten(arr));

方法 3:使用 reduce() 和递归

reduce() 方法也可以用来拍平数组。

function flattenArray(arr) {
    return arr.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenArray(val)) : acc.concat(val), []);
}

let arr = [1, [2, [3, [4, 5]]]];
let flatArr = flattenArray(arr);
console.log(flatArr); // 输出: [1, 2, 3, 4, 5]

Nginx如何设置CORS

在Nginx中实现跨域(CORS,Cross-Origin Resource Sharing)需要配置服务器来允许指定的跨域请求。以下是一个详细的步骤和配置示例:

步骤

  1. 找到并打开 Nginx 配置文件:

  2. 添加 CORS 配置:

示例配置

以下是在 Nginx 配置文件中配置 CORS 的示例:

server {
    listen 80;
    server_name example.com;

    location / {
        # 允许所有来源的跨域请求
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
            add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
            add_header 'Access-Control-Max-Age' 86400;
            add_header 'Content-Type' 'text/plain charset=UTF-8';
            add_header 'Content-Length' 0;
            return 204;
        }

        # 对于其他请求方法,允许跨域访问
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';

        # 实际请求处理
        proxy_pass http://backend_server;
    }
}

详细解释

  1. if ($request_method = 'OPTIONS')

  2. add_header 'Access-Control-Allow-Origin' '*'

  3. add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'

  4. add_header 'Access-Control-Allow-Headers' ...

  5. add_header 'Access-Control-Expose-Headers' ...

  6. add_header 'Access-Control-Max-Age' 86400

  7. add_header 'Content-Type' 'text/plain charset=UTF-8'add_header 'Content-Length' 0

  8. return 204

  9. proxy_pass http://backend_server

重新加载 Nginx 配置

配置完成后,需要重新加载 Nginx 配置使其生效:

sudo nginx -s reload

通过上述配置和步骤,Nginx 可以成功处理跨域请求,确保前端应用能够与后端服务进行跨域通信。如果有更复杂的需求,可以根据具体情况调整配置。

When do they fire?

window.onload

In some browsers it now takes over the role of document.onload and fires when the DOM is ready as well.

document.onload

How well are they supported?

当整个页面,包括样式、图片和其他资源被加载完成时,会触发 window 对象上的 load 事件。可以通过 onload 属性获取此事件。

下面的这个示例正确显示了图片大小,因为 window.onload 会等待所有图片加载完毕:

确实,JavaScript中的this关键字有很多细微之处,它的行为取决于函数的调用方式。以下是一些关于this的重要概念和使用场景,帮助你更好地理解它的行为:

1. 全局上下文中的 this

在全局上下文中(即不在任何函数内部),this指向全局对象。在浏览器中,全局对象是window

console.log(this); // 在浏览器中输出: window

2. 函数上下文中的 this

function myFunction() {
  console.log(this);
}
myFunction(); // 非严格模式下输出: window, 严格模式下输出: undefined
let obj = {
  name: 'Alice',
  greet: function() {
    console.log(this.name);
  }
};
obj.greet(); // 输出: Alice
function Person(name) {
  this.name = name;
}
let person = new Person('Bob');
console.log(person.name); // 输出: Bob

3. 箭头函数中的 this

箭头函数中的this是由其外部(定义时的作用域)决定的,而不是调用时的作用域。

let obj = {
  name: 'Alice',
  greet: () => {
    console.log(this.name);
  }
};
obj.greet(); // 输出: undefined, 因为箭头函数的this指向定义时的作用域,即全局对象

4. callapplybind 方法

这些方法可以显式地设置this的值。

function greet(greeting) {
  console.log(greeting + ', ' + this.name);
}
let person = { name: 'Alice' };
greet.call(person, 'Hello'); // 输出: Hello, Alice
greet.apply(person, ['Hi']); // 输出: Hi, Alice
let boundGreet = greet.bind(person);
boundGreet('Hey'); // 输出: Hey, Alice

5. DOM 事件处理程序中的 this

在事件处理程序中,this指向触发事件的元素。

let button = document.createElement('button');
button.textContent = 'Click me';
button.onclick = function() {
  console.log(this); // 输出: <button> 元素
};
document.body.appendChild(button);

6. ES6 类中的 this

在类的方法中,this指向类的实例。

class Person {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(this.name);
  }
}

let person = new Person('Alice');
person.greet(); // 输出: Alice

理解this在不同上下文中的行为是掌握JavaScript编程的一个重要方面。希望这些示例能帮助你更好地理解和使用this。如果你有更多问题或需要进一步的解释,随时告诉我!

自我介绍

面试官您好,我是***,本科毕业于****,硕士毕业于****信息管理专业。我热爱编程,学习能力强,拥有较强的适应能力,所以我决定从事前端开发的工作。我自学了操作系统和计算机网络,然后也开发了一个yelp-camp的项目,用于分享露营地点。我也使用了react框架来实现了复杂的用户界面和流畅的用户体验。在项目中我用到了包括状态管理、组件化开发以及使用Redux进行全局状态的管理。我也非常希望能加入**,来从事前端开发的工作。

职业规划

在回答职业规划的问题时,你可以从短期、中期和长期三个方面来阐述你的职业目标,同时结合你对前端开发的热情和Shopee的具体情况。以下是一个示例回答:

短期目标(1-2年)

“在短期内,我希望能够迅速融入Shopee的团队,熟悉公司的开发流程和技术栈。我将积极学习和掌握最新的前端技术,提升自己的编码能力和解决问题的能力。同时,我也希望能够参与到一些重要项目中,积累实战经验。”

中期目标(3-5年)

“在中期,我希望能够在团队中担当更多的责任,可能是作为一个小组的技术负责人,带领团队完成一些关键项目。我希望能够在项目管理和团队协作方面有更多的实践和提升,并能够为团队成员提供指导和帮助。此外,我也计划在这个阶段深入研究前端技术的某些领域,如性能优化、用户体验设计等,成为这些方面的专家。”

长期目标(5年以上)

“在长期,我希望能够成为公司的前端技术专家,甚至是架构师,参与到公司的技术决策中,为公司的技术发展方向提供建议。我希望自己能够具备全面的技术视野,能够设计和优化公司的前端架构,提升整体开发效率和产品质量。同时,我也希望自己在行业内有一定的影响力,能够通过分享和交流,推动前端技术的发展。”

结合Shopee的实际情况

“我选择Shopee不仅是因为它在行业内的领导地位,更因为它提供了一个充满挑战和成长机会的平台。我相信在Shopee,我能够不断学习和提升,达成我的职业目标。同时,我也希望通过自己的努力,为Shopee的发展贡献一份力量。”

这样的回答既展示了你的职业规划的清晰性和可行性,又表明了你对Shopee的认可和期待,同时也体现了你的职业抱负和对前端开发的热情。

为什么加入

在回答这个问题时,你可以结合Shopee的企业文化、发展前景以及你的个人职业目标,来展示你对公司的了解和兴趣。以下是一些参考答案:

  1. 公司背景和文化
  2. 个人发展和职业目标
  3. 公司对员工的重视
  4. 具体项目或成就
  5. 行业前景

综合以上内容,你可以根据自己的实际情况和感受,进行适当的调整和补充,确保回答既体现出你的个人优势,又能展示你对Shopee的认同和期待。

询问HR

在HR面试的最后,通常HR会问你是否有任何问题。提出一些有深度且与公司文化、团队动态和你的职业发展相关的问题,可以展示你的兴趣和主动性。以下是一些你可以考虑的问题:

  1. 公司文化和价值观

  2. 团队和项目

  3. 职业发展和培训机会

  4. 工作环境和氛围

  5. 对新员工的期望

  6. 公司未来的发展

  7. 员工福利和支持

  8. 绩效评估和反馈机制

这些问题不仅能够帮助你更好地了解Shopee的工作环境和公司文化,还能展示你对公司的兴趣和认真态度。选择你最关心的问题,并根据HR的回答,继续深入交流,这样的互动也会让HR对你有更深刻的印象。

你最成功的一件事?

我最成功的一件事是我自主开发了一个功能丰富的网页应用,这个项目让我感到非常有成就感。

整个项目开始于我对露营的热爱和分享露营地点的需求。我从零开始,先是研究了一些前端和后端的基础知识,然后跟着教程一步一步地实现了这个网页应用。

首先,我设计了一个用户注册和登录系统,确保用户可以安全地创建和管理自己的账户。然后,我开发了一个分享露营地点的功能,用户可以发布他们喜欢的露营地点,并为这些地点添加详细的信息和照片。

接下来,我整合了一个交互式的地图功能,用户可以在地图上查看所有的露营地点,地图上标注了这些地点的位置,让大家一目了然。这一部分让我花了不少时间,但最终实现了一个非常直观和友好的界面。

此外,我还实现了用户评分和评论的功能。用户可以为每个露营地点评分,并撰写评论,分享他们的露营体验和建议。这不仅丰富了网页的内容,还为其他露营爱好者提供了宝贵的参考信息。

整个开发过程不仅提升了我的编程技能,还让我学会了如何从用户的角度出发,设计和优化一个实用且有趣的应用。最终,我看到这个网页被大家使用和喜爱,我感到无比的成就和满足。这次经历也让我对未来的开发工作充满了信心和期待。

遇到的困难?

在开发过程中,我遇到了一个比较棘手的问题:登录跳转功能没有按预期工作。当用户登录后,系统并不会返回他们之前想访问的页面,而是跳转回了主页。这种用户体验非常不流畅,显然需要改进。

为了弄清楚问题的原因,我开始查阅相关资料和文档,经过一番研究,我发现问题出在登录后req.session里的数据会被清空。这样一来,原本存储的用户想要访问的页面信息也就丢失了,自然无法实现预期的跳转效果。

为了解决这个问题,我决定编写一个中间件来保存这些重要信息。我编写了一个中间件,将用户在登录前的目标页面信息保存在locals对象里,这样在用户成功登录后,可以从locals中读取这些信息,并实现正确的跳转。

整个过程并不是一帆风顺的,我遇到了许多小问题和挑战,但最终,我成功地实现了这一功能。这个经历不仅提高了我解决问题的能力,也让我学会了如何在面对困难时坚持不懈,不轻易放弃。

这个过程还让我认识到,在开发中遇到问题是常态,关键是要有耐心和毅力,不断尝试各种方法,最终总能找到解决方案。这次经验让我对未来的开发工作充满信心,也更加坚定了我继续深耕技术领域的决心。

用三个词形容自己

适应能力强(Adaptable)自我激励 团队合作

有没有其他offer

目前我还没有其他offer,不过我正在积极面试几家公司。我对贵公司的职位非常感兴趣,因为它非常符合我的技能和职业目标。

你的缺点?

缺点:过于注重细节(Detail-oriented to a fault):

“我有时会过于注重细节,花太多时间确保每一个小方面都完美。这虽然帮助我避免了错误,但有时也会影响我的整体进度。” “为了克服这一点,我学会了设定优先级,确保在关注细节的同时,不忽视大局。现在,我会更频繁地检查自己的进度,并确保在项目截止日期前完成关键任务。”