JS进阶篇(前端面试题整合)(二)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: JS进阶篇(前端面试题整合)(二)

Ajax 是什么? 如何创建一个Ajax?


AJAX全称是Asychronous JavaScript And Xml(异步的 JavaScript 和 XML)

它的作用是用来实现客户端与服务器端的异步通信效果,实现页面的局部刷新,早期的浏览器并不能原生支持ajax,可以使用隐藏帧(iframe)方式变相实现异步效果,后来的浏览器提供了对ajax的原生支持

其主要通过XMLHttpRequest(标准浏览器)、ActiveXObject(IE浏览器)对象实现异步通信效果

实现方式(gitee上的案例):

var xhr =null;//创建对象 
if(window.XMLHttpRequest){
  xhr = new XMLHttpRequest();
}else{
  xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
xhr.open(“方式”,”地址”,”标志位”);//初始化请求 
xhr.setRequestHeader(“”,””);//设置http头信息 
xhr.onreadystatechange =function(){}//指定回调函数 
xhr.send();//发送请求 

Ajax的优缺点


优点:


通过异步模式,提升了用户体验

优化了浏览器和服务器之间的传输,按需获取数据,减少不必要的数据往返,减少了带宽占用

Ajax在客户端运行,承担了一部分本来由服务器承担的工作,减少了大用户量下的服务器负载。

缺点:


ajax不支持浏览器back按钮

安全问题 AJAX暴露了与服务器交互的细节

对搜索引擎的支持比较弱

破坏了程序的异常机制。

一个页面从输入 URL 到页面加载显示完成,发生了什么?


当发送一个 URL 请求时,不管这个 URL 是 Web 页面的 URL 还是 Web 页面上每个资源的 URL,浏览器都会开启一个线程来处理这个请求,同时在远程 DNS 服务器上启动一个 DNS 查询。这能使浏览器获得请求对应的 IP 地址。

浏览器与远程 Web 服务器通过 TCP 三次握手协商来建立一个 TCP/IP 连接。该握手包括一个同步报文,一个同步-应答报文和一个应答报文,这三个报文在 浏览器和服务器之间传递。该握手首先由客户端尝试建立起通信,而后服务器应答并接受客户端的请求,最后由客户端发出该请求已经被接受的报文。

一旦 TCP/IP 连接建立,浏览器会通过该连接向远程服务器发送 HTTP 的 GET 请求。远程服务器找到资源并使用 HTTP 响应返回该资源,值为 200 的 HTTP 响应状态表示一个正确的响应。

此时,Web 服务器提供资源服务,客户端开始下载资源。

后续HTML页面解析参照前端面试题整合(JS进阶篇)(一)的 “html页面怎么解析的?它加载顺序是什么?”

JQuery一个对象为何可以同时绑定多个事件


低层实现方式是使用addEventListner或attachEvent兼容不同的浏览器实现事件的绑定,这样可以给同一个对象注册多个事件


对页面某个节点的拖曳


1.    给需要拖拽的节点绑定mousedown, mousemove, mouseup事件

2.    mousedown事件触发后,开始拖拽

3.    mousemove时,需要通过event.clientX和clientY获取拖拽位置,并实时更新位置

4.    mouseup时,拖拽结束

5.    需要注意浏览器边界的情况


gitee上的案例


 

        function mouseMove(ele, parent) {
            ele.addEventListener('mousedown', moveHandler);
            ele.style.position = 'absolute'
            function moveHandler(e) {
                if (e.type === 'mousedown') {
                    parent.ele = this;
                    parent.point = {
                        x: e.offsetX,
                        y: e.offsetY
                    }
                    parent.addEventListener('mousemove', moveHandler);
                    parent.addEventListener('mouseup', moveHandler);
                } else if (e.type === 'mousemove') {
                    this.ele.style.left = e.x - this.point.x + "px";
                    this.ele.style.top = e.y - this.point.y + "px";
                } else if (e.type === 'mouseup') {
                    parent.removeEventListener("mousemove", moveHandler);
                    parent.ele = null;
                    parent.point = null;
                }
            }
        }

new操作符具体干了什么


创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。

属性和方法被加入到 this 引用的对象中。

新创建的对象由 this 所引用,并且最后隐式的返回 this

以下是模拟操作:

new TestObj('str')=function(){
    let obj={};  //创建一个空对象
    obj.__proto__=TestObj.prototype;
    //把该对象的原型指向构造函数的原型对象,就建立起原型了:obj->Animal.prototype->Object.prototype->null
    return TestObj.call(obj,arguments);// 绑定this到实例化的对象上
}

前端开发的优化问题


(1) 减少http请求次数:CSS Sprites, JS、CSS源码压缩、图片大小控制合适;网页Gzip,CDN托管,data缓存 ,图片服务器。

(2) 前端模板 JS+数据,减少由于HTML标签导致的带宽浪费,前端用变量保存AJAX请求结果,每次操作本地变量,不用请求,减少请求次数

(3) 用innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能。

(4) 当需要设置的样式很多时设置className而不是直接操作style。

(5) 少用全局变量、缓存DOM节点查找的结果。减少IO读取操作。

(6) 避免使用CSS Expression(css表达式)又称Dynamic properties(动态属性)。

(7) 图片预加载,将样式表放在顶部,将脚本放在底部  加上时间戳。

(8) 避免在页面的主体布局中使用table,table要等其中的内容完全下载之后才会显示出来,显示比div+css布局慢。


fetch和Ajax有什么不同


XMLHttpRequest 是一个设计粗糙的 API,不符合关注分离(Separation of Concerns)的原则,配置和调用方式非常混乱,而且基于事件的异步模型写起来也没有现代的 Promise,generator/yield,async/await 友好

fetch 是浏览器提供的一个新的 web API,它用来代替 Ajax(XMLHttpRequest),其提供了更优雅的接口,更灵活强大的功能。

Fetch 优点主要有:语法简洁,更加语义化,基于标准 Promise 实现,支持 async/await

如何编写高性能的Javascript


使用 DocumentFragment 优化多次 append

通过模板元素 clone,替代 createElement

使用一次 innerHTML 赋值代替构建 dom 元素

使用 firstChild 和 nextSibling 代替 childNodes 遍历 dom 元素

使用 Array 做为 StringBuffer ,代替字符串拼接的操作

将循环控制量保存到局部变量

顺序无关的遍历时,用 while 替代 for

将条件分支,按可能性顺序从高到低排列

在同一条件子的多( >2 )条件分支时,使用 switch 优于 if

使用三目运算符替代条件分支

需要不断执行的时候,优先考虑使用 setInterval

定时器setInterval有一个有名函数fn,setInterval(fn,500)与setInterval(fn(),500)有什么区别?


第一个是重复执行每500毫秒执行一次,后面一个只执行一次。


简述一下浏览器内核


浏览器内核又可以分成两部分:渲染引擎和 JS 引擎。它负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入 CSS 等),以及计算网页的显示方式,然后会输出至显示器或打印机。JS 引擎则是解析 Javascript 语言,执行 javascript 语言来实现网页的动态效果。


JavaScript的属性元数据有哪些?


writable : true  属性值可修改

enumerable : true  属性可枚举

configurable : true 属性可重新配置

writable : false  属性值不可修改

enumerable : false  属性不可枚举

configurable : false 属性不可重新配置

懒加载(瀑布流)的实现原理


意义:懒加载的主要目的是作为服务器前端优化,减少请求数或延迟请求数实现原理:先加载一部分数据,当触发某个条件时利用异步加载剩余的数据,新得到的数据,不会影响有数据的显示,同时最大程度上减少服务器的资源消耗

实现方式:

(1)延迟加载,使用setTimeOut或setInterval进行加载延迟

(2)符合某些条件,或触发了某些事件才开始异步下载

(3)可视区加载


我的懒加载文章,以及源码地址


js实现数组去重


双层循环,外层循环元素,内层循环时比较值,如果有相同的值则跳过,不相同则push进数组

        class MyArray extends Array {
            constructor() {
                super(...arguments)
            }
            distinct() {
                var myArr = this,
                    list = []
                for (var i = 0; i < myArr.length; i++) {
                    for (var j = i + 1; j < myArr.length; j++) {
                        if (myArr[i] === myArr[j]) {
                            j = ++i;
                        }
                    }
                    list.push(myArr[i]);
                }
                return list;
            }
        }
        var _arr = new MyArray(4, 5, 6, 7, 7, 7, 1, 1, 1, 2, 2, 2, 5, 8, 5, 2, 4, 4, 4, 6, 9);
        console.log(_arr.distinct()); //[7, 1, 8, 5, 2, 4, 6, 9]

利用对象的属性不能相同的特点进行去重

        class MyArray extends Array {
            constructor() {
                super(...arguments)
            }
            distinct() {
                var myArr = this,
                    list = [],
                    obj = {}
                for (var i = 0; i < myArr.length; i++) {
                    obj[myArr[i]] || (obj[myArr[i]] = 1,
                        list.push(myArr[i])) //如果能查找到,证明数组元素重复了
                }
                return list;
            }
        }
        var _arr = new MyArray(4, 5, 6, 7, 7, 7, 1, 1, 1, 2, 2, 2, 5, 8, 5, 2, 4, 4, 4, 6, 9);
        console.log(_arr.distinct()); //[4, 5, 6, 7, 1, 2, 8, 9]


Set数据结构,它类似于数组,其成员的值都是唯一的

        function dedupe(array) {
            return Array.from(new Set(array));
        }
        console.log(dedupe([1, 1, 2, 3])) //[1,2,3]

实现快速排序和冒泡排序


快速排序:选取位置在数组中间的一个数,然后比它小的放在left[]的一个新数组里面,比他大的放在right[]的一个新数组里面,以此类推,重复执行这个过程,利用递归的思想,直至执行到left[]和right[]里面都只有一个数

冒泡排序:两两比较,前面的比后面的大,则换位。第一轮list.length-1次,挑出最大的;第二轮list.length-1-1次,挑出第二大的。以此往复

        class MyArray extends Array {
            constructor() {
                super(...arguments)
            }
            quickSort(list) { //快速排序
                var myArr = this,
                    listConfig = {
                        midItem: myArr[parseInt(myArr.length / 2)],
                        leftList: new MyArray(),
                        rightList: new MyArray()
                    }
                if (myArr.length <= 1) {
                    return myArr
                };
                for (var i = 0; i < myArr.length; i++) {
                    myArr[i] < listConfig.midItem ? listConfig.leftList.push(myArr[i]) : myArr[i] > listConfig
                        .midItem ? listConfig.rightList.push(myArr[i]) : '';
                }
                return listConfig.leftList.quickSort().concat([listConfig.midItem], listConfig.rightList
                    .quickSort()); //递归
            }
            bubbleSort() { //冒泡排序
                for (var i = 0; i < this.length - 1; i++) {
                    for (var j = 0; j < this.length - 1 - i; j++) {
                        if (this[j] > this[j + 1]) {
                            var item = this[j];
                            this[j] = this[j + 1];
                            this[j + 1] = item;
                        }
                    }
                }
                return this
            }
        }
        var quickSortArray = new MyArray(19, 15, 18, 17, 11, 21, 14, 61, 13, 10, 25);
        var bubbleSortArray = new MyArray(9, 5, 8, 7, 1, 2, 4, 6, 3, 10, 25);
        console.log(quickSortArray.quickSort()); //[10, 11, 13, 14, 15, 17, 18, 19, 21, 25, 61]
        console.log(bubbleSortArray.bubbleSort()); //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 25]


谈谈节流和防抖,如何实现


节流:使频繁执行的函数,定时执行,高频率函数执行时,使执行率减少,每n秒才能执行一次,打个比方:每隔1秒钟,会执行5次滚动条滚动事件,我只让它每一秒执行一次(案例:网站中的返回顶部)


防抖:使频繁执行的函数,延时执行,高频率函数执行时,n秒内只执行一次,在事件内多次执行会延时,打个比方:用户在输入框中输入字符,当用户一直在输入时,我们做个延时,当用户输入完毕后会有一段时间停顿,若这个停顿时间大于我们的我们延时时间,我们就进行下一步操作,反之则不进行并且一直延时(案例:搜索引擎搜索输入框)


区别:对于高频率执行函数,节流是每隔规定时间都会执行一次,防抖是只在规定时间外的最后一次执行


实现过程:

        var count = 0
        class OptimizeEvent {
            constructor() {}
            throttle(fn, time) { //节流
                var canDo = true
                return function (e) {
                    if (!canDo) {
                        return false
                    }
                    canDo = false
                    setTimeout(() => {
                        fn.call(this)
                        canDo = true
                    }, time)
                }
            }
            debounce(fn, time) { //防抖
                var _timer = null
                return function () {
                    if (_timer) {
                        clearTimeout(_timer)
                        _timer = null
                    }
                    _timer = setTimeout(fn, time)
                }
            }
        }
        var _event = new OptimizeEvent()
        inputBox.addEventListener('input', _event.debounce(function () {
            showBox.textContent = inputBox.value
        }, 1000))
        document.addEventListener('scroll', _event.throttle(function () {
            console.log(count++)
        }, 1000))

谈谈深拷贝的实现


深拷贝相对浅拷贝不同的是,深拷贝内所有引用类型属性值都是在新开辟的内存地址,被拷贝的原数据发生改变时不会影响复制后的对象。


常见方法


JSON.parse(),JSON.stringify()

jQury的$.extend(true,{},obj)

lodash的_.cloneDeep

我的深复制文章

 

      function deepClone(org, tag) {
        var tag = tag || {}; //初始化要复制的对象
        var name = Object.getOwnPropertyNames(org); //获取该对象的属性名,以字符串数组返回
        for (var i = 0; i < name.length; i++) { //遍历对象
          var desc = Object.getOwnPropertyDescriptor(org, name[i]); //获取对象的属性描述对象,无引用关系,返回另一个对象,改变时原对象不发生变化(复制的关键)
          if (typeof desc.value === 'object' && desc.value !== null) { //若遍历的每一项非空且为对象,则为引用值,则进行下一步
            var obj = desc.value.toString() === '[object Object]' ? {} : []; //判断是数组还是对象
            Object.defineProperty(tag, name[i], { //设置对象属性值,前三个的值是返回true或false
              configurable: desc.configurable, //是否可删除可替换
              enumerable: desc.enumerable, //是否可枚举可遍历
              writable: desc.writable, //是否可写入
              value: obj //对象的值
            });
            copyObj(desc.value, obj); //再次执行函数
          } else {
            Object.defineProperty(tag, name[i], desc); //否则直接将该对象的属性值进行复制(原始值)
          }
        }
        return tag;
      }

常用的对象方法有哪些


添加或更改对象属性

Object.defineProperty(object, property, descriptor)


添加或更改多个对象属性

Object.defineProperties(object, descriptors)


访问属性

Object.getOwnPropertyDescriptor(object, property)


以数组返回所有属性

Object.getOwnPropertyNames(object)


以数组返回所有可枚举的属性

Object.keys(object)


访问原型

Object.getPrototypeOf(object)


阻止向对象添加属性

Object.preventExtensions(object)


如果可将属性添加到对象,则返回 true

Object.isExtensible(object)


防止更改对象属性(而不是值)

Object.seal(object)


如果对象被密封,则返回 true

Object.isSealed(object)


防止对对象进行任何更改

Object.freeze(object)


如果对象被冻结,则返回 true

Object.isFrozen(object)


实现以下输出

       var a = ?
        if (a == 1&& a == 2 && a == 3) {
            console.log('回答正确')
        }
        // 打印回答正确
    class ConsoleA{
        constructor(){
            this.num = 0
            this.valueOf()
        }
        valueOf(){
            return this.num++
        }
    }
    var a = new ConsoleA()
        if (a == 1&& a == 2 && a == 3) {
            console.log('回答正确')
        }
        // 打印回答正确

因为直接调用a==1会进行隐式类型转换从而调用object的valueOf函数


思考以下代码输出,为什么?

var num = 9
switch (num) {
    default:
        console.log('default')
    case 1:
        console.log(1)
    case 2:
        console.log(2)
        break
}

输出   default  1   2

一般情况下default放在switch最后,作为类似于else的作用,而写在switch最上面就相当于if(true),而switch的特点是进入case之后不break跳出来就会一直执行而不做条件判断,所以后面case中的代码都会运行


相关文章
|
1月前
|
JavaScript 前端开发 程序员
前端原生Js批量修改页面元素属性的2个方法
原生 Js 的 getElementsByClassName 和 querySelectorAll 都能获取批量的页面元素,但是它们之间有些细微的差别,稍不注意,就很容易弄错!
|
1月前
|
JavaScript 前端开发 Java
springboot解决js前端跨域问题,javascript跨域问题解决
本文介绍了如何在Spring Boot项目中编写Filter过滤器以处理跨域问题,并通过一个示例展示了使用JavaScript进行跨域请求的方法。首先,在Spring Boot应用中添加一个实现了`Filter`接口的类,设置响应头允许所有来源的跨域请求。接着,通过一个简单的HTML页面和jQuery发送AJAX请求到指定URL,验证跨域请求是否成功。文中还提供了请求成功的响应数据样例及请求效果截图。
springboot解决js前端跨域问题,javascript跨域问题解决
|
1月前
|
缓存 JavaScript 前端开发
JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用
本文深入讲解了 JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用。
50 5
|
1月前
|
缓存 前端开发 JavaScript
JavaScript前端路由的实现原理及其在单页应用中的重要性,涵盖前端路由概念、基本原理、常见实现方式
本文深入解析了JavaScript前端路由的实现原理及其在单页应用中的重要性,涵盖前端路由概念、基本原理、常见实现方式(Hash路由和History路由)、优点及挑战,并通过实际案例分析,帮助开发者更好地理解和应用这一关键技术,提升用户体验。
75 1
|
1月前
|
JSON 前端开发 JavaScript
聊聊 Go 语言中的 JSON 序列化与 js 前端交互类型失真问题
在Web开发中,后端与前端的数据交换常使用JSON格式,但JavaScript的数字类型仅能安全处理-2^53到2^53间的整数,超出此范围会导致精度丢失。本文通过Go语言的`encoding/json`包,介绍如何通过将大整数以字符串形式序列化和反序列化,有效解决这一问题,确保前后端数据交换的准确性。
54 4
|
1月前
|
机器学习/深度学习 自然语言处理 前端开发
前端神经网络入门:Brain.js - 详细介绍和对比不同的实现 - CNN、RNN、DNN、FFNN -无需准备环境打开浏览器即可测试运行-支持WebGPU加速
本文介绍了如何使用 JavaScript 神经网络库 **Brain.js** 实现不同类型的神经网络,包括前馈神经网络(FFNN)、深度神经网络(DNN)和循环神经网络(RNN)。通过简单的示例和代码,帮助前端开发者快速入门并理解神经网络的基本概念。文章还对比了各类神经网络的特点和适用场景,并简要介绍了卷积神经网络(CNN)的替代方案。
148 1
|
1月前
|
移动开发 前端开发 JavaScript
前端实训,刚入门,我用原生技术(H5、C3、JS、JQ)手写【网易游戏】页面特效
于辰在大学期间带领团队参考网易游戏官网的部分游戏页面,开发了一系列前端实训作品。项目包括首页、2021校园招聘页面和明日之后游戏页面,涉及多种特效实现,如动态图片切换和人物聚合效果。作品源码已上传至CSDN,视频效果可在CSDN预览。
45 0
|
2月前
|
存储 人工智能 前端开发
前端大模型应用笔记(三):Vue3+Antdv+transformers+本地模型实现浏览器端侧增强搜索
本文介绍了一个纯前端实现的增强列表搜索应用,通过使用Transformer模型,实现了更智能的搜索功能,如使用“番茄”可以搜索到“西红柿”。项目基于Vue3和Ant Design Vue,使用了Xenova的bge-base-zh-v1.5模型。文章详细介绍了从环境搭建、数据准备到具体实现的全过程,并展示了实际效果和待改进点。
196 2
|
2月前
|
JavaScript 前端开发 程序员
前端学习笔记——node.js
前端学习笔记——node.js
56 0
|
2月前
|
人工智能 自然语言处理 运维
前端大模型应用笔记(一):两个指令反过来说大模型就理解不了啦?或许该让第三者插足啦 -通过引入中间LLM预处理用户输入以提高多任务处理能力
本文探讨了在多任务处理场景下,自然语言指令解析的困境及解决方案。通过增加一个LLM解析层,将复杂的指令拆解为多个明确的步骤,明确操作类型与对象识别,处理任务依赖关系,并将自然语言转化为具体的工具命令,从而提高指令解析的准确性和执行效率。