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中的代码都会运行