一些前端面经

跨域问题

跨域是如何形成的?

当我们请求一个url的协议、域名、端口三者之间任意一个与当前页面url的协议、域名、端口不同这种现象我们把它称之为跨域。

浏览器在解析JavaScript出于安全方面的考虑,只允许在同域名下页面进行相互资源请求调用,不允许调用其他域名下的页面的对象;简单的理解就是因为JavaScript同源策略的限制。

注意:跨域并不是请求发不出去,请求能发出去,服务器能收到请求并正常返回结果,只是结果被浏览器拦截了,所以页面无法正常使用数据。

跨域会导致:

  1. 无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
  2. 无法接触非同源网页的 DOM
  3. 无法向非同源地址发送 AJAX 请求(可以发送,但浏览器会拒绝接受响应)

导致跨域的根本原因是请求浏览器的同源策略导致的 ,而跨域请求报错的原因是: 浏览器同源策略 && 请求是ajax类型 && 请求确实跨域了。

页面发起跨域请求后,浏览器会先发起预检请求,预检通过后,在发起正式请求。如果预检请求不过,浏览器就会停止后面的业务请求,导致访问失败。

解决方法: jsonp,cors,代理转发
点击查看更多

  1. jsonp
    利用<script src="xxx">元素的这个天然支持跨域的策略,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。一般是在请求的url中添加一个回调函数参数,服务器的响应将被包裹在这个参数中,从而实现跨域读取数据。

    <script type="text/javascript">
        function handleResponse(data){
            alert(data.msg);
        }
    </script>
    <script type="text/javascript" src="http://crossdomain.com/jsonServerResponse?callback=handleResponse">
    

    服务端支持的代码:服务端获取回调函数的名称,然后将数据包裹在回调函数中作为响应返回给客户端。

    const express = require('express');
    const app = express();
    
    app.get('/data', (req, res) => {
        const data = { message: 'Hello, World!' };
        const callbackName = req.query.callback; // 获取回调函数名称
    
        // 返回数据,并将数据包裹在回调函数中
        res.send(`${callbackName}(${JSON.stringify(data)})`);
    });
    
    app.listen(80, () => {
        console.log('Server started');
    });
    
  2. CORS(Cross-Origin Resource Sharing)

    实现CORS通信的关键是服务器,需要在服务器端做一些小小的改造。
    只要服务器实现了CORS接口,就可以跨源通信。
    在响应头上添加Access-Control-Allow-Origin属性,指定同源策略的地址。同源策略默认地址是网页的本身。只要浏览器检测到响应头带上了CORS,并且允许的源包括了本网站,那么就不会拦截请求响应。

    用Express框架,配置了一个中间件来处理CORS。设置了允许跨域的源http://www.example.com

    const express = require('express');
    const app = express();
    
    // 配置CORS中间件
    app.use(function(req, res, next) {
        // 允许特定域的跨域访问
        res.header('Access-Control-Allow-Origin', 'http://www.example.com');
        // 允许发送跨域请求的HTTP方法
        res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
        // 允许的请求头
        res.header('Access-Control-Allow-Headers', 'Content-Type');
        // 是否允许发送Cookie
        res.header('Access-Control-Allow-Credentials', 'true');
        next();
    });
    
    // 跨域请求处理
    app.get('/data', (req, res) => {
        const data = { message: 'Hello, World!' };
        res.send(data);
    });
    
    app.listen(80, () => {
        console.log('Server started');
    });
    
  3. 代理服务器
    在客户端和服务端中间加一个代理服务器,使得客户端向代理服务器发送请求,再由代理服务器向真正的服务器发送请求,并将结果转发给客户端。

    vue 在 vue.config.js 配置代理服务器

    // vue.config.js
    module.exports = {
        devServer: {
            proxy: {
            '/api': {
                target: 'http://api.example.com', // 目标服务器的URL
                changeOrigin: true,
                pathRewrite: {
                '^/api': '', // 可选,用于重写路径
                },
            },
            },
        },
    };
    

CORS 解决跨域一般会设置哪几个请求头,并说说这些请求体的作用

  1. Origin(请求头):这是浏览器发送的请求头,包含了发起跨域请求的源(Origin)信息。服务器通常会根据这个头来判断是否允许跨域请求。
  2. Access-Control-Allow-Origin(响应头):这是服务器的响应头,用来指定哪些源(Origin)被允许访问资源。可以设置为特定的源,也可以设置为通配符*表示允许所有源访问资源。
  3. Access-Control-Allow-Methods(响应头):用于指定哪些HTTP方法(例如GET、POST、PUT、DELETE等)是允许的。服务器会将允许的方法列在这个响应头中。
  4. Access-Control-Allow-Headers(响应头):用于指定允许的请求头字段。这个头允许服务器在响应中列出允许的请求头,以便浏览器知道哪些自定义请求头字段是允许的。
  5. Access-Control-Allow-Credentials(响应头):如果需要在跨域请求中包含凭据(如Cookies或HTTP认证信息),这个头需要被设置为true。如果不需要使用凭据,可以将其设置为false。
  6. Access-Control-Expose-Headers(响应头):用于指定响应中可以被访问的响应头字段。默认情况下,浏览器只能访问一些简单的响应头字段,如果要让其他字段可访问,需要在这个头中列出。

这些请求头和响应头一起协同工作,以确保跨域请求的安全性和可控性。服务器需要正确设置这些响应头来明确指定哪些跨域请求是被允许的,而浏览器则会根据这些响应头来决定是否允许跨域请求的执行。

需要注意的是,CORS是一个安全机制,只能在浏览器环境中生效。如果是其他环境(如服务器之间的通信),可能需要采用不同的跨域解决方法,如代理或JSONP。

如果设置 access-control-allow-origin: * 会有什么问题吗?

设置 Access-Control-Allow-Origin 头为 * 表示允许所有来源(Origin)的跨域请求访问资源,会有一些潜在的安全问题。

  1. 安全性问题:使用 * 允许任何来源访问资源可能导致潜在的安全漏洞。攻击者可以轻松地伪造请求来访问资源,而不受同源策略的限制。
  2. 敏感信息泄露:如果资源包含敏感信息,如用户个人数据或会话信息,允许所有来源访问可能导致敏感信息泄露的风险。
  3. 缺乏粒度控制:* 无法提供细粒度的控制,无法区分哪些来源是真正可信任的。这可能会导致意外的安全问题,特别是在处理敏感数据时。
  4. 不符合合规性:某些法规和标准(如GDPR)可能要求更严格的跨域策略,以确保对个人数据的合法处理和保护。

浏览器输入url的整个过程

当页面输入url地址之后,首先进行是否有缓存的判断,没有缓存,正常通过向服务器发起请求获取页面资源,有缓存的话进行缓存是否过期等一系列判断,最终将页面资源展示出来。先走缓存,再服务器请求,一切以快速,简捷为目的。

  1. URL 解析:浏览器的核心代码会判断你输入的是一个合法的 URL 还是一个待搜索的关键词,并且根据你输入的内容进行自动完成、字符编码等操作。
  2. DNS 查询IP地址:浏览器会先检查是否在缓存中,没有则调用系统库函数进行查询。到DNS服务器查询对应的IP地址。将IP地址返回给浏览器,浏览器将ip放在协议中发到网络中。比如 www.server.com.(实际上域名最后还有一个点,这个最后的一个点代表根域名)
    • 根 DNS 服务器(.)
    • 顶级域 DNS 服务器(.com)
    • 权威 DNS 服务器(server.com)
  3. TCP 连接
  4. 处理请求
  5. 接受响应:浏览器接收到来自服务器的响应资源后,会对资源进行分析。
  6. 渲染页面

七层模型

  1. 应用层(Application Layer):应用层提供了网络服务和应用程序接口,包括各种网络应用协议,如HTTP、FTP、SMTP等。
  2. 表示层(Presentation Layer):表示层负责数据的格式化、加密和解密,以确保不同系统之间的数据格式一致性。
  3. 会话层(Session Layer):会话层建立、管理和终止会话,以确保数据在不同系统之间的顺序传递。它还处理会话恢复、同步和错误处理。
  4. 传输层(Transport Layer):传输层负责端到端的通信,提供数据可靠性和错误恢复功能。它通常使用端口号来标识不同的应用程序,并可以提供可靠的数据传输机制,如TCP。
  5. 网络层(Network Layer):网络层负责数据的路由和转发,以确保数据从源地址传输到目标地址。它使用路由算法来选择最佳路径,并处理子网划分、逻辑寻址等任务。
  6. 数据链路层(Data Link Layer):数据链路层负责在物理层的基础上,建立直接的点对点连接,以可靠地传输数据帧。它处理错误检测和纠正,以及访问共享介质的问题。
  7. 物理层(Physical Layer):物理层是网络通信的最底层,它负责定义物理介质和数据传输的方式。它关注的是如何在物理介质上传输原始比特流,包括电压、电流、光信号等。

get和post请求的区别

  • 使用上的区别:
    • GET使用URL或Cookie传参,而POST将数据放在BODY中",这个是因为HTTP协议用法的约定。
    • GET方式提交的数据有长度限制,则POST的数据则可以非常大",这个是因为它们使用的操作系统和浏览器设置的不同引起的区别。
    • POST比GET安全,因为数据在地址栏上不可见",这个说法没毛病,但依然不是GET和POST本身的
      区别。
  • 本质区别
    • GET和POST最大的区别主要是GET请求是幂等性的,POST请求不是。这个是它们本质区别。幂等性是指一次和多次请求某一个资源应该具有同样的副作用。简单来说意味着对同一URL的多个请求应该返回同样的结果。

介绍下http缓存

浏览器缓存是浏览器将用户请求过的静态资源存储到电脑本地磁盘中,当再次访问时,就可以直接从本地缓存中加载而不需要去向服务器请求了。但是缓存也有缺点,如果服务端资源更新了,客户端没有强制刷新的情况下,看到的内容还是旧的。所以,前端需要根据项目中各个资源的实际情况,做出合理的缓存策略。这就出现了强缓存和协商缓存。

  • 强缓存
    使用强缓存策略时,如果缓存资源有效,则直接使用缓存资源,不必再向服务器发起请求。

    • 由服务端开启,利用http头的Expires,Cache-Control来控制。

    • Expires:response header里面的过期时间,浏览器再次加载资源时,如果在过期时间内,就命中强缓存。

    • Cache-Control:

      private:仅浏览器可以缓存  
      public:浏览器和代理服务器都可以缓存  
      max-age=xxx:过期时间  
      no-cache:不进行强缓存。可以在客户端存储资源,每次都必须去服务端做新鲜度校验,来决定从服务端获取新的资源(200)还是使用客户端缓存(304)。也就是所谓的协商缓存。
      no-store:不强缓存,也不协商缓存。永远都不要在客户端存储资源,永远都去原始服务器去获取资源。
      
    • 强缓存步骤:

      1,第一次请求文件时,缓存中没有该信息,直接请求服务器。
      2,服务器返回请求的文件,并且http response header中cache-control为max-age=xxx
      3,再次请求该文件时,判断是否过期,如果没有过期,直接读取本地缓存,如果已经过期了,则进行协商缓存。
      
  • 协商缓存
    使用协商缓存时,先向服务器发送请求,如果资源没有变化,就返回304状态,让浏览器用本地的缓存副本。如果资源发生变化,则返回修改后的资源。

    • 需要和服务器交互。

    • 主要依赖响应头的 last-modified 和 Etag。

    • last-modified:资源最后修改时间。

    • Etag:资源不同,就会生成不同的Etag。

      协商缓存:它的触发条件有两点、
          第一点是 Cache-Control 的值为 no-cache,则会促发协商缓存。
          第二点是 max-age 过期了,触发协商缓存。
      

http和https的区别

  1. 安全性不同
    HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输。

  2. 响应速度
    理论上,HTTP响应速度更快,这是因为HTTP只需三次握手,也就是3个包即可建立连接, 而https除了三次握手,还需进行ssl握手,一共需要12个包。

  3. 端口
    HTTP采用的是80端口,HTTPS是443端口。

  4. 消耗资源
    https是构建在SSL之上的http协议,所以https会消耗更多的服务器资源。

  5. 展示方式
    由于http是一种没有加密的协议,各大浏览器厂商开始支持https站点。例如http站点,会被谷歌浏览器标记为“不安全”等等,https站点,则会被各大浏览器加上“绿色安全锁”标记,如果网站配置增强级SSL证书,地址栏还会变为“绿色地址栏。”

为什么需要https

HTTPS(超文本传输安全协议)是HTTP的安全版本,它通过加密和认证机制提供了更安全的数据传输。以下是为什么需要使用HTTPS的主要原因:

  1. 数据加密:HTTPS使用加密算法来保护传输的数据。这意味着在数据传输过程中,即使被拦截,攻击者也无法轻易解读或窃取其中的信息。这对于敏感信息的传输(如信用卡号、密码等)至关重要。
  2. 数据完整性:HTTPS还提供了数据完整性检查,确保数据在传输过程中没有被篡改或损坏。如果数据在传输过程中被篡改,接收方将会检测到并拒绝接受被修改的数据。
  3. 认证与信任:HTTPS使用数字证书来验证服务器的身份。这意味着当您访问一个使用HTTPS的网站时,您可以确信您连接到的是真正的网站,而不是一个冒充的恶意网站。这有助于防止中间人攻击。
  4. SEO 改善:搜索引擎如谷歌已经将HTTPS纳入了其搜索算法中,这意味着使用HTTPS有助于提高您的网站在搜索引擎结果页中的排名。因此,采用HTTPS有助于改善网站的可见性和流量。
  5. 用户信任:现代用户越来越关注在线隐私和安全。提供HTTPS连接可以增加用户对您网站的信任,使他们更有信心与您的网站进行交互。
  6. 合规性:一些法规和行业标准要求在处理敏感数据(如医疗记录、金融信息等)时必须使用HTTPS。不遵守这些法规可能会导致法律问题和罚款。

总之,HTTPS是保护用户数据隐私和安全的重要手段,也是建立信任和提高网站可见性的关键因素。因此,几乎所有涉及用户数据传输或隐私的网站都应该使用HTTPS来保护用户和网站的利益。

http1.2 2.0 3.0 版本区别

HTTP1.0与HTTP1.1

  1. 长连接——HTTP1.0每次请求都会创建一个新的TCP连接,请求完之后就会关闭。HTTP1.1引入了长连接的概念,会在响应头加入connection:keep-alive,这样请求一次之后,客户端和服务器之间的TCP链接不会关闭,再次访问时就可以直接用这条链接。

  2. HOST域——HTTP1.0给每台服务器绑定唯一的IP,请求的URL中没有传主机名(host),但是随着虚拟技术的发展,一台物理服务器上可以存在多个虚拟主机,并且共享一个IP地址。HTTP1.1的请求支持host头域,如果没有host的话会报错。

  3. 节约带宽——HTTP1.0浪费带宽,例如:用户只需要某个对象的一部分,但是服务器会把整个对象发送过来,并且不支持断点续传功能,断连后需要重新下载整个包。HTTP1.1在请求头引入range头域,允许请求资源的某个部分。

  4. HTTP1.1错误码更多。

HTTP1.1与HTTP2.0

  1. 数据压缩——HTTP1.1不支持header数据的压缩,HTTP2.0使用HPACK算法对header的数据进行压缩,在网络上传输速度更快。

  2. 多路复用——HTTP2.0使用了多路复用,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大好几个数量级。

  3. 服务端推送——客户端请求之前就发送数据。在2.0中服务器可以对客户端的一个请求发送多个响应。

状态码都有哪些

  • 1xx消息:请求已被接受,需要继续处理。临时响应。
    • 100 告知客户部分响应已被服务器接收,客户端应继续发送请求。
  • 2xx成功:服务器已经成功接受请求。
    • 200(OK)请求成功,返回想要的数据。
    • 201(created)已创建,请求成功且服务器创建了新资源。
    • 202(accepted)已接受,但尚未处理。
    • 203 非授权信息,服务器接受了请求,但返回来自于第三方。
    • 204 无内容,服务器接收了请求,但是没有返回任何内容。
    • 205 重置内容,同204,但要求请求者重置文档视图。
    • 206 服务器成功处理部分GET请求。
  • 3xx重定向:客户端采取进一步的操作才能完成请求。
    • 301 永久移除,客户端请求的资源被永久移除到新的地方,服务器返回时,会自动将请求转向新的url。
    • 302 临时移除,服务器当前从不同位置响应请求,但请求者应从原有的位置发送请求。
    • 304 未修改,客户端发送带有条件的GET请求,文档并未改变时服务端返回304.
    • 307 临时重定向,请求的资源临时从不同的URL响应请求。
  • 4xx请求错误:客户端看起来发生了错误,妨碍了服务器的处理。
    • 400 错误请求,语义错误,请求无法被服务器理解,或者请求参数有误。
    • 401 未授权,请求需要验证。
    • 403 禁止,服务器拒绝该请求。
    • 404 未找到,找不到请求网页。
  • 5xx服务器错误:代表服务器无法完成明显有效的请求。
    • 500 服务器内部错误,服务器代码报错,无法完成请求。
    • 502 错误网关,服务器作为网关或代理,从上游服务器收到无效响应。
    • 503 服务器不可用,服务器目前无法使用,只是暂时状态。

四次挥手的过程

  • 第一次挥手:客户端向服务端发送连接释放报文(FIN=1,ACK=1) ,主动关闭连接,同时等待服务端的确认。
    • 序列号 seq = u,即客户端上次发送的报文的最后一个字节的序号 + 1
    • 确认号 ack = k,即服务端上次发送的报文的最后一个字节的序号 + 1
  • 第二次挥手: 服务端收到连接释放报文后,立即发出确认报文 (ACK=1),序列号 seq =k,确认号 ack = u + 1。
    • 这时 TCP 连接处于半关闭状态,即客户端到服务端的连接已经释放了,但是服务端到客户端的连接还未释放。这表示客户端已经没有数据发送了,但是服务端可能还要给客户端发送数据。
  • 第三次挥手:服务端向客户端发送连接释放报文 (FIN=1,ACK=1),主动关闭连接,同时等待 A的确认。
    • 序列号 seg = w,即服务端上次发送的报文的最后一个字节的序号 +1。
    • 确认号 ack =u +1,与第二次挥手相同,因为这段时间客户端没有发送数据。
  • 第四次挥手:客户端收到服务端的连接释放报文后,立即发出确认报文 (ACK=1),序列号 seq =u+1,确认号为 ack= w + 1。
    • 此时,客户端就进入了 TIME-WAIT 状态。注意此时客户端到 TCP 连接还没有释放,必须经过2*MSL(最长报文段寿命) 的时间后,才进入 CLOSED 状态。而服务端只要收到客户端发出的确认,就立即进入 CLOSED 状态。可以看到,服务端结束 TCP 连接的时间要比客户端早一些。

进程和线程的区别

  1. 安全性不同
    HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输。
  2. 响应速度
    理论上,HTTP响应速度更快,这是因为HTTP只需三次握手,也就是3个包即可建立连接, 而https除了三次握手,还需进行ssl握手,一共需要12个包。
  3. 端口
    HTTP采用的是80端口,HTTPS是443端口。
  4. 消耗资源
    https是构建在SSL之上的http协议,所以https会消耗更多的服务器资源。
  5. 展示方式
    由于http是一种没有加密的协议,各大浏览器厂商开始支持https站点。例如http站点,会被谷歌浏览器标记为“不安全”等等,https站点,则会被各大浏览器加上“绿色安全锁”标记,如果网站配置增强级SSL证书,地址栏还会变为“绿色地址栏。”

手机同时运行多个程序,这些进程和线程的运行过程

死锁(经典问题)

在两个或者多个并发进程中,如果每个进程持有某种资源而又等待其它进程释放它或它们现在保持着的资源,在未改变这种状态之前都不能向前推进,称这一组进程产生了死锁。通俗的讲就是两个或多个进程无限期的阻塞、相互等待的一种状态。

死锁的四个条件

  • 互斥条件:一个资源一次只能被一个进程使用。
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得资源保持不放。
  • 不剥夺条件:进程获得的资源,在未完全使用完之前,不能强行剥夺。
  • 循环等待条件:若干进程之间形成一种头尾相接的环形等待资源关系。

文件传输的协议有哪些

  • FTP(File Transfer Protocol):FTP是一种用于在网络上传输文件的标准协议。它支持匿名和身份验证访问,允许用户上传和下载文件。

  • SFTP(Secure File Transfer Protocol):SFTP是基于SSH(Secure Shell)的协议,提供了安全的文件传输方式。它使用加密来保护文件传输过程中的数据。

  • SCP(Secure Copy Protocol):SCP也是基于SSH的协议,用于在网络上安全地复制文件。与SFTP不同,SCP只支持文件复制,不支持目录操作。

  • HTTP/HTTPS:虽然HTTP和HTTPS主要用于网页传输,但它们也可以用于文件下载和上传,特别是通过Web服务器提供的文件。

  • TFTP(Trivial File Transfer Protocol):TFTP是一种简单的文件传输协议,通常用于从网络引导设备或配置路由器等嵌入式系统。

  • NFS(Network File System):NFS是一种在Unix和Linux系统之间共享文件的协议。它允许一个计算机将其文件系统共享给其他计算机,使其可以像本地文件一样访问。

  • CIFS/SMB(Common Internet File System/Server Message Block):这是一种用于在Windows和Unix/Linux系统之间共享文件和打印机的协议。它通常用于局域网内的文件共享。

  • WebDAV(Web Distributed Authoring and Versioning):WebDAV是一种用于在Web上管理和编辑文件的协议。它可以用于与远程服务器交互,支持文件的创建、编辑和删除。

  • BitTorrent:BitTorrent是一种P2P(Peer-to-Peer)文件传输协议,用于高速分发大型文件。它允许多个用户共享文件块,从而提高了下载速度。

SMTP(Simple Mail Transfer Protocol):SMTP虽然主要用于电子邮件传输,但也可以用于附件的文件传输,尤其是在发送邮件中包含附件时。

tcp如何实现可靠性传输

三次握手、四次挥手

1. 重传

  • 超时重传
    • 设置一个定时器,如果超过指定时间,没有收到应答报文,就重发数据。
  • 快速重传
    • 不以时间为驱动,以数据为驱动。三次同样的ACK,就会触发重传机制。
  • SACK(选择性确认
    • 在TCP头部加一个SACK字段,可以将已收到的数据的信息发送给发送方。这样发送方就可以知道哪些数据收到了,哪些没收到。
    • 当触发重传机制的时候,就可以只传没有接收到的数据。
  • Duplicate SACK
    • D-SACK,告诉发送方有哪些数据被重复接收了

2. 滑动窗口(接收方决定

窗口:实际上是在操作系统中开辟一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓存区中保留已发送的数据。如果收到确认应答,就可以将数据从缓存中删除。
窗口大小:无需等待确认应答,可以继续发送数据的最大值。

TCP有个字段 Window,窗口大小。接收端告诉发送端自己还有多少缓冲区来接收数据,发送方就可以根据接收端的处理能力,来发送数据,不会导致接收端处理不过来。

通常窗口大小是由接收方来决定的。

3. 流量控制

TCP提供一种机制,让发送方根据接收方的接收能力,来控制发送的数据量。

糊涂窗口综合征:

  • 接收方通告一个小窗口。
  • 发送方发送小数据。

4. 拥塞控制

网络发生拥塞时,TCP会降低发送的数据量。目的是避免发送方的数据填满整个网络
拥塞窗口(cwnd):调节发送方的数据发送量。会根据网络的拥塞程度动态变化。
swnd = min(cwnd, rwnd); —— 发送窗口 = min(拥塞窗口, 接收窗口);
慢启动门限 ssthresh。cwnd < ssthresh 使用慢启动。cwnd >= ssthresh 使用拥塞避免。

  • 慢启动
    • 每当发送方收到一个ACK,拥塞窗口大小 +1。
  • 拥塞避免
    • 每当发送方收到一个ACK,拥塞窗口大小 + 1/cwnd
  • 拥塞发生
    • 网络拥塞时,会发生数据包重传,主要有:超时重传、快速重传。
      • 超时重传的拥塞发生算法:
        • ssthresh = cwnd/2
        • cwnd = 1
      • 快速重传的拥塞发生算法:
        • cwnd = cwnd/2
        • ssthresh = cwnd
        • 进入快速恢复算法
  • 快速恢复
    • cwnd = cwnd + 3
    • 重传丢失的数据包
    • 如果收到重复的ACK, cwnd = cwnd + 1
    • 如果收到新数据的ACK,cwnd = ssthresh(原因:该ACK确认了新的数据,说明之前的数据都已收到,该恢复过程结束了,可以到恢复之前的状态了,再次进入拥塞避免状态)

tcp和udp区别

  1. TCP面向链接,UDP发送数据前不需要建立连接。
  2. TCP是可靠性传输,无差错,不丢失,不重复。UDP是不可靠性传输,尽最大努力交付。
  3. TCP链接是点到点的,UDP支持1对1,1对多,多对多。
  4. TCP面向字节流,把数据看成一串无结构的字节流。UDP面向报文,网络出现拥塞不会使发送速率降低,但是会出现丢包。
  5. TCP首部开销大,20字节。UDP首部开销小,8字节。

udp的应用场景

  • TCP 是面向连接,能保证数据的可靠性交付,因此经常用于:
    • FTP文件传输
    • HTTP / HTTPS
  • UDP 面向无连接,它可以随时发送数据,再加上UDP本身的处理既简单又高效,因此经常用于:
    • 包总量较少的通信,如 DNS 、SNMP等
    • 视频、音频等多媒体通信
    • 广播通信

常见请求头共有哪些?

  • Host:指定服务器的域名或IP地址和端口号,告诉服务器应该响应哪个主机。
  • User-Agent:包含发起请求的用户代理(通常是浏览器)的信息,用于服务器识别客户端的类型和能力。
  • Accept:指定客户端能够接受的内容类型,通常是MIME类型(例如,text/html、application/json)的列表。
  • Accept-Language:指定客户端首选的自然语言,用于服务器选择合适的本地化内容。
  • Accept-Encoding:指定客户端能够接受的内容编码,如gzip、deflate等,以便服务器进行内容压缩。
  • Authorization:用于身份验证的令牌,通常用于访问受保护的资源。
  • Cookie:包含之前服务器发送的cookie,用于跟踪用户的会话状态。
  • Referer:指定了请求的来源页面的URL,用于服务器统计和引用检查。
  • Cache-Control:控制缓存行为,例如no-cache、max-age等,以指导浏览器和代理服务器处理响应的缓存。
  • Connection:指定与连接相关的选项,如keep-alive,用于控制是否保持持久连接。
  • DNT(Do Not Track):通知服务器不要跟踪用户的浏览行为。
  • If-Modified-Since:允许客户端在资源未更改的情况下请求服务器返回304(未修改)响应。
  • Range:请求部分内容,通常用于断点续传或分块下载。
  • X-Requested-With:一些JavaScript库和框架会将此头部设置为XMLHttpRequest,以指示请求是通过AJAX发起的。
  • Origin:用于CORS(跨源资源共享)请求,指示请求的发起源。
  • Content-Type:用于POST请求,指定请求体的媒体类型,如application/json、application/x-www-form-urlencoded等。
  • Content-Length:指定请求体的字节长度。

JS事件循环机制

同步任务 ——> 异步微任务 ——> DOM渲染页面 ——>异步宏任务
每一个宏任务执行完毕之后,都会检查是否存在待执行的微任务,如果有,则执行完所有的微任务之后,再执行下一个宏任务

知道有哪些宏任务与微任务吗

常见的macrotask有:(⼀般由浏览器发起)DOM渲染后触发

  1. script整体代码
  2. setImmediate:node 的⽅法
  3. setTimeout 和 setInterval
  4. requestAnimationFrame
  5. I/O
  6. UI rendering

常见的microtask有:(⼀般由JS⾃⾝创建)DOM渲染前触发

  1. process.nextTick (Node环境中)
  2. Promise callback(例如 promise.then)
  3. Object.observe (基本上已经废弃)
  4. MutationObserver

介绍下es6新语法,挑几个

1. let(变量)和const(常量)

他们都是块级作用域,在同一个代码块中不允许重复声明。使用 let 在全局作用域中声明的变量不会成为 window 对象的属性,var 声明的变量则会(不过,let 声明仍然是在全局作用域中发生的,相应变量会在页面的生命周期内存续,所以使用window访问会为undefined)
```js
var a = 1;
window.a; // 1

     let b = 1;
     window.b; // undefined
 ```
  • var存在变量提升问题(在变量被声明赋值之前读取,会输出变量=undefined);没有作用域,容易造成变量污染。
  • let不存在变量提升,只有被定义之后才能使用;有块级作用域。
  • const不存在变量提升;const定义的常量,无法被重新赋值;当定义const的时候,必须初始化,否则会报错;有块级作用域(如果const的是一个对象 ,对象包含的值是可以被修改的 ,抽象一点说 ,就是对象所指向的地址不能改变 ,里面的成员是可以变)。

2. 模板字符串:

在模板字符串中可以嵌入变量,可以当作普通字符串使用,也可以定义多行字符串。

3. 变量的解构赋值:

按照一定的模式,从数组和对象中提取值,对变量进行复制。

    let [name, pwd, sex] = ['xiao', '456789', 'nv'];
    let user = {
        name: 'zs', age: 20, gender: '男'
    }
    const {username: name, age, gender} = user;

4. 箭头函数:

本质上是一个匿名函数,箭头函数内部的this,永远和箭头函数外部的this保持一致。

    const obj1 = {
        num:22,
        fn1: function() {
            console.log(this);
            let num = 33;
            const obj2 = {
                num: 44,
                fn2: () => {
                    console.log(this.num);
                }
            }
            obj2.fn2();
        }
    }
    // 原因箭头函数没有this,箭头函数的this是继承父执行上下文里面的this ,这里箭头函数的执行上下文是函数fn1(),所以它就继承了fn1()的this,obj1调用的fn1,所以fn1的this指向obj1, 所以obj1.num 为 22。
    obj1.fn1(); // 22
    // fn1: ƒ(), num:22

5. 对象扩展:

属性简写:允许对象中直接写变量,这时属性名为变量名,属性值为变量值

    let name = 'zs'
    let age = 22
    function show() {console.log('我是zs')}
    // ES6
    const user = {name, age, show};
    // 之前的写法
    var user = {name: name, age: age, show: show}; 

6. 展开运算符:

    // 1. 将字符串转成数组
        let str = 'abcd';
        const Arr = [...str];
        console.log(Arr); // ['a','b','c','d']
    // 2. 合并数组
        const arr1 = [1,2,3], arr2 = [3,4,5];
        const arr = [...arr1, ...arr2];
        console.log(arr); // [1,2,3,4,5,6]

7. promise

明明有了promise,为啥还需要async await?

async/await是一种编写异步代码的新方法。在这之前编写异步代码使用的是回调函数和promise。
async/await实际是建立在promise之上的。因此你不能把它和回调函数搭配使用。
async/await可以使异步代码在形式上更接近于同步代码。这就是它最大的价值。

介绍 Symbol 的作用,以及有哪些用途

用来表示独一无二的值。它的主要作用是创建一个不可变且唯一的标识符,通常用于对象属性的命名、防止属性名冲突、创建私有属性和常量等。

  1. 创建唯一属性名:由于每个 Symbol 值都是唯一的,因此可以用来创建对象的属性名,确保属性名不会被意外覆盖或冲突。这对于库和框架的开发特别有用。

    const uniqueKey = Symbol();
    const obj = {};
    obj[uniqueKey] = '这是一个唯一属性';
    console.log(obj[uniqueKey]); // 输出: 这是一个唯一属性
    
  2. 防止属性名冲突:在多人合作开发或使用第三方代码库时,为了避免属性名冲突,可以使用 Symbol 来创建独一无二的属性名,确保不会与其他属性发生冲突。

    const LIBRARY = Symbol();
    const myObj = {
      [LIBRARY]: '这是一个库的属性'
    };
    
  3. 创建私有属性和方法:在 JavaScript 中没有真正的私有属性或方法,但可以使用 Symbol 来模拟私有属性,以减少意外访问和修改。

    const privateData = Symbol('私有数据');
    
    class MyClass {
      constructor() {
        this[privateData] = '这是私有数据';
      }
    
      getPrivateData() {
        return this[privateData];
      }
    }
    
    const instance = new MyClass();
    console.log(instance.getPrivateData()); // 输出: 这是私有数据
    console.log(instance[privateData]); // undefined,无法直接访问私有属性
    
  4. 创建常量:由于每个 Symbol 值都是唯一的,可以将它们用作常量的标识符,以避免被重写。

    const RED = Symbol('红色');
    const GREEN = Symbol('绿色');
    const BLUE = Symbol('蓝色');
    
  5. 使用 Symbol.iterator 进行自定义迭代:Symbol.iterator 是用于自定义对象的迭代行为的标准符号。通过实现对象的 Symbol.iterator 方法,可以使对象可以被 for…of 循环迭代。

    const myIterable = {
      [Symbol.iterator]: function* () {
        yield 1;
        yield 2;
        yield 3;
      }
    };
    
    for (const value of myIterable) {
      console.log(value); // 依次输出 1, 2, 3
    }
    

const声明的常量如果是一个数组,可以插入新元素吗

可以。关键字保护的是常量的引用,而不是其内容。对于数组和对象等复杂数据类型,可以修改其内容,但不能重新分配一个新的数组或对象给常量。

js如何实现继承

在JavaScript中,每个对象都有一个原型对象(prototype)。原型对象就是一个普通的对象,在创建新对象时,可以将该对象作为新对象的原型。原型对象可以包含共享的属性和方法,这些属性和方法可以被新对象继承和访问。对象之间通过原型链(prototype chain)互相关联,形成了一个原型的链条。

当访问对象的属性或方法时,JavaScript会首先在对象本身查找,如果找不到,就会沿着原型链向上查找,直到找到对应的属性或方法,或者到达原型链的顶层(Object.prototype)

继承参考1
继承参考1

  1. 原型链继承

让一个构造函数的原型是另一个类型的实例,那么这个构造函数new出来的实例就具有该实例的属性。
当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

  function Parent1() {
    this.name = 'parent1';
    this.play = [1, 2, 3]
  }
  function Child1() {
    this.type = 'child2';
  }
  Child1.prototype = new Parent1();
  console.log(new Child1());

优点:写法方便简洁,容易理解。

缺点:对象实例共享所有继承的属性和方法。传教子类型实例的时候,不能传递参数,因为这个对象是一次性创建的(没办法定制化)。

  1. 借用构造函数继承

在子类型构造函数的内部调用父类型构造函数;使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上。

  function Parent1(){
    this.name = 'parent1';
  }
 
  Parent1.prototype.getName = function () {
    return this.name;
  }
 
  function Child1(){
    Parent1.call(this);
    this.type = 'child1'
  }
 
  let child = new Child1();
  console.log(child);  // 没问题
  console.log(child.getName());  // 会报错

优点:解决了原型链实现继承的不能传参的问题和父类的原型共享的问题。

缺点:借用构造函数的缺点是方法都在构造函数中定义,因此无法实现函数复用。无法机场父类实例上的方法。

  1. 组合继承(经典继承)

将 原型链 和 借用构造函数 的组合到一块。使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性

  function Parent3 () {
    this.name = 'parent3';
    this.play = [1, 2, 3];
  }
 
  Parent3.prototype.getName = function () {
    return this.name;
  }
  function Child3() {
    // 第二次调用 Parent3()
    Parent3.call(this);
    this.type = 'child3';
  }
 
  // 第一次调用 Parent3()
  Child3.prototype = new Parent3();
  // 手动挂上构造器,指向自己的构造函数
  Child3.prototype.constructor = Child3;
  var s3 = new Child3();
  var s4 = new Child3();
  s3.play.push(4);
  console.log(s3.play, s4.play);  // 不互相影响
  console.log(s3.getName()); // 正常输出'parent3'
  console.log(s4.getName()); // 正常输出'parent3'

优点: 解决了原型链继承和借用构造函数继承造成的影响。

缺点: 无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。

  1. 原型式继承

这里不得不提到的就是 ES5 里面的 Object.create 方法,这个方法接收两个参数:一是用作新对象原型的对象、二是为新对象定义额外属性的对象(可选参数)。

let parent4 = {
  name: "parent4",
  friends: ["p1", "p2", "p3"],
  getName: function() {
    return this.name;
  }
};

let person4 = Object.create(parent4);

优点是:不需要单独创建构造函数。

缺点是:属性中包含的引用值始终会在相关对象间共享,子类实例不能向父类传参

介绍下flex相关的属性, 作用, 可选值

1. 基本属性

1.两个轴:主轴和侧轴;flex默认水平方向为主轴,垂直方向为侧轴,主轴和侧轴分别可以用不同的属性来定义,可以通过flex-direction进行调整。
2.两个点:起点和终点;主轴和侧轴都有起点和终点,用来定义子元素的对齐方式。
3.两个对象:容器和子元素;分别有不同的属性,容器属性来控制子元素的排列方式,会对每一个子元素进行应用。子元素的属性来控制自身大小及位置,不会影响其他的子元素。

2. 父容器属性介绍

  1. flex-direction:设置主轴的方向
    • row 从左到右
    • row-reverse 从右到左
    • column 从上到下
    • column-reverse 从下到上
  2. justify-content:主轴上子元素的排列顺序
    • flex-start 默认值,从头部开始,如果是主轴,就从左到右
    • flex-end 从尾部开始排列
    • center 主轴居中对齐,如果主轴是x轴,就水平对齐
    • space-around 平分剩余空间
    • space-between 先两边贴边,再平分剩余空间
  3. flex-wrap:子元素是否换行
    • nowrap
    • wrap
  4. align-content:侧轴上的子元素的排列方式,把一整行作为一个整体布局,不影响行内元素
    • flex-start 侧轴头部开始排列
    • flex-end 侧轴尾部开始排列
    • center 侧轴中间显示
    • space-around 子项在侧轴平分剩余空间
    • space-between 子项在侧轴先分布在两头,再平分剩余空间
    • stretch 子项元素平分父元素高度
  5. align-item:侧轴上子元素的排列顺序,一行中的每一个元素都应用布局
    • flex-start 从上到下
    • flex-end 从下到上
    • center 水平居中
    • stretch 拉伸,默认值
  6. flex-flow:复合属性,相当于同时设置flex-direction和flex-wrap
    flex-flow:row wrap;相当于flex-direction:row;flex-wrap:wrap;

3. 子元素属性介绍

  1. flex:子项目占用的份数,定义子元素拉伸和缩放比例,复合属性,包括三个flex: flex-grow, flex-shrink, flex-basis;后两个可以省略。
    1. flex-grow:拉伸比例,当子元素在主轴上有剩余空间时,对元素进行拉伸显示。默认为0。
    2. flex-shrink:缩放比例,当子元素在主轴上超限时,对元素进行压缩,默认值1.
    3. flex-basis:定义拉伸或缩放的基础比例。跟直接写宽度一致。flex-basis:40px;
  2. align-self:子项在自己侧轴的排列方式
    如果行内每一个元素都应用的话,就和align-item一样
  3. order:子项的排列顺序,先后顺序
    默认为0,数字越大越靠后,可以为负数

页面渲染的时候如果卡顿了该从哪方面分析原因

  • JavaScript 执行问题:
    • 大量的 JavaScript 代码执行可能导致卡顿。使用浏览器的性能分析工具来检查哪些函数或代码块执行时间过长。
    • 可能存在大量的计算或循环,可以优化算法或将一些操作移至 Web Worker 来减轻主线程的负担。
  • DOM 操作问题:
    • 频繁的 DOM 操作,特别是在大型 DOM 树上,可能导致卡顿。减少 DOM 操作次数,使用文档片段(Document Fragments)等优化技术。
    • 避免强制同步布局(reflow)和重绘(repaint),这些操作通常很昂贵。
  • 网络请求问题:
    • 大量的网络请求或请求资源过大可能导致页面加载缓慢。使用浏览器的网络分析工具来检查请求的性能和延迟。
    • 可以考虑使用资源合并、缓存、延迟加载等策略来改善网络性能。
  • 渲染性能问题:
    • 复杂的 CSS 样式或大型图像可能导致渲染性能下降。检查是否存在不必要的 CSS 样式,优化图像,使用 CSS 硬件加速等方式来改善渲染性能。
    • 避免在动画中使用 position: absolute 或 box-shadow 等会触发复杂渲染的属性。
  • 内存问题:
    • 内存泄漏可能导致页面渲染性能下降。使用浏览器的内存分析工具来检查是否有未释放的对象或循环引用。
    • 优化内存使用,尤其是在长时间运行的单页应用(SPA)中。
  • 事件处理问题:
    • 大量的事件监听器或事件处理函数可能导致事件冒泡和触发的性能问题。优化事件处理,尤其是移除不再需要的事件监听器。

js代码会影响浏览器页面渲染吗

阻塞渲染:当浏览器解析 HTML 和 CSS 时,如果遇到 JavaScript 代码,它会阻塞页面的渲染,直到脚本执行完成。这可能导致页面加载速度变慢,尤其是在包含大量或复杂 JavaScript 代码的情况下。

DOM 操作:JavaScript 可以通过操作 DOM(文档对象模型)来修改页面的结构和内容。频繁的 DOM 操作可能导致页面的重绘(repaint)和回流(reflow),从而影响渲染性能。为了减少性能损失,应尽量避免频繁的 DOM 操作。

引入js代码如何实现异步?

  • async 属性:

    • 使用 async 属性时,浏览器会异步下载 JavaScript 文件,不会阻塞 HTML 的解析和渲染。
    • 一旦脚本下载完成,浏览器会立即执行脚本,不会按照它们在 HTML 中的顺序执行。
    • async 脚本可以并行下载和执行,适用于那些独立的脚本,它们不依赖于其他脚本的执行顺序。
  • defer 属性:

    • 使用 defer 属性时,浏览器会异步下载 JavaScript 文件,但会在 HTML 解析完成之后、DOMContentLoaded 事件触发之前按照它们在 HTML 中的顺序执行。
    • 多个带有 defer 属性的脚本会按照它们在页面中的顺序执行。

js和css代码会引发卡顿吗?

  • 大量循环和计算:如果JavaScript代码包含复杂的循环或计算,且运行时间较长,可能会导致页面卡顿,因为它会阻塞浏览器的主线程。
  • DOM 操作:频繁的DOM操作(例如,添加、删除、修改元素)可能导致页面重新布局和重绘,这可能会引发性能问题。
  • 内存泄漏:如果JavaScript代码中存在内存泄漏问题,对象无法被垃圾回收,最终可能导致页面变慢或崩溃。
  • 大型文件和资源加载:加载大型JavaScript或CSS文件、大量图像或其他资源可能导致页面加载速度变慢,尤其是在网络速度较慢的情况下。
  • 复杂的CSS选择器:复杂的CSS选择器可能会增加渲染时间,尤其是在页面中有大量元素时。
  • 动画和过渡:使用CSS动画和过渡效果可能会占用大量的CPU资源,特别是在复杂的动画效果下。
  • 异步操作:虽然JavaScript通常是单线程的,但异步操作(例如Ajax请求、定时器)也可能在后台运行,如果处理不当,可能导致性能问题。
  • 浏览器兼容性问题:某些JavaScript和CSS特性在不同浏览器中的实现方式不同,可能会导致性能差异或兼容性问题。

如何减少重排和重绘?

  • 避免在 JavaScript 中频繁地修改元素的样式属性。如果需要多次修改样式,最好将这些修改合并成一次操作,以减少重排和重绘的次数。
  • 使用 CSS 类切换元素的样式,而不是直接操作样式属性。通过添加或移除类,可以一次性修改多个属性,从而减少重排的可能性。
  • 当需要创建大量 DOM 元素时,可以使用文档片段(Document Fragments)来在内存中构建元素,然后一次性将其添加到文档中,而不是逐个添加。
  • 对于需要动画效果的元素,使用 CSS 的 transform 属性和 opacity 属性来实现。这不会触发重排,并且通常性能更好。
  • 表格布局(Table Layout)通常会导致重排,尤其是在表格中的内容发生变化时。尽量避免过多使用表格布局。
  • 当dom元素position属性为fixed或者absolute, 可以通过css形变触发动画效果,此时是不会出发reflow的。

如何优化css代码?

口述Promise 并看题说输出

说说 this 指向

构造函数 this 指向

匿名函数和普通函数有什么区别

  1. 箭头函数更简洁
    • 如果没有参数,直接写空括号
    • 如果只有一个参数,括号可以省略
    • 如果多个参数,逗号分开
    • 如果返回值只有一句,可以省略大括号
  2. 箭头函数没有自己的this
    箭头函数不会创建自己的this,只会在自己的作用域的上一层继承this。所以箭头函数的this指向在创建时就确定了。
  3. 箭头函数继承来的this不会改变
  4. call()、apply()、bind()等方法不能改变箭头函数中this的指向
    call()、apply()、bind()方法可以用来动态修改函数执行时的指向,但由于箭头函数的定义时就已经确定且永远不会改变。
  5. 箭头函数不能作为构造函数使用
    还是因为箭头函数没有自己的this,并且this只想不会改变
    构造函数的new都做了什么 
    1. JS内部先生成一个对象
    2. 把函数中的this指向该对象
    3. 执行构造函数中的语句
    4. 最终返回该对象实例
    
    let fun = (name, age) => {
        this.name = name;
        this.age = age;
    }
    let p = new fun('gt', 24); // 报错
    
  6. 箭头函数没有自己的arguments
    在箭头函数中访问arguments实际上获得的是外层局部函数执行环境中的值。
  7. 箭头函数没有prototype

闭包的作用

有权访问另外⼀个函数作⽤域中变量的函数。
闭包是指那些能够访问⾃由变量的函数. ⾃由变量是指在函数中使⽤的,但既不是函数参数也不是函数的局部变量
的变量。 闭包 = 函数 + 函数能够访问的⾃由变量。

  1. 独⽴作⽤域,避免变量污染
  2. 实现缓存计算结果
  3. 库的封装 jQuery

有哪些情况导致内存泄漏

  1. 意外的全局变量
  2. 被遗忘的定时器和回调函数
  3. 事件监听没有移除
  4. 没有清理的DOM 引⽤
  5. ⼦元素存在的内存泄漏
  6. 闭包

常用的数据结构

  • 数组(Array):一种有序的数据集合,可以按索引访问元素。数组通常用于存储和访问一组相似类型的数据。
  • 链表(Linked List):由节点组成的线性数据结构,每个节点包含数据和指向下一个节点的指针。链表用于在插入和删除元素时提供高效性能。
  • 栈(Stack):一种具有后进先出(LIFO)特性的线性数据结构。栈通常用于跟踪函数调用和处理递归算法。
  • 队列(Queue):一种具有先进先出(FIFO)特性的线性数据结构。队列通常用于处理任务调度和广度优先搜索算法。
  • 哈希表(Hash Table):使用哈希函数将键映射到值的数据结构。哈希表提供了快速的数据检索和插入操作。
  • 树(Tree):一种分层的数据结构,包括根节点、子节点和叶节点。二叉树、二叉搜索树和平衡树是树的常见类型。
  • 图(Graph):由节点和边组成的非线性数据结构,用于表示各种关系和网络结构。
  • 堆(Heap):一种特殊的树结构,通常用于实现优先队列。最小堆和最大堆是堆的两种常见类型。
  • 集合(Set):一种存储唯一值的数据结构,用于查找和去重操作。
  • 字典(Dictionary):一种键-值对的数据结构,也称为关联数组或映射。字典用于快速查找和关联数据。
  • 链表表(Hash Map):一种用于处理键值对的数据结构,类似于字典。哈希表通常用于实现字典和集合。
  • 双向链表(Doubly Linked List):类似于链表,但每个节点都有指向前一个节点的指针,可以在双向上遍历。
  • 栈和队列的变种:例如双端队列(Deque)和优先队列,它们在栈和队列的基础上添加了更多的功能。
  • 位图(Bitset):一种用于处理位级数据的数据结构,通常用于高效地表示大量布尔值。
  • Trie(前缀树):用于处理字符串数据的树结构,常用于实现搜索和自动补全功能。

数组的应用场景

element-ui有哪些组件常用

不同的浏览器上element-ui的兼容性问题

  • IE9的弹性布局flex都会失效,凡是在IE9中使用flex的都需要改成float浮动布局或者使用display: inline-block;属性来纠正。
  • IE11不支持ES6的一些写法
    • 安装:npm install --save babel-polyfill;
    • 配置:module.exports = { entry: [“babel-polyfill”, “./src/main.js”] };
    • main.js中配置:import ‘babel-polyfill’ //放在最顶部,确保全面加载。

移动端flutter在不同的设备上适配的操作

前端实现用户登录有哪些阶段

  • 收集用户凭证信息:首先,前端需要提供一个用户界面,以便用户输入登录凭证,通常是用户名和密码。在某些情况下,也可以使用其他身份验证方式,如电子邮件、手机号码、社交媒体帐户等。
  • 验证用户输入:在接收到用户输入后,前端通常会进行一些基本的验证,例如检查用户名和密码是否已输入、是否符合特定的格式要求等。
  • 发送请求到后端:一旦用户提供了凭证信息并通过前端验证,前端会将这些信息封装在一个HTTP请求中,然后将请求发送到后端服务器。
  • 后端验证:后端服务器将接收到的请求进行身份验证。这可能涉及到检查用户名和密码是否匹配、检查用户是否存在于数据库中以及执行其他安全性检查,如防止暴力攻击。
  • 生成令牌(Token):如果用户成功通过验证,后端服务器通常会生成一个身份验证令牌(Token),该令牌将用于将用户标识为已登录用户。这个令牌可以是JWT(JSON Web Token)等形式。
  • 令牌存储:一旦生成令牌,后端通常将令牌存储在后端的数据库或内存中,同时也会将令牌发送回前端。
  • 前端存储令牌:前端通常会将令牌存储在本地,通常使用浏览器的localStorage或cookie。令牌的存储和管理需要谨慎处理,以防止安全问题。
  • 访问受保护的资源:一旦用户已登录并且具有有效的令牌,前端可以使用该令牌来请求受保护的资源,例如用户的个人资料信息、购物车、订单等。
  • 处理令牌过期:令牌通常具有过期时间,因此前端需要监测令牌的过期并及时更新令牌。可以通过自动续签令牌或在令牌过期前重新登录来实现。
  • 退出登录:前端通常还需要提供退出登录的选项,以清除存储的令牌并将用户注销。
  • 处理错误情况:前端需要处理可能出现的错误情况,例如登录失败、令牌过期、服务器错误等,以提供友好的用户体验。
  • 安全性考虑:在整个登录流程中,前端需要考虑安全性问题,如跨站点脚本攻击(XSS)、跨站请求伪造(CSRF)、令牌泄漏等。

这些是前端实现用户登录的一般阶段,但具体的实现细节可能会因应用的需求和使用的身份验证机制而有所不同。在设计和实现登录系统时,安全性应该是首要考虑因素,并且需要遵循最佳实践以保护用户的身份和数据。

sso单点登录

单点登录(Single Sign-On,简称 SSO)是一种身份验证和授权机制,允许用户在多个应用程序或服务之间进行一次登录,并在登录后可以访问所有受信任的应用程序,而无需重新进行身份验证。SSO的主要目标是提高用户体验、降低密码管理的复杂性,并增强应用程序的安全性。

关键概念:

  • 身份提供者(Identity Provider,IdP):这是SSO系统的核心组件,负责认证用户并生成令牌,该令牌用于向受信任的服务提供商(Service Provider,SP)验证用户身份。
  • 受信任的服务提供商(Service Provider,SP):这是需要实现SSO的应用程序或服务。SP依赖于IdP来验证用户身份,并基于验证结果授予或拒绝用户访问。
  • 令牌(Token):在用户成功登录后,IdP生成一个令牌,该令牌包含有关用户身份的信息。这个令牌可以是SAML令牌、JWT(JSON Web Token)或其他格式的令牌。

工作原理:

  1. 用户访问app系统,app系统是需要登录的,但用户现在没有登录。
  2. 跳转到CAS server,即SSO登录系统,以后图中的CAS Server我们统一叫做SSO系统。 SSO系统也没有登录,弹出用户登录页。
  3. 用户填写用户名、密码,SSO系统进行认证后,将登录状态写入SSO的session,浏览器(Browser)中写入SSO域下的Cookie。
  4. SSO系统登录完成后会生成一个ST(Service Ticket),然后跳转到app系统,同时将ST作为参数传递给app系统。
  5. app系统拿到ST后,从后台向SSO发送请求,验证ST是否有效。
  6. 验证通过后,app系统将登录状态写入session并设置app域下的Cookie。

至此,跨域单点登录就完成了。以后我们再访问app系统时,app就是登录的。接下来,我们再看看访问app2系统时的流程。

  1. 用户访问app2系统,app2系统没有登录,跳转到SSO。
  2. 由于SSO已经登录了,不需要重新登录认证。
  3. SSO生成ST,浏览器跳转到app2系统,并将ST作为参数传递给app2。
  4. app2拿到ST,后台访问SSO,验证ST是否有效。
  5. 验证成功后,app2将登录状态写入session,并在app2域下写入Cookie。

利用Node.js koa异步中间件——用户登录验证拦截器

// 用户信息认证中间件
const authMiddleware = async (ctx, next) => {
    const headers = ctx.headers
    const id = ctx.cookies.get('userId')
    const param = ctx.request.query

    // 是否已登录
    if (id !== undefined) {
        return next()
    }

    // 如果是同步请求,并且携带了ticket(是单点登录跳转过来)
    if (!headers['is-xhr'] && param.ticket) {
        await new Promise((resolve, reject) => {
            http.get(`http://www.sso.com/authTicket?ticket=${param.ticket}`, function (data) {
                let str = "";
                data.on("data", function (chunk) {
                    str += chunk; // 监听数据响应,拼接数据片段
                })
                data.on("end", function () {
                    const res = JSON.parse(str)
                    if (res.code === 200) {
                        ctx.cookies.set('userId', 1)
                        ctx.response.status = 302
                        ctx.response.set({
                            Location: `http://${ctx.request.header.host}`
                        })
                        resolve()
                    }
                })
            })
        })
    }

    ctx.response.body = {
        code: 302,
        data: {
            url: `http://www.sso.com?service=${ctx.request.header.host}${ctx.request.url}`
        }
    };
}
app.use(authMiddleware)

CSS动画都有哪些实现

  1. transition过渡动画。transition: property duration timing-function delay;

    • transition-property: 规定设置过渡效果的 css 属性名称
    • transition-duration: 规定完成过渡效果需要多少秒或毫秒
    • transition-timing-function:指定过渡函数, 规定速度效果的速度曲线
    • transition-delay: 指定开始出现的延迟时间
  2. transform 转变动画。可以利用 transform 功能来实现文字或图像的 旋转、缩放、倾斜、移动 这四种类型的变形处理

    • 旋转 rotate,用法: 主要分为2D旋转和3D旋转。transform: rotate(45deg);
    • 缩放 scale,主要分为2D缩放和3D缩放。transform: skew(30deg) ;
    • 倾斜 skew,D移动,transform: translate(45px);
  3. animation 关键帧动画
       @keyframes 规则用于创建动画。在 @keyframes 中规定某项 CSS 样式, 就能创建由当前样式逐渐改为新样式的动画效果 。
       必须定义动画的名称和时长。如果忽略时长, 则动画不会允许, 因为默认值是 0。
       请用百分比来规定变化发生的时间, 或用关键词 “from” 和 “to”, 等同于 0% 和 100% 。

    • animation: name duration timing-function delay iteration-count direction;
    • animation-name 规定需要绑定到选择器的 keyframe 名称
    • animation-duration 规定动画完成一个周期所花费的秒或毫秒。默认是 0。
    • animation-timing-function 规定动画的速度曲线。 默认是 “ease”。
    • animation-delay 规定动画何时开始 。 默认是 0。
    • animation-iteration-count 规定动画被播放的次数 。 默认是 1。
    • animation-direction 规定动画是否在下一周期逆向地播放 。 默认是 “normal”; alternate (轮流)。

重绘和重排,transform是否会触发

transform属性是一种特殊的CSS属性,用于对元素进行2D或3D的变换,例如平移、旋转、缩放等,而不会触发重排。这是因为transform属性只影响元素的绘制,而不改变其布局。因此,当你应用transform变换时,浏览器会执行重绘,但不会执行重排。

事件模型(事件捕获,事件触发、事件冒泡),怎么优化

盒模型-border-box

  • box-sizing:border-box(怪异盒模型)
    它指定了盒子的宽度和高度只包括内容区域、内边距和边框,不包括外边距。 换句话说,边框和内边距的尺寸会从元素的内容区域中减去,使得内容区域的尺寸保持不变。
    width/height = content + padding + border,包含 border和padding。

  • box-sizing:content-box(标准盒模型)
    它指定了盒子的宽度和高度只包括内容区域,不包括内边距、边框和外边距。 content-box 是 CSS 盒子模型的默认值,因此在不指定盒子模型属性时,浏览器会默认使用 content-box 来计算盒子的宽度和高度。
    width/height = content宽高,不包含border和padding;

Promise和async await区别

一、什么是promise,及其作用

Promise是ES6中的一个内置对象,实际是一个构造函数

特点:

  1. 三种状态:pending(进行中)、resolved(已完成)、rejected(已失败)。只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都不能改变这个状态。
  2. 两种状态的转化:其一,从pending(进行中)到resolved(已完成)。其二,从pending(进行中)到rejected(已失败)。只有这两种形式的转变。
  3. Promise构造函数的原型对象上,有then()和catch()等方法,then()第一个参数接收resolved()传来的数据,catch()第一个参数接收rejected()传来的数据

作用:

  1. 通常用来解决异步调用问题
  2. 解决多层回调嵌套的方案
  3. 提高代码可读性、更便于维护

二、promise的使用

function getJSON() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let json = Math.random() * 2
      if (json > 1) {
        resolve(json)
      } else {
        reject(json)
      }
    }, 2000)
  })
}

const makeRequest = () =>
  getJSON()
    .then(data => {
      console.log('data==>', data)
      return 'done'
    })
    .catch(err => {
      console.log('err==>', err)
    })

makeRequest()

三、什么是Async/Await,及其作用

  • async/await是ES8新特性
  • async/await是写异步代码的新方式,以前的方法有回调函数和Promise
  • async/await是基于Promise实现的,它不能用于普通的回调函数
  • async/await与Promise一样,是非阻塞的
  • async/await使得异步代码看起来像同步代码,这正是它的魔力所在

四、Async/Await的使用

function getJSON() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let json = Math.random() * 2
      if (json > 1) {
        resolve(json)
      } else {
        reject(json)
      }
    }, 2000)
  })
}

const makeRequest = async () => {
  const value = await getJSON()
  console.log(value)
  return value
}

makeRequest()

五、Async/await 比 Promise 更优越的表现

  • 简洁干净:使用 async/await 能省去写多少行代码
  • 错误处理:async/wait 能用相同的结构和好用的经典 try/catch 处理同步和异步错误,错误堆栈能指出包含错误的函数。
  • 调试:async/await 的一个极大优势是它更容易调试,使用 async/await 则无需过多箭头函数,并且能像正常的同步调用一样直接跨过 await 调用。

Canvas和svg

  1. 绘制的图片格式不同

    • Canvas 的工具getContext 绘制出来的图形或传入的图片都依赖分辨率,能够以 .png 和 .jpg格式保存存储图像,可以说是位图。
    • SVG 可以在H5中直接绘制,但绘制的是矢量图。
    • 由于位图依赖分辨率,矢量图不依赖分辨率,所以Canvas和SVG的图片格式的不同实际上是他们绘制出来的图片的格式不同造成的。
  2. Canvas不支持事件处理器,SVG支持事件处理器

    • Canvas 绘制的图像 都在Canvas这个画布里面,是Canvas的一部分,不能用js获取已经绘制好的图形元素。Canvas就像动画,每次显示全部的一帧的内容,想改变里面某个元素的位置或者变化需要在下一帧中全部重新显示。
    • 而SVG绘图时,每个图形都是以DOM节点的形式插入到页面中,可以用js或其他方法直接操作
  3. 适用范围不同

    • 由于Canvas 和 SVG 的工作机制不同,
    • Canvas是逐像素进行渲染的,一旦图形绘制完成,就不会继续被浏览器关注。而SVG是通过DOM操作来显示的。
    • SVG适合带有大型渲染区域的应用程序,比如地图。
    • 而Canvas适合有许多对象要被频繁重绘的图形密集型游戏。

大量数据加载的问题,面试官帮助引申,最后引申到虚拟列表

密码强度检查问题

前端图标怎么做的(矢量图svg)

webpack中的loader和plugin的区别是什么?

loader: webpack视一切文件为模块,但webpack原生只能解析js文件,如果想将其他文件也打包的话,就会用到loader。它只专注于转化文件(transform)这一领域,完成压缩,打包,语言编译。所以,Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力

plugin: plugins在整个编译周期都起作用,也是为了扩展webpack的功能,但是 plugin 是作用于webpack本身上的。不仅只局限在打包,资源的加载上,从打包优化和压缩,到重新定义环境变量,功能强大到可以用来处理各种各样的任务。

webpack和vite

vite开发服务器启动速度比webpack快

  • webpack会先打包,然后启动开发服务器,请求服务器时直接给予打包结果
  • 由于vite启动的时候不需要打包 ,也就不需要分析模块依赖,编译,使用原生ES模块导入方式,所以启动速度非常快;
  • vite采用的是按需动态编译的模式,当浏览器请求需要的模块时,再对模块进行编译,这种处理模式极大的缩短了编译时间,当项目越大,文件越多,vite的开发时优势越明显

vite的热更新比webpack快。vite在HRM方面(HMR是指当你对代码进行修改并保存后,webpack对代码重新打包,并将新的模块发送到浏览器端,浏览器通过替换旧的模块,在不刷新浏览器的前提下,就能够对应用进行更新),当改动了一个模块后,vite仅需让浏览器重新请求该模块即可,不像webpack那样需要把该模块的相关依赖模块全部编译一次,效率更高。

由于现代浏览器本身就支持ES Module,会自动向依赖的Module发出请求。vite充分利用了这点,将开发环境下的模块文件,就作为浏览器要执行的文件,而不是像webpack那样进行打包合并。

vite 使用esbuild(Go编写)预构建依赖,比webpack的nodejs,快10-100倍。

选择Vite还是Webpack
你需要根据项目的需求和优先级来做出决策。如果你注重开发体验、追求快速的冷启动和即时模块热重载功能,以及对Vue.js项目有特别的关注,那么Vite是一个非常不错的选择。而如果你需要处理复杂的构建需求、依赖于Webpack丰富的生态系统和高级功能,或者正在进行一个大型项目,那么Webpack可能更适合。

改变原数组的方法有哪些?

为什么不会再计算,计算属性原理是什么?执行顺序呢?

回答场景问题,用户在搜索框中输入内容可以显示类似内容,如何避免用户频繁多次搜索导致多次访问请求?

回答场景问题,在用户点击内容发送请求后,如何实现因为等待时间过长阻止该请求的发送。

数组有哪些方法 => forEach, map, filter分别应用于哪些场景,slice 和 splice

ESLint的规则有哪些(简历里写了,我没明白到底问的是啥 答airbnb啥啥啥的还是变量声明赋值那一套的……)

深拷贝和浅拷贝的区别

  • 浅拷贝:只是拷贝数据的内存地址,而不是在内存中重新创建一个一模一样的对象(数组)
    • 浅拷贝的意思就是,复制了对象(数组)存储在栈内存中的地址,而不是在内存中重新开辟一个新的存储空间用于存储新的对象。也就是两个对象共用一个内容。
  • 深拷贝:在内存中开辟一个新的存储空间,完完全全的拷贝一整个一模一样的对象(数组)
    • 深拷贝不同于浅拷贝的是。不比浅拷贝只会拷贝栈内存中的数据地址。深拷贝会在内存中重新开辟一段新的存储空间。使得两个对象(数组)指向两个不同的堆内存数据。从而实现改变互不影响。

npm 与 yarn 不同

1.yarn的速度快。

  • 并行安装:npm和 yarn 在执行包的安装时,都会执行一系列任务。npm 是按照队列执行每个 package,必须要等到当前 package 安装完成之后,才能继续后面的安装。而yarn 是同步执行所有任务,提高了性能。

  • 离线模式:如果之前已经安装过一个软件包,用yarn再次安装时之间从缓存中获取,就不用像npm那样再从网络下载了。

  • 安装版本统一:为了防止拉取到不同的版本,yarn 有一个锁定文件 记录了被确切安装上的模块的版本号。每次只要新增了一个模块,yarn 就会创建(或更新)yarn.lock 这个文件,每一次拉取同一个项目依赖时,使用的都是一样的模块版本;npm 其实也有办法实现处处使用相同版本的 packages,但需要开发者执行 npm shrinkwrap 命令,通过 shrinkwrap 命令生成 npm-shrinkwrap.json 文件,只有当这个文件存在的时候,packages 版本信息才会被记录和更新。

2.更简洁的输出。

  • npm 的输出信息比较冗长。在执行 npm install 的时候,命令行里会不断地打印出所有被安装上的依赖。相比之下,yarn 简洁太多:默认情况下,结合了 emoji直观且直接地打印出必要的信息,也提供了一些命令供开发者查询额外的安装信息。

3.多注册来源处理。

  • 所有的依赖包,不管他被不同的库间接关联引用多少次,安装这个包时,只会从一个注册来源去装,要么是 npm 要么是 bower, 防止出现混乱不一致。

4.更好的语义化。

  • yarn改变了一些npm命令的名称,比如 yarn add/remove,感觉上比 npm 原本的 install/uninstall 要更清晰。

yarn 当初设计出来是为了解决 npm 的哪些问题?

yarn 作为区别于 npm 的依赖管理工具,诞生之初就是为了解决历史上 npm 的某些不足,比如 npm 缺乏对于依赖的完整性和一致性保障,以及 npm 安装速度过慢的问题等。

那目前还存在哪些问题?

  • 安全性问题:尽管 Yarn 的安全性不断得到改进,但一些安全漏洞仍然可能存在。开发者需要保持更新依赖项以修复潜在的安全问题。

  • 依赖解决策略:Yarn 和 npm 在处理依赖解决时采用了不同的策略,这可能导致某些项目在使用不同工具时出现不同的行为。这可能会引发一些问题,特别是在复杂的依赖关系中。

  • 锁文件问题:Yarn 使用 yarn.lock 文件来锁定依赖项的版本,以确保构建的一致性。然而,有时锁文件可能会导致问题,特别是在合并或解决依赖冲突时。

使用过 pnpm 吗?为什么使用它,它解决了什么问题?

更快速的依赖下载。pnpm平均比npm和yarn快上2~3倍。这一点在依赖的下载上额外明显。

更高效的利用磁盘空间。pnpm 有一个store的概念(是一块存储文件的空间,后面会说到),内部使用"基于内容寻址"的文件系统来存储磁盘上所有的文件,这一套系统的优点是:

不会重复下载依赖。举个例子,你4个项目都依赖了express.js(第三方插件)。如果是npm/yarn的话,express.js就会被安装4个在你的磁盘空间当中。但是pnpm 得益于"基于内容寻址"的文件系统,使用pnpm下载的文件不论被多少项目所依赖,都只会在磁盘中写入一次。后面再有项目使用的时候会采用硬链接的方式去索引那一块磁盘空间。假设从express@2.0升级到了express@3.0。而express@3.0比express@2.0多了20个文件。这个时候pnpm并不会删除express@2.0再去重新下载express@3.0。而是复用express@2.0原本的磁盘内容。再在express@2.0的基础上增加20个文件形成express@3.0。

介绍一下虚拟滚动的原理

  • 计算可见区域: 首先,需要计算出可见区域的尺寸和位置。通常,这是通过监听滚动事件或容器大小变化事件来实现的。一旦知道了可见区域的范围,就可以确定哪些项应该在可见区域内显示。

  • 渲染可见项: 一旦计算出可见区域,只有在这个区域内的项才会被渲染到DOM中。这通常是通过创建一个虚拟的滚动容器,该容器具有与可见区域相同的尺寸,然后将仅在可见区域内的项渲染到虚拟容器中。

  • 项的高度估算: 为了确定要渲染的项,需要知道每个项的高度或大小。如果项的高度是不同的,通常需要提供一个项高度的估算值,以便在滚动时可以计算出可见区域内的项。

  • 滚动事件处理: 当用户滚动页面时,需要处理滚动事件。根据滚动位置,计算出新的可见区域,然后根据新的可见区域来更新虚拟容器中的内容。这可以通过监听滚动事件并重新计算可见项来实现。

你会对高频事件有什么优化吗?

介绍一下防抖和节流

防抖是指在一段连续触发事件后,只有在事件停止触发一定时间后,才执行一次相应的函数。防抖的主要思想是如果连续触发事件,那么只有最后一次触发是有意义的,前面的触发都会被忽略。

使用场景:

  • 输入框搜索建议:当用户在输入框中输入搜索关键词时,可以使用防抖来确保只在用户停止输入一段时间后才触发搜索请求,减少不必要的请求。
  • 窗口大小调整:当窗口大小被调整时,可以使用防抖来确保只在用户停止调整窗口大小一段时间后执行相应的布局或样式计算。
  • 滚动加载:在滚动页面时,可以使用防抖来确保只在用户停止滚动一段时间后加载更多内容,以减轻服务器负担。
  • 按钮点击:在某些情况下,防止用户多次快速点击按钮可以使用防抖来确保只执行一次点击事件。

节流是指在一段连续触发事件的过程中,控制函数的执行频率,确保每隔一定时间执行一次函数。节流的主要思想是在一定时间间隔内只执行一次函数。

使用场景:

  • 滚动事件:在页面滚动时,可以使用节流来确保每隔一定时间执行一次滚动事件处理函数,以减少事件触发频率。
  • 鼠标移动事件:当鼠标移动时,可以使用节流来确保每隔一定时间执行一次鼠标移动事件处理函数,以提高性能。
  • 定时器任务:在一些需要定时执行的任务中,可以使用节流来控制任务的执行频率,例如动画效果。
  • 限制请求频率:在某些情况下,需要限制用户请求的频率,可以使用节流来确保每隔一定时间才允许发送请求,以防止过多的请求。
  • 表单提交:在表单提交时,可以使用节流来确保每隔一定时间才执行提交操作,以防止用户频繁提交表单。

TS

手撕柯里化

BFC

块格式化上下⽂(Block Formatting Context,BFC) 是Web页⾯的可视CSS渲染的⼀部分,是块盒⼦的布局过
程发⽣的区域,也是浮动元素与其他元素交互的区域。

特点

  • 是页⾯上的⼀个独⽴容器,容器⾥⾯的⼦元素不会影响外⾯的元素
  • 垂直⽅向上,⾃上⽽下,与⽂档流排列⽅式⼀致
  • 同⼀ BFC 下的相邻块级元素可能发⽣margin折叠,创建新的 BFC 可以避免外边距折叠
  • BFC区域不会与浮动容器发⽣重叠(两栏布局/float + overflow:hidden)
  • 计算BFC⾼度时,浮动元素参与计算
  • 每个元素的左margin和容器的左border相接触,即,每个元素的外边距盒(margin box)的左边与包含块边框盒
    (border box)的左边相接触(从右向左的格式的话,则相反),即使存在浮动

重绘重排

重绘(Repaint):
重绘是指在不改变页面布局的情况下,更新元素的可见样式(例如颜色、背景、字体等),导致浏览器重新绘制元素的过程。重绘是相对较快的操作,因为它不需要重新计算元素的位置和大小。

触发重绘的原因包括:

  • 修改元素的颜色、背景颜色、文本样式等。
  • 显示或隐藏一个元素,但不改变文档流中的其他元素。
  • 添加或修改一个 CSS 类,导致元素样式变化。

重排(Reflow):
重排是指在改变页面布局的情况下,重新计算元素的位置和大小,然后重新绘制整个页面的过程。重排是一项昂贵的操作,会导致页面重新布局和重新渲染,因此通常比重绘慢得多。

触发重排的原因包括:

  • 修改元素的尺寸(宽度、高度)。
  • 添加或删除元素。
  • 修改文档的结构,例如改变元素的位置。
  • 改变字体大小。
  • 改变浏览器窗口大小。

区别和优化:

  • 性能开销:重绘的性能开销通常比重排小,因为它只涉及重新绘制元素的可见样式。重排涉及重新计算布局和重新渲染整个页面,因此更昂贵。
  • 合并操作:为了减少性能开销,可以尝试合并多个重绘或重排操作,例如使用 CSS 类一次性修改多个样式属性,而不是多次单独修改。
  • 使用动画:在进行动画效果时,尽量使用 CSS3 动画或使用 transform 和 opacity 属性,因为它们不会触发重排,可以更平滑地执行动画。
  • 减少 DOM 操作:减少对 DOM 的频繁操作,可以减少触发重排和重绘的机会。可以考虑使用文档片段(DocumentFragment)来减少 DOM 操作。
  • 使用虚拟DOM:在一些现代前端框架中,如React和Vue.js,使用虚拟DOM可以优化 DOM 操作,减少不必要的重排和重绘。

vue生命周期

  1. beforeCreate:会在实例初始化完成、props 解析之后、data() 和 computed 等选项处理之前立即调用。此时不能获得DOM节点。
  2. created:在这个阶段vue实例已经创建,以下内容已经设置完成:响应式数据、计算属性、方法和侦听器。然而,此时挂载阶段还未开始,因此 $el 属性仍不可用。仍然不能获取DOM元素。
  3. beforeMount:在组件内容被渲染到页面之前自动执行的函数,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。
  4. mounted:在组件被渲染之后自动执行的函数。一般我们的异步请求都写在这里。在这个阶段,数据和DOM都已被渲染出来。
  5. beforeUpdate:数据变化的时候自动执行的函数,此时变化后的数据还未渲染到页面之上。
  6. updated: 数据变化之后自动执行的函数,此时变化后的数据已经渲染到页面之上。
  7. beforeDestroy: 当 Vue 应用被销毁时,自动执行的函数。
  8. destroyed: Vue 应用被销毁后,且 dom 完全销毁之后,自动执行的函数。

绑定事件的时候在哪个生命周期函数中注册

created:如果你希望在组件实例创建之后立即注册事件,可以使用created生命周期函数。在这个阶段,组件的数据已经初始化,但DOM元素可能还没有完全渲染。这适用于非常早期的事件绑定。

mounted:如果你的事件处理需要依赖于已渲染的DOM元素,通常最好在mounted生命周期函数中注册事件。在这个阶段,组件的DOM元素已经渲染完成,可以安全地注册事件。

内存泄漏

内存泄漏是指在程序运行时,分配的内存没有被正确释放,导致内存空间的浪费,最终可能会导致程序崩溃或运行缓慢。

闭包引起的内存泄露

1. 闭包

闭包是一种特殊的JavaScript函数,它可以访问其自身范围外的变量。闭包可以导致内存泄漏,因为它们可以保留对外部变量的引用,即使它们不再需要。

当一个函数创建一个闭包时,它会创建一个新的作用域链,其中包含了该函数的变量和所有外部函数的变量。如果闭包中包含对一个DOM元素或其他大型对象的引用,那么这个对象将一直存在于内存中,即使它不再需要。

例如,下面的代码创建了一个闭包,它保留了对一个DOM元素的引用,即使该元素被删除:

function createClosure() {
  var element = document.getElementById('myElement');
  return function() {
    // do something with element
  };
}
  
var closure = createClosure();

解决这个问题的方法是在不需要的时候手动解除对外部变量的引用。例如,在上面的代码中,可以将闭包修改为:

function createClosure() {
  var element = document.getElementById('myElement');
  return function() {
    // do something with element
    element = null; // remove reference to element
  };
}
  
var closure = createClosure();

2. 意外的全局变量

在JavaScript中,如果没有使用var、let或const关键字声明变量,它将成为全局变量,即使它在函数内部声明。如果创建了一个全局变量,但没有及时释放它,那么它将一直存在于内存中,可能导致内存泄漏。

例如,下面的代码会创建一个全局变量和一个匿名函数,并将该函数分配给一个DOM元素的事件处理程序:

function init() {
  myGlobalVar = 'hello world';
  
  document.getElementById('myButton').addEventListener('click', function() {
    // do something with myGlobalVar
  });

  // 解决方法:不用的时候释放
  myVar = null;
}

在这个例子中,myGlobalVar是一个全局变量,即使在点击按钮后,匿名函数已经执行完毕,myGlobalVar仍然存在于内存中,并可能导致内存泄漏。

解决这个问题的方法是使用var、let或const关键字声明所有的变量,并在不再需要时及时释放它们。

3. 定时器未清除

在使用JavaScript定时器时,如果不及时清除定时器,那么它将继续存在并持有内存。这是因为定时器仍然在等待计时器到达指定的时间,这可能会导致内存泄漏。

例如,下面的代码创建了一个定时器,它每秒钟执行一次:

    var timer = setInterval(function() {
      // do something
    }, 1000);

    // 不用的时候手动清除
    clearInterval(timer);

4. 未释放DOM元素引用

当通过JavaScript操作DOM元素时,如果不适时地释放对这些元素的引用,就会导致内存泄漏。确保在不再需要某个DOM元素时,将其引用置为null,以便垃圾回收器可以释放相关内存。

// 错误的做法,会导致内存泄漏
const element = document.getElementById('myElement');
// 没有释放引用
// ...

// 正确的做法,释放引用
const element = document.getElementById('myElement');
// 不再需要时,将引用置为null
element = null;    

5. 循环引用

当对象之间相互引用,即使它们不再被使用,也不会被垃圾回收。确保在不再需要对象时,解除相互引用。

垃圾回收

垃圾回收程序需要跟踪记录变量是否还需要使用,标记策略主要有两种:标记清理和引用计数。

标记清理(mark-and-sweep)

垃圾回收程序运行时,会标记内存中存储的所有变量。然后,将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉。在此之后,若变量再被加上标记,则被视为待删除变量(因为任何在上下文中的变量都访问不到它们了)。随后,垃圾回收程序做一次内存清理,销毁带标记的所有值并回收内存。

引用计数

对每个值都记录它被引用的次数。当声明变量并赋引用值时,这个值的引用数为1。当值又被赋给另一个变量时,引用数加1。依此,不断累加。当变量被其他值覆盖时,则引用数减1。当引用数为0时,则表示该值不会再被访问了。垃圾回收程序会在下次运行时,释放引用数为0的值,回收内存。

引用计数策略存在循环引用的问题,当两个对象通过各自属性相互引用时,他们的引用数永远不会变成0,导致内存永远无法释放:

// 循环引用问题
let element = document.getElementById("some_element"); 
let myObject = new Object(); 
myObject.element = element; 
element.someObject = myObject;

// 避免循环引用
// 把变量设置为 null 实际上会切断变量与其之前引用值之间的关系。当下次垃圾回收程序运行时,这些值就会被删除,内存也会被回收。
myObject.element = null; 
element.someObject = null;

介绍下vdom相对于原生dom的优势(避免频繁更新, 还有呢?)

  • 性能优化:虚拟DOM可以减少直接操作原生DOM所带来的性能开销。当数据发生变化时,虚拟DOM会在内存中构建一个虚拟DOM树,然后与前一时刻的虚拟DOM树进行比较,找出真正变化的部分,最后只更新变化的部分到实际的DOM中。这种优化可以减少不必要的DOM操作,提高页面渲染性能。

  • 跨平台兼容性:虚拟DOM的概念不限于浏览器环境,也可以用于服务器端渲染(SSR)和原生应用开发,因此可以实现跨平台的组件共享和代码复用。

  • 简化复杂性:虚拟DOM可以将复杂的DOM操作抽象为更简单的JavaScript操作,提高了代码的可读性和可维护性。它允许开发者将关注点从DOM操作转移到应用逻辑。

  • 提高开发效率:通过虚拟DOM,开发者可以更容易地进行组件化开发,将界面拆分成多个独立的组件,并在需要时复用这些组件。这有助于提高开发效率,特别是在大型应用程序中。

  • 可预测性和稳定性:虚拟DOM的比较和更新过程是可控的,它使应用程序的状态更加可预测,减少了由于直接操作DOM而引发的不稳定性和bug。

声明式组件是如何实现状态和视图的分离

data和template分离?

2和3的区别

  1. 根节点不同

    • vue2中必须要有根标签
    • vue3中可以没有根标签,会默认将多个根标签包裹在一个fragement虚拟标签中,有利于减少内存。
  2. 组合式API和选项式API

    • 在vue2中采用选项式API,将数据和函数集中起来处理,将功能点切割了当逻辑复杂的时候不利于代码阅读。
    • 在vue3中采用组合式API,将同一个功能的代码集中起来处理,使得代码更加有序,有利于代码的书写和维护。
  3. 生命周期的变化

    • 创建前:beforeCreate -> 使用setup()
    • 创建后:created -> 使用setup()
    • 挂载前:beforeMount -> onBeforeMount
    • 挂载后:mounted -> onMounted
    • 更新前:beforeUpdate -> onBeforeUpdate
    • 更新后:updated -> onUpdated
    • 销毁前:beforeDestroy -> onBeforeUnmount
    • 销毁后:destroyed -> onUnmounted
    • 异常捕获:errorCaptured -> onErrorCaptured
    • 被激活:onActivated 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行。
    • 切换:onDeactivated 比如从 A 组件,切换到 B 组件,A 组件消失时执行
  4. v-if和v-for的优先级

    • 在vue2中v-for的优先级高于v-if,可以放在一起使用,但是不建议这么做,会带来性能上的浪费
    • 在vue3中v-if的优先级高于v-for,一起使用会报错。可以通过在外部添加一个标签,将v-for移到外层
  5. diff算法不同

    • vue2中的diff算法
      遍历每一个虚拟节点,进行虚拟节点对比,并返回一个patch对象,用来存储两个节点不同的地方。
      用patch记录的消息去更新dom
      缺点:比较每一个节点,而对于一些不参与更新的元素,进行比较是有点消耗性能的。
      特点:特别要提一下Vue的patch是即时的,并不是打包所有修改最后一起操作DOM,也就是在vue中边记录变更新。(React则是将更新放入队列后集中处理)。

    • vue3中的diff算法
      在初始化的时候会给每一个虚拟节点添加一个patchFlags,是一种优化的标识。
      只会比较patchFlags发生变化的节点,进行识图更新。而对于patchFlags没有变化的元素作静态标记,在渲染的时候直接复用。

  6. 响应式原理不同

    • vue2 通过 Object.definedProperty() 的 get() 和 set() 来做数据劫持、结合和发布订阅者模式来实现,Object.definedProperty() 会遍历每一个属性。
    • vue3 通过 proxy 代理的方式实现。
      proxy 的优势:不需要像Object.definedProperty()的那样遍历每一个属性,有一定的性能提升proxy可以理解为在目标对象之前架设一层“拦截”,外界对该对象的访问都必须通过这一层拦截。这个拦截可以对外界的访问进行过滤和改写。
      当属性过多的时候利用Object.definedProperty()要通过遍历的方式监听每一个属性。利用proxy则不需要遍历,会自动监听所有属性,有利于性能的提升

路由懒加载

也叫延迟加载,即在需要的时候进行加载,随用随载。

使用懒加载的原因: vue是单页面应用,使用webpcak打包后的文件很大,会使进入首页时,加载的资源过多,页面会出现白屏的情况,不利于用户体验。运用懒加载后,就可以按需加载页面,提高用户体验。

const Home = () => import('./Home.vue');
const About = () => import('./About.vue');

// 使用动态导入语句便告诉Webpack按需加载这两个组件,并且将它们打包到两个单独的JavaScript文件中
const Home = () => import(/* webpackChunkName: "home" */ './views/Home.vue')
const About = () => import(/* webpackChunkName: "about" */ './views/About.vue')

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
];

介绍VueX

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

  • State

    提供唯一的公共数据源,所有共享的数据统一放到store的state进行储存,相似与data。

  • Mutation

    更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:

  • Action ——进行异步操作

    Action和Mutation相似,一般不用Mutation 异步操作,若要进行异步操作,使用Action

    原因:为了方便devtools打个快照存下来,方便管理维护。所以说这个只是规范,而不是逻辑的不允许,只是为了让这个工具能够追踪数据变化而已。

  • Getter

    类似于vue中的computed,进行缓存,对于Store中的数据进行加工处理形成新的数据。

  • Modules

    当遇见大型项目时,数据量大,store就会显得很臃肿。

    为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。

vuex, pinia 还有其他?

组合式 API 的优势(Vue3)

  1. 逻辑复用:组合式 API 提供了更灵活的逻辑复用方式。通过将相关的逻辑聚合在一起,可以更好地组织和重用代码。这使得组件更易于阅读、维护和测试,同时也促进了团队合作和代码共享。

  2. 模块化组织:组合式 API 将组件逻辑拆分为可组合的函数,而不再依赖于生命周期钩子函数。这种模块化组织方式使得代码更具可读性和可维护性,同时提供了更好的代码组织结构,使开发者能够更容易地理解和管理组件。

  3. 更自由的 JavaScript 编程:组合式 API 使用普通的 JavaScript 函数来定义组件的逻辑,不再受限于特定的 Vue 实例上下文。这使得开发者能够更自由地使用传统的 JavaScript 工具和模式,例如条件语句、循环和函数调用,从而提高了开发的灵活性和可维护性。

  4. 代码组织和可维护性:通过组合式 API,可以更好地组织和封装组件的逻辑。逻辑相关的代码可以放在同一个函数中,使得代码结构更清晰,易于维护和理解。这种方式也有助于减少代码冗余,提高代码复用性和可测试性。

响应式 API的优点(Vue2)

  1. 简化状态管理:响应式 API 可以轻松地将数据定义为响应式对象,使其能够自动追踪依赖关系,并在数据变化时自动触发重新渲染。这简化了状态管理的过程,减少了手动处理状态变化的工作量,提高了开发效率。

  2. 响应式数据驱动 UI:使用响应式 API,数据的变化会自动反映在相关的 UI 上,无需手动操作 DOM。这使得开发者能够更专注于数据层面的逻辑,而无需过多关注 UI 的变化。同时,Vue 的响应式系统也优化了 DOM 的更新,提供了高效的渲染性能。

  3. 数据与视图分离:响应式 API 可以帮助开发者将数据与视图分离,使代码更具可维护性和可扩展性。通过将数据的定义和操作集中在组件内部,可以更好地组织和管理组件的状态,提高代码的可读性和可维护性。

vue的自定义指令

Vue 虚拟dom以及 diff算法

diff算法就是对虚拟节点进行比较,然后把节点不同的地方存储在一个patch对象中,用patch来局部更新ODM。

两个树完全的diff算法时间复杂度为O(n3),Vue优化成O(n)。因为在前端操作DOM的时候,不会把前元素作为上一级或者下一级元素,很少会跨越层级地移动DOM元素,常见的都是同级的比较。

  1. 构建虚拟DOM树
    当数据发生变化时,会构建一个虚拟DOM树。

  2. 比较虚拟DOM
    将新旧的DOM树进行比较,找出差异。比较只会从同层级进行,从根节点开始,向下逐级比较。
    在这里插入图片描述

  3. 计算差异
    在比较过程中,有以下三种差异。

    • 节点替换:如果节点的标签名不同,就会被认为是不同类型的节点,需要进行替换。
    • 节点更新:如果节点标签名相同,但是属性或者文本内容变了,要更新属性或者文本内容。
    • 节点移动/添加/删除:如果标签名相同但是子节点有变化,尽可能移动/添加或删除,而不是完全重新创建。
  4. 实际DOM更新
    一旦计算出差异,Vue将这些差异应用到实际DOM上,以保持实际DOM与虚拟DOM的同步。这一步通常通过一系列DOM操作来实现,如更新节点属性、插入新节点、移动节点位置、删除不需要的节点等。

Vue.js 2和Vue.js 3 diff算法区别:

  1. 优化的虚拟DOM算法

    • Vue 2使用的是基于全量比较的虚拟DOM算法。每次数据变化时,Vue 2会重新生成整个虚拟DOM树,然后与之前的虚拟DOM树进行比较,找出差异,并更新实际的DOM。这对于小型应用来说效率足够,但对于大型应用或有大量动态数据的情况来说,可能会导致性能问题。
    • Vue 3引入了一种称为“基于代数效应的优化”的虚拟DOM算法。它通过跟踪数据变化和效应依赖关系来优化更新过程。Vue 3将虚拟DOM划分为更小的块,并使用更高效的算法进行差异比较,从而减少不必要的工作。这使得Vue 3在性能方面表现更出色,特别是在大型应用中。
  2. 静态树提升

    • Vue 2中,所有的模板都会被解析成渲染函数,包括那些永远不会改变的部分。这可能导致不必要的性能开销。
    • Vue 3引入了静态树提升(Static Tree Hoisting)的概念。它会将那些永远不会改变的部分提升为静态节点,减少了渲染函数的复杂性,从而提高了性能。
  3. Fragments和Teleport
    Vue 3引入了Fragments(片段)和Teleport(传送门)等新的组件,以更灵活地处理复杂的UI结构。这些功能使得在组件层次结构中进行更精细的控制变得更容易。

  4. Composition API
    Vue 3引入了Composition API,这是一种新的API风格,允许开发者更灵活地组织和重用组件逻辑。这个API使得代码更可维护,并且更容易共享和测试组件逻辑。

  5. 动态组件优化
    Vue 3中对动态组件进行了优化,通过缓存动态组件的模板和实例,避免了多次创建和销毁组件的开销,从而提高了性能。

Vue中计算属性中我写了a+b,如果页面渲染没有涉及这一部分,还会计算吗?

会计算

父子通信,组件间通信

  1. 父传子props

  2. 子传父$emit
    父组件<Son :text="wenzi" @changeFn="changeFn" />
    子组件中this.$emit('changeFn', 传递的参数)

  3. 跨组件event-bus,用法this.bus.$emit('自定义事件名',传递的参数) this.bus.$on('监听的事件名',(e)=>{ e这个形参所接收的就是监听事件所携带的参数数据 })

        // 发送数据
        <button @click="sendGiftFn">送礼物</button>
        sendGiftFn () {
            // 通过 bus 事件总线发起 自定义事件,并且传递参数(第一个是事件名,第二个开始是参数)
            this.bus.$emit('sendMessage', this.gift)
        }
    
        // 接收数据
        created() {
            // e 就是 sendMessage 这个事件所传递的数据
            this.bus.$on("sendMessage", (e) => {
                this.info = e;
            });
        }
    
  4. Vuex

  5. $parent $children

  6. 插槽

  7. ref 父组件调用子组件函数
    父组件

    <base-input ref="usernameInput"></base-input>
    this.$refs.usernameInput.focus()
    

    子组件usernameInput

    <input ref="input">
    methods: {
      // 用来从父级组件聚焦输入框
      focus: function () {
          this.$refs.input.focus()
      }
    }
    
  8. provide和inject

错误捕获 errorCaptured

errorCaptured生命周期钩子函数是Vue组件中的一个钩子函数,用于捕获组件内部的错误。它能够在组件内部捕获并处理错误,还可以向上冒泡并触发父组件的errorCaptured钩子函数。

errorCaptured生命周期的作用如下:

错误处理:通过在组件中定义errorCaptured钩子函数,可以捕获并处理组件内部的错误。在该钩子函数中,可以执行一些逻辑,如记录错误信息、进行错误提示、发送错误报告等。

错误冒泡:当组件的errorCaptured钩子函数捕获到错误时,如果该组件有父组件,则错误会向上冒泡,并触发父组件的errorCaptured钩子函数。这样就可以在父组件中统一处理子组件的错误,或者在组件层级结构中的其他层次上进行错误处理。

比如,当子组件发生错误时,它的errorCaptured钩子函数会被调用,然后错误会向上冒泡,直到遇到父组件的errorCaptured钩子函数。这样可以保证在需要的地方集中处理错误,而不需要在每个组件中都进行错误处理。

需要注意的是,errorCaptured钩子函数只能捕获组件内部的错误,无法捕获组件外部的错误或异步错误。对于异步错误,可以使用全局的错误处理机制,如window.onerror或Promise的catch方法。

总之,errorCaptured生命周期钩子函数的作用是捕获组件内部的错误并处理,同时可以将错误向上冒泡到父组件进行统一处理。它提供了一种在组件层次结构中处理错误的机制,使得错误处理更加灵活和集中。

2和3的响应式原理,体现在开发过程中的变化

Options API: Vue 2 的开发过程主要围绕选项式API,包括 data、computed、watch 等选项,开发者需要将状态和逻辑分散在不同的选项中,有时需要手动管理生命周期钩子。

单一入口: 在Vue 3中,所有的响应式数据都可以在 setup 函数中声明,这包括 ref、reactive、computed 等,使开发者能够更一致地管理状态。

Proxy: Vue 3 引入了 JavaScript 的 Proxy 对象来实现响应式数据。Proxy 允许更细粒度地捕获对象操作,包括属性的访问、修改、添加和删除等。

vue2数组响应式的缺点

  • 无法检测数组的直接变更:Vue 2 无法直接检测到对数组的以下变更:

    • 直接通过索引设置元素的值,例如 vm.items[0] = newValue。

    • 修改数组的长度,例如 vm.items.length = newLength。

      这意味着如果你使用这些方式直接修改数组,Vue 2 将无法触发响应式更新。为了解决这个问题,你需要使用 Vue 提供的特殊方法来触发更新,如 vm.$set 或 Vue.set。

  • 性能问题:在大型数组上进行变更操作时,Vue 2 的响应式系统可能会导致性能下降,因为它需要遍历整个数组来检测变化。

  • 数组方法的变更:Vue 2 覆盖了数组的一些方法(例如 push、pop、shift、unshift、splice、sort 和 reverse)以使它们触发响应式更新。但这也意味着如果你使用自定义的数组方法或使用非覆盖方法(如 concat)来修改数组,Vue 2 将无法检测到变化。

  • 嵌套数组的限制:Vue 2 的响应式系统对于嵌套数组的支持不够灵活,当你修改嵌套数组时,可能需要额外的处理来确保响应式更新正常工作。

  • 不可预测的变更检测:有时,Vue 2 可能无法准确地检测到某些数组变更,导致视图没有正确地更新。

vue2为什么没有实现通过下标修改数组的响应式

因为数组会很大,维护每个下标为响应式的话对性能的影响比较大, 而对象键一般是比较少的。

  1. 性能开销: 直接通过下标修改数组的某个元素可能导致性能问题,特别是在大型数组上。Vue 2 的响应式系统采用了一种优化策略,只拦截了数组的部分方法(如 push、pop、shift、unshift、splice、sort 和 reverse),这些方法是最常用于数组操作的,而且在内部已经被覆盖,以触发响应式更新。

  2. 一致性问题: 如果 Vue 2 允许直接通过下标修改数组的元素,可能会引入一些一致性问题,因为在某些情况下,Vue 不知道数组的哪个元素被修改了,无法进行准确的响应式更新。这可能导致不可预测的行为和性能问题。

为了避免这些问题,Vue 2 采用了一种相对简化的响应式策略,将注意力集中在常见的数组操作方法上,并鼓励开发者使用这些方法来更新数组,以确保响应式更新的一致性和性能。如果你需要通过下标修改数组的元素,并希望触发响应式更新,Vue 2 提供了 $set 方法和 Vue.set 静态方法,它们允许你显式地通知 Vue 哪个元素被修改了。

在 Vue 3 中,通过 Proxy 对象和 Reflect,Vue 引入了更强大和灵活的响应式系统,可以更好地处理通过下标修改数组的情况,同时提供了更多的性能优化。因此,如果你需要更高级的数组响应式处理,Vue 3 可能更适合你的需求。

手撕:冒泡排序

删除有序链表中的重复节点

手撕:实现水平垂直居中(2种方式)

手撕代码,先写ES6类的继承,再用ES5实现

ES6

class Parent {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    console.log('Hello, ' + this.name);
  }
}
class Child extends Parent {
  constructor(name, age) {
    super(name);
    this.age = age;
  }
}
let child = new Child('Alice', 10);
child.sayHello(); // 输出:Hello, Alice

Object.create()方法可以创建一个新对象,并将该对象的原型指向指定的对象。通过这种方式可以实现对象的继承。

let parent = {
  name: 'Parent',
  sayHello: function() {
    console.log('Hello, ' + this.name);
  }
};
let child = Object.create(parent);
child.name = 'Child';
child.sayHello(); // 输出:Hello, Child

ES5

function Parent() {
  this.name = 'Parent';
}
Parent.prototype.sayHello = function() {
  console.log('Hello, ' + this.name);
}
function Child() {
  this.age = 10;
}
Child.prototype = new Parent();
var child = new Child();
child.sayHello(); // 输出:Hello, Parent

super函数的作用是什么?

在 JavaScript 中,super 关键字用于调用父类(超类)的构造函数和方法。它主要有两个作用:

  1. 调用父类构造函数:当你创建一个子类,并且子类需要初始化自己的属性时,可以使用 super 调用父类的构造函数,以确保子类拥有父类的属性和行为。

  2. 调用父类方法:在子类中,你可以使用 super 来调用父类的方法,这对于重写父类方法但仍然需要使用父类方法的情况非常有用。

    class Parent {
      speak() {
        console.log("Parent is speaking");
      }
    }
    
    class Child extends Parent {
      speak() {
        super.speak(); // 调用父类的 speak 方法
        console.log("Child is speaking");
      }
    }
    
    const child = new Child();
    child.speak();
    

Object.create是干什么的?

Object.create() 是 JavaScript 中用于创建一个新对象的方法。这个新对象的原型(Prototype)会指向传入 Object.create() 方法的参数对象。它的主要作用是实现原型继承,允许你创建一个对象,并将其连接到另一个对象,从而实现属性和方法的共享和继承。

基本语法如下:

Object.create(proto, [propertiesObject])

参数说明:

  • proto:要用作新对象原型的对象。
  • propertiesObject(可选):一个可选的对象,用于定义新对象的属性。这个对象的属性描述符将被应用到新对象上。

发红包,m块钱发给n个人,每个人[0.1, m/2]块钱(线性切割)

手撕 数组去重 回文串

手撕 1047. 删除字符串中的所有相邻重复项

手撕 全排列

手写 数组的reduce方法,添加到Array.prototype原型链上

手撕 升序有重复数字的数组的二分查找

括号匹配

写一个简单的节流函数

写了一道简单的斐波那契数列,然后分析一下空间复杂度与时间复杂度

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值