JavaScript 知识总结中篇(更新版)

71.get 请求传参长度的误区

常说:get 请求参数的大小存在限制,而 post 请求的参数大小无限制。

实际上 HTTP 协议并未规定 get / post 的请求参数大小限制。

纠正误区:是浏览器或 web 服务器对 get 请求参数的最大长度显示进行限制(即限制 url 的长度)并且,不同的浏览器和 WEB 服务器,限制的最大长度不一样(若考虑支持IE,最大长度为 2083byte;若只支持 Chrome,最大长度为 8182byte)。

72.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 请求和返回 的数据在网络上都是明文传输 (包括 urlheader和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 方法不具有

幂等性:同样的请求被执行一次与连续执行多次的效果一样,服务器状态也一样

73.类的创建和继承

(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) 寄生组合继承

通过寄生方式,砍掉父类的实例属性,在调用两次父类的构造时,不会初始化两次实例方法/属性

74.如何让事件先冒泡后捕获

在 DOM 标准事件模型中,先捕获后冒泡。

如果要实现先冒泡后捕获,对于同一个事件,监听捕获和冒泡,分别对应相应的处理函数,监听到捕获事件,先暂缓执行,直到冒泡事件被捕获后,再执行捕获事件。

75.事件委托

事件委托指,不在事件发生的dom元素上设置监听函数,而是在其父元素上设置,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生元素 DOM 的类型,做出不同的响应。

举例:ul 和 li 标签的事件监听,在添加事件时采用事件委托机制,不在 li 标签上添加,而是在 ul 元素上添加。

优点:适合动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制。

76.图片的懒加载和预加载

预加载:提前加载图片,当用户需要查看时,可直接从本地缓存中渲染。

懒加载:主要目的是服务器前端的优化,减少请求数或延迟请求数。

两种技术的本质:两者行为相反,一个是提前加载,一个是迟缓甚至不加载。

懒加载对服务器前端有一定的缓解压力作用,预加载会增加服务器前端压力。

77.除了懒加载,还有什么限制首屏性能的问题

1. 过多的资源请求:当页面需要加载大量资源时,会增加页面加载时间。过多的资源请求可能会导致网络延迟和带宽问题。

2. 大型图像和媒体文件或未压缩的资源:会增加页面的下载时间。优化图像大小和格式,或使用适当的图像压缩方法可以有助于减少加载时间。

3. 未优化的代码:低效的 JavaScript 和 CSS 代码可能导致加载和执行时间增加。不必要的重复代码、未使用的库以及不优化的循环等都会影响性能。

4. 阻塞渲染的资源:如果某些资源(如 JavaScript)阻塞了 DOM 渲染,页面加载会变慢。将脚本放在页面底部,或使用异步加载方法可以减轻这个问题。

5. 缺乏浏览器缓存策略:如果没有设置适当的浏览器缓存策略,每次访问页面都需要重新下载资源,增加了加载时间。

6. 缺乏响应式设计:如果网站没有进行响应式设计,不同屏幕大小和设备上的页面加载可能会变慢,影响用户体验。

7. 复杂的 CSS 和布局:复杂的 CSS 和布局可能导致页面回流和重绘,影响渲染性能。

78.mouseover 和 mouseenter 的区别

mouseover:当鼠标移入元素或其子元素都会触发事件,有冒泡的过程。对应移除事件: mouseout

mouseenter:当鼠标移入元素本身会触发事件,不会冒泡。对应移除事件: mouseleave

79.clientHeight, scrollHeight, offsetHeight 以及 scrollTop, offsetTop, clientTop 的区别

clientHeight:可视区域的高度,不包含 border 和滚动条

offsetHeight:可视区域的高度,包含 border 和滚动条

scrollHeight:所有区域的高度,包含因滚动被隐藏的部分

offsetParent:距离元素最近的一个具有定位的祖宗元素,若都没有定位,则为 body

clientTop:边框 border 的厚度,未指定情况下一般为 0

offsetTop:元素到 offsetParent 顶部的距离

scrollTop:滚动后被隐藏的高度,获取对象相对于由 offsetParent 属性指定的父坐标(css

定位的元素或 body 元素)距离顶端的高度。

imgimg

注:只有元素渲染完成才会计算入 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.多个定时器的代码执行时间可能比预期小

img

如上图例子中,第一个定时器是在 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。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

低保和光头哪个先来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值