72.get 请求传参长度的误区
常说:get 请求参数的大小存在限制,而 post 请求的参数大小无限制。 实际上 HTTP 协议并未规定 get / post 的请求参数大小限制。
纠正误区:是浏览器或 web 服务器对 get 请求参数的最大长度显示进行限制(即限制 url 的长度)并且,不同的浏览器和 WEB 服务器,限制的最大长度不一样:若考虑支持IE,最大长度为 2083byte;若只支持 Chrome,最大长度为 8182byte。
73.get 和 post 的区别
参考链接:GET 和 POST 的区别? - 知乎
GET和POST请求的区别(超详细)post和get的区别Shao_Taylor的博客-CSDN博客
HTTP 常见请求方式:GET、POST、PUT、DELETE
HTTP
协议是基于TCP/IP
的应用层协议, GET和``POST
都使用同一个传输层协议,所以在传输上没区别
1.缓存方面:
get 类似于查找的过程,用户获取数据不需要每次都与数据库连接,因此可以使用缓存。
post 主要进行修改和删除,必须与数据库交互,因此不能使用缓存。
2. 报文格式:
get 方法的参数放在 url 中,post 方法的参数放在 body 中
3.安全性:
从传输的角度来说,由于HTTP
自身是一个明文协议,每个HTTP
请求和返回的数据在网络上都是明文传输 (包括 url
、header和 ``body)
。 在网络节点抓包就能获取完整的数据报文,要防止泄密的唯一手段就是使用HTTPS
(用SSL
协议协商出的密钥加密明文HTTP
数据)。这里要注意,post 可以进行复杂的加密,get 不可以。
但相对来说,get请求参数会以 url 的形式保留在浏览器的记录里,存在安全问题。
post 数据放在请求主体body中,数据不会被浏览器记录。
因此,get 方法对于服务器更安全,post 方法对于客户端更安全。
4.长度及参数类型限制:
在浏览器中,对GET
请求方式的url
长度有限制,对post没有。
因为浏览器要对url
进行解析,解析时要分配内存。对于一个字节流的解析,必须分配buffer
来保存所有要存储的数据。而url
必须当作一个整体,无法分块处理,因此处理一个 get 请求时必须分配一整块足够大的内存。如果url
太长,并发又很高,就容易超出服务器的内存。
get 只支持 ASCII 字符格式的参数, post 方法没有限制。
5.数据包的发送:
post方法有时会发送两个 tcp 数据包(先发送 Header 再发送 Data),与浏览器有关(火狐)。
GET 方法 Header 和 Data 一起发送。
6.幂等性:
get 方法具有幂等性,post 方法不具有
幂等性:同样的请求被执行一次与连续执行多次的效果一样,服务器状态也一样
74.类的创建和继承
(1).类的创建:
new 一个 function,在这个 function 的 prototype 中增加属性和方法。
创建一个 Animal 类:
// 定义一个动物类
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};
生成一个 Animal 类,实例化生成对象后,有方法和属性。
(2).类的继承——原型链继承
特点:基于原型链,既是父类的实例,也是子类的实例
缺点:无法实现多继承
在下面的代码中:
new 一个空对象,这个空对象指向 Animal,Cat.prototype 指向这个空对象,这就是基于原型链的继承。
// 定义一个具体的 Cat 类
// new 一个空对象,这个空对象指向 Animal
function Cat() {};
// Cat.prototype 指向了这个空对象
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
var cat = new Cat();
// 进行输出测试
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
(3).构造继承
使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类
注:这里不涉及原型
特点:可以实现多继承
缺点:只能继承父类实例的属性和方法,不能继承原型上的属性和方法。
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
var cat = new Cat();
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
(4).实例继承和拷贝继承
实例继承:为父类实例添加新特性,作为子类实例返回
拷贝继承:拷贝父类元素上的属性和方法
(5).组合继承
相当于构造继承和原型链继承的组合体。通过调用父类构造函数,继承父类的属性并保留传参的优点,再通过将父类实例作为子类原型,实现函数复用
特点:可以继承实例属性/方法,也可以继承原型属性/方法
缺点:调用了两次父类构造函数,生成两份实例
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat = new Cat();
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
(6).寄生组合继承:
通过寄生方式,砍掉父类的实例属性,在调用两次父类的构造时,不会初始化两次实例方法/属性
75.如何让事件先冒泡后捕获
在 DOM 标准事件模型中,先捕获后冒泡。
如果要实现先冒泡后捕获,对于同一个事件,监听捕获和冒泡,分别对应相应的处理函数,监听到捕获事件,先暂缓执行,直到冒泡事件被捕获后,再执行捕获事件。
76.事件委托
事件委托指,不在事件发生的dom元素上设置监听函数,而是在其父元素上设置,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生元素 DOM 的类型,来做出不同的响应。
举例:ul 和 li 标签的事件监听,在添加事件时采用事件委托机制,不在 li 标签上添加,而是在 ul 元素上添加。
优点:适合动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制。
77.图片的懒加载和预加载
预加载:提前加载图片,当用户需要查看时,可直接从本地缓存中渲染。
懒加载:主要目的是服务器前端的优化,减少请求数或延迟请求数。
两种技术的本质:两者行为相反,一个是提前加载,一个是迟缓甚至不加载。
懒加载对服务器前端有一定的缓解压力作用,预加载会增加服务器前端压力。
78.mouseover 和 mouseenter 的区别
mouseover:当鼠标移入元素或其子元素都会触发事件,有冒泡的过程。对应移除事件: mouseout
mouseenter:当鼠标移入元素本身会触发事件,不会冒泡。对应移除事件: mouseleave
79.clientHeight, scrollHeight, offsetHeight 以及 scrollTop, offsetTop, clientTop 的区别
clientHeight:可视区域的高度,不包含 border 和滚动条
offsetHeight:可视区域的高度,包含 border 和滚动条
scrollHeight:所有区域的高度,包含因滚动被隐藏的部分
offsetParent:距离元素最近的一个具有定位的祖宗元素,若都没有定位,则为 body
clientTop:边框 border 的厚度,未指定情况下一般为 0
offsetHeight:元素到 offsetParent 顶部的距离
scrollTop:滚动后被隐藏的高度,获取对象相对于由 offsetParent 属性指定的父坐标(css
定位的元素或 body 元素)距离顶端的高度。
注:只有元素渲染完成才会计算入 offsetTop,若中间有元素数据需异步获取,则 offsetTop 值最终偏小。
80.Ajax 解决浏览器缓存问题
在 ajax 发送请求前加 anyAjaxObj.setRequestHeader("If-Modified-Since","0")。
在 ajax 发送请求前加 anyAjaxObj.setRequestHeader("Cache-Control","no-cache")。
在 URL 后面加一个随机数: "fresh=" + Math.random()。
在 URL 后面加时间戳:"nowtime=" + new Date().getTime()。
如果是使用 jQuery:$.ajaxSetup({cache:false}),这样页面的所有 ajax 都会执行这条语句,不需要保存缓存记录。
81.eval 是做什么的
将对应的字符串解析成 JS 并执行,应避免使用 JS,消耗性能(2 次,一次解析成 JS,一次执行)
82.对象深度克隆的简单实现
function deepClone(obj) {
var newObj= obj instanceof Array ? [] : {};
for(var item in obj) {
// 数组是对象,但与对象有一定区别
var temple = typeof obj[item] == 'object' ? deepClone(obj[item]) : obj[item];
newObj[item] = temple;
}
return newObj;
}
83.实现一个 once 函数,传入函数参数只执行一次
function ones(func) {
var tag = true;
return function() {
if(tag) {
func.apply(null, arguments);
tag = false;
}
return undefined;
}
}
84.将原生的 ajax 封装成 promise
var myAjax = (url) => {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('get', url);
xhr.send(data);
xhr.onreadystatechange = function() {
if(xhr.readyState == 4 && xhr.status == 200 ){
var json = JSON.parse(xhr.responseText);
resolve(json);
}
else if(xhr.readyState == 4 && xhr.status != 200) {
reject('error');
}
}
})
}
85.JS 监听对象属性的改变
假设一个 user 对象
(1) 在 ES5 中,可以通过 Object.defineProperty 来监听已有属性
Object.defineProperty(user, 'name', {
set:function(key,value) {
}
})
缺点:如果 id 不在 user 对象中,则不能监听 id 的变化
(2) 在 ES6 中,可以通过 Proxy 来实现
即使属性在 user 中不存在,通过 user.id 来定义也可以监听属性
var user = new Proxy({}, {
set:function(target, key, value, receiver) {
}
})
86.如何实现一个私有变量,用 getName 方法可以访问,但不能直接访问
(1) 通过 defineProperty 实现
obj = {
name: yuxiaoliang,
getName: function() {
return this.name
}
}
object.defineProperty(obj, "name", {
//不可枚举 不可配置
});
(2) 通过函数的创建形式
function product() {
var name = 'xiaoZhao';
this.getName = function() {
return name;
}
}
var obj = new product();
87.setTimeout、setInterval 和 requestAnimationFrame(RAF)之间的区别
参考链接:深入理解定时器系列第二篇——被誉为神器的requestAnimationFrame - 小火柴的蓝色理想 - 博客园
RAF 不需要设置时间间隔,采用的是系统时间间隔,不会因为间隔时间的长短而受影响。
如果前面的任务多,会影响 setTimeout 和 setInterval 真正运行时的时间间隔。 setTimeout 和 setInterval 不精确,它们的内在运行机制决定了时间间隔参数。实际上只是指定了 把动画代码添加到浏览器UI线程队列中 等待执行的时间。如果队列前面加入了其他任务,那动画代码要等前面的任务完成后再执行。
特点:
(1)RAF 会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中完成,并且重绘或回流的时间间隔取决于浏览器的刷新频率。
(2)在不可见的元素中,RAF 将不会进行重绘或回流,因此CPU、GPU 和内存使用量更少
(3)RAF 是由浏览器专门为动画提供的 API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了 CPU 开销。
88.实现一个 bind 函数
原理:通过 apply 或者 call 方法来实现。
(1)初始版本
Function.prototype.bind = (obj,arg) => {
var arg = Array.prototype.slice.call(arguments, 1);
var context = this;
return (newArg) => {
arg = arg.concat(Array.prototype.slice.call(newArg));
return context.apply(obj, arg);
}
}
(2) 考虑原型链
在 new 一个 bind 生成的新函数的时候,必须的条件是要继承原函数的原型
Function.prototype.bind = (obj, arg) => {
var arg = Array.prototype.slice.call(arguments, 1);
var context = this;
var bound = (newArg) => {
arg = arg.concat(Array.prototype.slice.call(newArg));
return context.apply(obj,arg);
}
var F = () => {}
// 这里需要一个寄生组合继承
F.prototype = context.prototype;
bound.prototype = new F();
return bound;
}
89.用 setTimeout 实现 setInterval
用 setTimeout() 方法来实现 setInterval() 与 setInterval()自身 之间的区别:
使用 setInterval() 创建的定时器确保了定时器代码规则地插入队列中。这个问题在于:如果定时器代码 在代码再次添加到队列之前 还没完成执行,就会导致定时器代码连续运行多次, 并且之间没有间隔。但是 javascript 能避免这个问题。当且仅当没有该定时器的代码实例时,才会将定时器代码添加到队列中。确保了定时器代码加入队列中最小的时间间隔为指定时间。
重复定时器的规则有两个问题:1.某些间隔被跳过 2.多个定时器的代码执行时间可能比预期小
如上图例子中,第一个定时器是在 205ms 处添加到队列中,但是要过 300ms 才能执行。在405ms 又添加了一个副本。在 605ms 处,第一个定时器代码还在执行中,而且队列中已经有一个定时器实例,所以 605ms 的定时器代码不会添加到队列中。在 5ms 处添加的定时器代码执行结束后,405 处的代码立即执行。
function say() {
// some codes
setTimeout(say, 200);
}
setTimeout(say, 200);
OR
setTimeout(function() {
// some codes
setTimeout(arguments.callee, 200);
}, 200);
90.代码的执行顺序
这道题的衍生在多个大厂的笔试题中出现
参考文章:从promise、process.nextTick、setTimeout出发,谈谈Event Loop中的Job queue · Issue #5 · forthealllight/blog · GitHub
setTimeout(function() {
console.log(1);
}, 0);
new Promise(function(resolve, reject) {
console.log(2);
resolve();
}).then(function() {
console.log(3);
}).then(function() {
console.log(4);
});
process.nextTick(function() {
console.log(5)
});
console.log(6);
// 输出 2,6,5,3,4,1
分析:
首先执行主线程中的同步任务,主线程任务执行完毕后,再从 event loop(事件循环)中读取任务。
script (主程序代码) ——> process.nextTick ——> promise ——> setTimeout
I) 主体部分: 定义promise的构造部分是同步的,先输出2 ,再输出6
(同步情况下,严格按照定义的先后顺序)
II)) process.nextTick:输出5
III) promise:这里其实是promise.then部分,输出3,4
IV) setTimeout : 最后输出1
setTimeout(function() {
console.log(1);
}, 0);
new Promise(function(resolve, reject) {
console.log(2);
setTimeout(function() {
resolve();
}, 0)
}).then(function() {
console.log(3);
}).then(function() {
console.log(4)
});
process.nextTick(function() {
console.log(5)
});
console.log(6);
//输出 2 6 5 1 3 4
分析:
和上面的区别在于promise的构造中,没有同步的resolve,因此promise.then在当前的执行队列中是不存在的,只有promise从pending转移到resolve,才会有then方法,而这个resolve是在一个setTimout时间中完成的,因此最后输出3, 4。