一、谈谈你对回流和重绘的理解?
- 回流:当一个元素自身的宽高,布局,显示或隐藏,或元素内部的文字结构发生变化,导致需要重新构建页面的时候,就产生了回流复制
- 重绘:当一个元素自身的宽高,布局,及显示或隐藏没有改变,而只是改变了元素的外观风格的时候,就产生了重绘。
当元素添加或者删除可见的DOM元素,当元素的位置发生改变,元素的尺寸发生改变,内容改变和页面第一次渲染的时候页面会进行回流。
当改变元素的color、background、box-shadow、visibility、background-size
的时候页面会进行重绘。
二、JS数据类型有哪些?
基本数据类型:
- Number
- String
- Boolean
- Null
- Undefined
- Symbol
- bigInt
引用数据类型:
- object
- Array
- Date
- Function
- RegExp
三、如何判断js的数据类型?
typeof
:可以用来区分除了Null
类型以外的原始数据类型,对象类型的可以从普通对象里面instanceof
:A instanceof B
可以判断A
是不是B
的实例,返回一个布尔值,由构造类型判断出数据类型。- 通过
Object
下的toString.call()
方法来判断:Object.prototype.toString.call(true); // => "[object Boolean]"
- 根据对象的
contructor
判断:arr.constructor === Array
- 通过Jquery中判断数据类型:
jQuery.isArray()
四、JS中创建对象的几种方法
1、{} 或者 new Object()
就是先创建对象,后通过赋值法给对象添加属性,如下:
<script>
'use strict'; //使用strict模式
/**
使用{}创建对象,等同于 new Object();
**/
var o = {};
o.name = '胖蔡';
o.sayName = function(){
alert(this.name);
}
alert(o.name+'-'+o.age);
o.sayName();
</script>
2、使用字面量
象字面变量是对象定义的一种简写形式,举个例子:var person = {name: 'zhang', age:20}
, 这就是字面量形式,完全等价于var person = {}; person.name='zhang'; person.age=20;
3、工厂模式创建对象
通过函数来创建对象,和上述并没有本质区别:
<script>
'use strict';
// 使用工厂模式创建对象
// 定义一个工厂方法
function createObject(name){
var o = new Object();
o.name = name;
o.sayName = function(){
alert(this.name);
};
return o;
}
var o1 = createObject('zhang');
var o2 = createObject('li');
//缺点:调用的还是不同的方法
//优点:解决了前面的代码重复的问题
alert(o1.sayName===o2.sayName);//false
</script>
4、构造函数模式(constructor)
使用构造函数实现创建对象:
<script>
'use strict';
/**
* 构造函数模式创建对象
**/
function Person(name){
this.name = name;
this.sayName = function(){
alert(this.name);
};
}
var p1 = new Person('zhang');
var p2 = new Person('li');
p1.sayName();
p2.sayName();
alert(p1.constructor === p2.constructor);//true
alert(p1.constructor === Person);//true
alert(typeof(p1));//object
alert(p1 instanceof Object); //true
alert(p2 instanceof Object); //true
alert(p1.sayName===p2.sayName);//false
</script>
5、原型模式(prototype)
每个方法中都有一个原型(prototype
),每个原型都有一个构造器(constructor
),构造器又指向这个方法。
<script>
'use strict';
/*
* 原型模式创建对象
*/
function Animal() { }
Animal.prototype.name = 'animal';
Animal.prototype.sayName = function () { alert(this.name); };
var a1 = new Animal();
var a2 = new Animal();
a1.sayName();
alert(a1.sayName === a2.sayName);//true
alert(Animal.prototype.constructor);//function Animal(){}
//修改a2.name,a1的name不会变
a2.name = 'dog';
a2.sayName();//dog
a1.sayName();//animal
</script>
这个方法和上述类似,对象的创建均是使用构造函数实现的,但对象属性是通过 JS
的原型来进行创建的。通过原型创建对象,把属性和方法绑定到prototype上,通过这种方式创建对象,方法是共享的,每个对象调用的是同一个方法。
如果往新建的对象中加入属性,那么这个属性是放在对象中,如果存在与原型同名的属性,也不会改变原型的值。但是访问这个属性,拿到的是对象的值。
访问的顺序:对象本身>构造函数的prototype
如果对象中没有该属性,则去访问prototype
,如果prototype
中没有,继续访问父类,直到Object
,如果都没有找到,返回undefined
。
6、构造函数+原型模式
这种方式结合上面两种方法,代码如下:
<script>
'use strict';
function Animal(name){
this.name = name;
this.friends = ['dog','cat'];
}
Animal.prototype.sayName = function(){
alert(this.name);
};
var a1 = new Animal('d');
var a2 = new Animal('c');
a1.friends.push('snake');
alert(a1.friends);//[dog,cat,snake]
alert(a2.friends);//[dog,cat]
</script>
五、简单介绍下JS闭包,什么是函数柯里化?
闭包是指函数可以访问其外部上下文中定义的变量,即使这些变量在闭包函数执行时已经销毁。闭包可以创建私有变量并且可以在不同的函数调用中维持它们的值。在JavaScript
中,每次函数调用都会创建一个新的执行上下文环境,闭包需要在其执行环境中创建一个持久化环境以保存其外部上下文的状态。
function counter() {
let count = 0;
return function increment() {
count++;
console.log(count);
}
}
var c = counter();
c(); // 输出1
c(); // 输出2
c(); // 输出3
函数柯里化是通过将一个多参数的函数转换成一个多个单参数函数的过程,例如将函数f(x,y)转换成函数f(x)(y)。这个过程可以用闭包来实现。柯里化的用途包括简化函数调用、提供默认参数值以及延迟求值等。示例代码如下:
function fn(a, b) {
return function inner(type) {
let res
switch (type) {
case '+': res = a + b; break;
case '-': res = a - b; break;
case '*': res = a * b; break;
case '/': res = a / b; break;
}
return res
}
}
// f 接受到的就是 inner 函数
var f = fn(100, 200)
console.log(f('+'))
console.log(f('-'))
console.log(f('*'))
console.log(f('/'))
由于闭包的内部函数可以访问函数外部参数,所以有可能导致本该回收的变量没有被回收,导致内存泄漏,所以不能滥用闭包。
六、JS中的作用域是什么?作用域链又是什么?
代码(变量)可以使用的范围就是作用域。主要是为了提高程序的可靠性,也是为了减少命名冲突:
- 全局作用域:指的是整个js文件,定义在全局中的全局变量,可以在局部作用域中使用,函数内部没有声明直接赋值的变量也叫全局变量
- 局部作用域:主要指的是函数作用域,函数内部也就是局部作用域
在函数内部var定义的变量,叫做局部变量,局部变量无法被外部获取。
作用域链是指JavaScript
在查找变量的时候,先从当前作用域开始查找,如果没有找到则向上一级作用域查找,直到找到为止。理解作用域链可以帮助我们更好地理解变量的作用范围及可访问性。例如,当我们在一个函数中访问一个变量时,JavaScript
会先查找当前函数内的变量,如果没有找到则向上一级作用域查找,直到找到为止。如果一直找到全局作用域还没有找到,则会返回undefined
。
七、如何修改this的指向?
this
指针都是当前运行的函数执行上下文,也可以理解为一个变量。当为了实现的需求,我们需要对某些运行的代码修改this
指向,这时就可以使用JavaScript
中提供的修改this指向的三种方法:call、 apply、 bind。
call、bind、apply
都是JavaScript
中用于改变函数执行上下文(即 this 指向)的方法。call
和apply
的作用是一样的,都是用来调用函数并且改变函数内部的this
指向。区别在于传参的方式不同,call
的参数是一个一个传递的,而 apply 的参数是以数组的形式传递的。bind
方法不会立即执行函数,而是返回一个新的函数,这个新的函数的this
值被绑定到了指定的对象,调用时也可以传入参数。同时使用bind
方法可以实现柯里化,即将函数转化为接收部分参数的函数。
八、什么是事件冒泡?怎么阻止事件冒泡?
当我们点击子元素触发父元素的事件,这种现象,我们叫做事件冒泡,即由子元素向祖先元素传播,就像气泡从水底上浮,我们可以使用event.stopPropagation();
阻止事件冒泡。
event.stopPropagation();
// 阻止了事件冒泡,但不会阻击默认行为。event.preventDefault();
// 阻止默认事件,比如a的跳转事件。
九、简单说下深拷贝和浅拷贝
- 浅拷贝:基本数据类型拷贝的是值,引用数据类型拷贝的是地址
- 深拷贝:引用数据类型拷贝的是开辟新地址中的值
使用场景可以在数据传值的时候,当需要进行数据传值的时候,使用...
或者Object.assign
进行数据复制的时候,使用的就是浅拷贝,通过JSON.stringify、JSON.parse
或者直接通过对象遍历进行实现深拷贝。
十、防抖与节流
- 防抖:前面的所有的触发都被取消,最后一次执行在规定的时间之后才会触发,也就是说如果连续快速的触发,只会执行最后一次。
- 节流:在规定的间隔时间范围内不会重复触发回调,只有大于这个时间间隔才会触发回调,把频繁触发变为少量触发。
防抖的原理是:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。节流的原理是:规定在一个单位时间内,只能触发一次函数,可以配合setTimeout、clearTimeout
来使用。如果这个函数时间触发多次函数,只有一次有效。和防抖函数差不多。
十一、请解释一下 JavaScript 的同源策略
JavaScript
的同源策略(Same-Origin Policy)
是一种安全机制,用于保护Web浏览器中的文档对象模型(DOM)
免受恶意网站的跨域攻击。同源策略要求在访问Web
页面中的资源时,只能与来源相同的资源进行交互,来源由协议、主机名和端口号组成。
具体来说,以下条件满足时,就认为两个URL具有相同的源:
- 协议(
Protocol
):两个URL的协议必须相同,如http://
和http://
,或https://
和https://
。 - 主机名(
Host
):两个URL
的主机名必须相同,如example.com
和example.com
。 - 端口号(
Port
):如果URL
中指定了端口号,两个URL
的端口号必须相同,如example.com:80
和example.com:80
。如果URL
中没有指定端口号,则默认为80
(对于HTTP
)或443
(对于HTTPS
)。
十二、什么是跨域?如何解决跨域?
跨域:指浏览器同源策略限制的请求。跨域解决方法如下:
1、jsonp
浏览器的script、img、iframe
标签是不受同源策略限制的 ,所以通过script
标签引入一个js
文件,这个js
文件载入成功后会执行我们在url参数中指定的callback
函数,并把把我们需要的json
数据作为参数传入。在服务器端,当req.params
参数中带有callback
属性时,则把数据作为callback
的参数执行,并拼接成一个字符串后返回。
- 兼容性好,在很古老的浏览器中也可以用,简单易用,支持浏览器与服务器双向通信。
- 只支持GET请求,且只支持跨域HTTP请求这种情况(不支持HTTPS)
2、iframe
H5
提供了postMessage()
的方法,可以在父子页面进行通信,iframe
窗口和window.open
方法打开的窗口,它们与父窗口无法通信。HTML5
为了解决这个问题,引入了一个全新的API
:跨文档通信 API(Cross-document messaging)
。这个API
为window
对象新增了一个window.postMessage
方法,允许跨窗口通信,不论这两个窗口是否同源。
3、服务器配置CORS
服务器端通过检查请求头部的origin
,从而决定请求应该成功还是失败。具体的方法是在服务端设置Response Header
响应头中的Access-Control-Allow-Origin
为对应的域名,实现了CORS
(跨域资源共享),这里出于在安全性方面的考虑就是尽量不要用 *
,但对于一些不重要的数据则随意,例如图片。CORS
需要后端配置,前端不需要处理。
4、中间件代理(nodejs等)
通过一个nodejs
服务来进行跨域处理,网页数据直接通过中间件获取,这样获取的数据就都是同源的了。
5、websocket
websocket
可以不需要考虑跨域问题,WebSocket
在建立连接时需要借助 HTTP
协议,连接建立好了之后 client
与 server
之间的双向通信就与 HTTP
无关了。
十三、JavaScript中的垃圾回收机制
JavaScript中的内存回收机制主要依靠自动垃圾回收(Garbage Collection)来管理内存。垃圾回收器定期扫描内存中的对象,并标记那些不再被引用的对象,然后释放它们所占用的内存空间。
- 标记-清除(Mark and Sweep):这是最常用的垃圾回收算法。垃圾回收器首先标记所有从根对象(如全局对象、活动执行上下文、当前调用栈等)可访问到的对象。然后,它遍历所有对象并清除未被标记的对象,即释放它们所占用的内存。
- 引用计数(Reference Counting):这种算法会为每个对象维护一个引用计数器。当对象被引用时,计数器加1;当对象的引用被移除时,计数器减1。当计数器为零时,表示对象不再被引用,即可被回收。然而,引用计数算法难以处理循环引用的情况,导致循环引用的对象无法被回收。
现代JavaScript引擎通常使用标记-清除算法作为主要的垃圾回收机制。除此之外,还使用了一些优化技术来改进回收性能,如分代回收、增量回收和空闲时间回收等。