【前端面试】四、JavaScript 21-27

目录

21. 柯里化

22. 渲染实时图表

23. 预加载、懒加载、按需加载

24. ajax请求失败时自动重连

25. 实现私有变量

26. once函数

27. 实现sleep效果

28. js遍历方法


21. 柯里化

柯里化:

柯里化允许将一个多参数的函数转换成一系列使用一个参数的函数链。每个函数都返回下一个待执行的函数,直到所有参数都收集完毕,最后执行原始函数。

主要优点:

  1. 参数复用:通过预先应用一些参数,可以创建接受较少参数的新函数。
  2. 延迟计算/执行:只有在所有必要的参数都准备好之后,才会执行原始函数。
  3. 部分应用:通过提供函数的一部分参数来创建一个新函数,该新函数预置了这些参数。

示例: 

假设我们有一个简单的加法函数 add,它接受两个参数:

function add(a, b) {  
    return a + b;  
}

使用柯里化技术来转换这个函数:

function curryAdd(a) {  
    return function(b) {  
        return a + b;  
    };  
}  
  
// 使用  
const addFive = curryAdd(5);  
console.log(addFive(3)); // 输出 8

通用柯里化函数

接受任意数量的参数,并返回一个新函数,该函数接受剩余的参数:

function curry(fn) {  
    return function curried(...args) {  
        if (args.length >= fn.length) {  
            // 如果提供的参数数量足够,则直接调用原始函数  
            return fn.apply(this, args);  
        } else {  
            // 否则,返回一个新的函数,该函数继续收集剩余参数  
            return function(...args2) {  
                return curried.apply(this, args.concat(args2));  
            };  
        }  
    };  
}  
  
// 使用  
const add = (a, b, c) => a + b + c;  
const curriedAdd = curry(add);  
  
console.log(curriedAdd(1)(2, 3)); // 输出 6  
console.log(curriedAdd(1, 2)(3)); // 输出 6


const curry2 = func => {
    const g = (...allArgs) => allArgs.length >= func.length ? func(...allArgs) : (...args) => g(...allArgs, ...args)
    return g
}

22. 渲染实时图表

http请求动态展示-定时器轮询

function getChart(){
    let request = new XMLHttpRequest();
    request.open('get', 'http://localhost:3333/getChart');
    request.send();
    request.onreadystatechange = () => {
        if(request.readyState == 4 && request.status == 200) {
            let data = JSON.parse(request.responseText);
            option.series[0].data = data.result;
            // 使用echart配置项和数据显示图表
            myChart.setOption(option);
         }
    }
}

getChart();

setInterval(getChart, 2000); // 定时器

websocket双向通信实时数据

    // 连接服务器
    let ws = new WebSocket('ws://127.0.0.1:8090');

    // 连接成功触发
    ws.onopen = () => alert('连接成功');

    // 连接失败触发
    ws.onerror = () => alert('连接失败');

    // 接收消息触发
    ws.onmessage = (res) => {
        let data = JSON.parse(res.data);
        option.series[0].data = data.message;
        // 使用echart配置项和数据显示图表
        myChart.setOption(option);
    }

图表响应式处理

  window.onresize = () => {
      myChart.resize();
  }

23. 预加载、懒加载、按需加载

1.预加载(Preloading)

定义:一种优化技术,允许网页在用户实际请求前提前下载资源(如图片、视频、JS文件等)。这些资源存储在浏览器缓存,以便用户需要时能立即访问,提高页面加载速度和用户体验。

优势

  • 提升性能:通过减少等待时间,提升页面的整体加载速度。
  • 改善用户体验:用户无需等待资源加载即可看到页面内容,特别在需要加载大型媒体文件时。

劣势

  • 增加服务器前端压力:资源被提前下载,服务器面临更多请求,特别是在高流量情况下。
  • 浪费带宽:如果用户最终没有查看到预加载的资源,这些资源的下载就浪费了带宽。

适用场景

  • 重要的、用户很可能会看到的资源,如首屏图片、关键功能所需的JS文件等。
  • 已知用户行为路径的页面,如电商网站的产品详情页。

2.懒加载(Lazy Loading)

定义:一种性能优化技术,它延迟了非关键或屏幕外资源(如图片、视频等)的加载,直到它们即将进入视口或用户需要它们时才开始加载。

优势

  • 减少初始加载时间:通过减少初始加载时下载的资源数量,可以显著提高页面加载速度。
  • 节约带宽:只加载用户实际需要的资源,避免不必要的带宽浪费。
  • 降低服务器压力:减少了初始加载时的请求数,减轻服务器负担。

劣势

  • 可能闪烁或延迟:如果资源在用户需要时才开始加载,可能导致页面内容不一致或延迟显示。

适用场景

  • 页面上大量非关键资源的加载,如图片库、长滚动页面等。
  • 带宽有限或网络条件不佳的环境。

3.按需加载

JavaScript 的按需加载

  • 动态导入(Dynamic Imports)

ES6 引入动态导入(import())语法,允许按需加载模块。这在SPA中特别有用,可以按需加载路由对应的组件。

button.addEventListener('click', async () => {  
  const module = await import('./path/to/your/module.js');  
  const { yourFunction } = module;  
  yourFunction();  
});
  • 使用库如 RequireJS

对于非ES6环境,可以使用RequireJS等库来实现模块的按需加载。

require(['path/to/your/module'], function(module) {  
  module.yourFunction();  
});

图片的按需加载

  • 图片懒加载(Lazy Loading Images)

图片懒加载通常通过监听滚动事件并在图片进入视口时动态设置其src属性来实现。

<img src="placeholder.jpg" data-src="real-image.jpg" alt="Lazy loaded image" class="lazy">  
  
<script>  
  // DOM已完全加载并解析  
  document.addEventListener('DOMContentLoaded', function() {  
    const lazyImages = [].slice.call(document.querySelectorAll('img.lazy'));  
  
    function lazyload() {  
      // 当图片元素与视口交叉时触发加载图片的回调函数
      if ("IntersectionObserver" in window) {  
        let lazyImageObserver = new IntersectionObserver(function(entries, observer) {  
          entries.forEach(function(entry) {  
            if (entry.isIntersecting) {  
              let lazyImage = entry.target;  
              lazyImage.src = lazyImage.dataset.src;  
              lazyImage.classList.remove('lazy');  
              lazyImageObserver.unobserve(lazyImage);  
            }  
          });  
        });  
  
        lazyImages.forEach(function(lazyImage) {  
          lazyImageObserver.observe(lazyImage);  
        });  
      } else {  
        // Fallback for browsers that do NOT support IntersectionObserver  
        lazyImages.forEach(function(lazyImage) {  
          function checkInView() {  
            if (lazyImage.offsetTop < (window.innerHeight + window.pageYOffset)) {  
              lazyImage.src = lazyImage.dataset.src;  
              lazyImage.classList.remove('lazy');  
            }  
          }  
          checkInView();  
          window.addEventListener('scroll', checkInView);  
        });  
      }  
    }  
  
    lazyload();  
  });  
</script>

CSS 的按需加载

CSS 通常是在页面加载时一起加载的,但你可以通过动态插入<link>标签来按需加载CSS。

function loadCSS(url) {  
  var link = document.createElement("link");  
  link.type = "text/css";  
  link.rel = "stylesheet";  
  link.href = url;  
  document.getElementsByTagName("head")[0].appendChild(link);  
}  
  
button.addEventListener('click', function() {  
  loadCSS('path/to/your/stylesheet.css');  
});

HTML 的按需加载

HTML 片段的按需加载通常通过 AJAX 调用后端API来获取HTML内容,然后将其插入到DOM中。

function loadHTML(url, targetId) {  
  fetch(url)  
    .then(response => response.text())  
    .then(html => {  
      document.getElementById(targetId).innerHTML = html;  
    })  
    .catch(error => console.error('Error loading HTML:', error));  
}  
  
button.addEventListener('click', function() {  
  loadHTML('path/to/your/html-fragment.html', 'targetElementId');  
});

24. ajax请求失败时自动重连

 // 需求:实现ajax请求,失败的时候自动重新连接,重试次数可以传递,延迟时间
        const rate = 0.8
        function request(){
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    let res = Math.random();
                    if(res > rate) {
                        resolve(res)
                    }else{
                        reject(res)
                    }
                });
            })
        }
        function retry (func, times = 0, delay = 0){
            return new Promise((resolve, reject) => {
                  // func是一件事,将它封装起来才能在times--调用
                let inner = async function inner() {
                    try {
                        const result = await func();
                        resolve(result) // 成功
                    } catch(e) {
                        console.log(times);
                        if(times-- <= 0){
                            reject(e) // 彻底失败
                        } else{
                            setTimeout(() => {
                                inner(); //再次尝试
                            }, delay);
                        }
                    }
                }   
                inner()
            })
        }
        retry(request, 3, 2000)
        .then(res => {
            console.log(res,'成功');
        })
        .catch(e => {
            console.log(e,'失败');
        })

25. 实现私有变量

使用构造函数和闭包

function Product(name) {  
    var privateName = name;  // 私有变量  
    // 公开方法,可以访问私有变量  
    this.getName = function() {  
        return privateName;  
    };  
    // 注意:这里不暴露设置名字的方法,保持name的私有性  
    // 但如果需要,可以添加一个方法来修改privateName  
}  
  
var obj = new Product('yuxiaoliang');  
console.log(obj.getName()); // 输出: yuxiaoliang  
// console.log(obj.privateName); // 这会抛出错误,因为privateName是私有的  
  
// 如果需要修改私有变量,可以添加一个方法  
Product.prototype.setName = function(newName) {  
    this.getName = function() { // 注意:这实际上改变了getName函数,不是最佳实践  
        return newName;  // 它只是替换了getName的返回值,实际上并没有修改原始的私有变量
    };  
};  

使用 ES6 类和私有字段

从 ES2020 开始,JavaScript 类支持私有字段(使用 # 前缀)。

class Product {  
    #privateName; // 私有字段  
  
    constructor(name) {  
        this.#privateName = name;  
    }  
  
    getName() {  
        return this.#privateName;  
    }  
  
    // 可以添加一个方法来修改私有字段  
    setName(newName) {  
        this.#privateName = newName;  
    }  
}  
  
const obj = new Product('yuxiaoliang');  
console.log(obj.getName()); // 输出: yuxiaoliang  
// console.log(obj.#privateName); // 这会抛出语法错误,因为#privateName是私有的  
obj.setName('new name');  
console.log(obj.getName()); // 输出: new name

26. once函数

传入函数参数只执行一次

function ones(func) {  
    var tag = true;  
    return function() {  
        if (tag) {  
            // 使用 call 来保持 this 上下文,或者传递正确的 this 值  
            // 如果你希望 func 在全局作用域执行,可以传递 null 或 undefined  
            // 但这里我改为使用 this,这样返回的函数就可以像 func 一样被调用  
            func.call(this, ...arguments);  
            tag = false;  
        }  
    };  
}  
  
// 使用示例  
function sayHello() {  
    console.log('Hello, world!');  
}  
  
const onceSayHello = ones(sayHello);  
onceSayHello(); // 输出: Hello, world!  
onceSayHello(); // 无输出,因为 sayHello 只被执行了一次  
  
// 另一个示例,演示 this 上下文  
function greet(greeting) {  
    console.log(greeting + ', ' + this.name);  
}  
  
const person = {  
    name: 'Alice',  
    greetOnce: ones(greet)  
};  
  
person.greetOnce('Hi'); // 输出: Hi, Alice  
person.greetOnce('Hello'); // 无输出

27. 实现sleep效果

//while循环的方式: 容易造成死循环
function sleep(ms){
    var start = Date.now(),
        expire = start + ms;
    while(Date.now() < expire);
    console.log('while循环的方式');
    return;
}

// promise
function sleep(ms){
    return new Promise((resolve) => setTimeout(resolve, ms));
}
sleep(2000).then(() => {console.log('promise')})

// async封装
function sleep(ms){
    return new Promise((resolve) => setTimeout(resolve,ms));
}
async function test(){
    var temple = await sleep(1000);
    console.log('async封装')
    return temple
}
test();

//generate实现
function* sleep(ms){
    yield new Promise((resolve,reject) => setTimeout(resolve, ms))
}
sleep(1000).next().value.then(() => console.log('generate实现'))

28. js遍历方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值