一、函数作用提升和变量作用提升分别是什么?两者有什么区别?
1. 变量作用提升:
通过 var 定义(声明)的变量,变量声明会被提升到作用域的最顶端,这样该变量就可以在定义语句之前被访问到。
2. 函数作用提升:
通过function声明的函数,函数声明会被提升到作用域的最顶端,这样该函数就可以在声明语句之前被调用。
3. 区别
变量提升: 将变量的声明提升,但是变量的赋值(即定义)不会被提升,变量的赋值是在代码执行阶段代码执行到原位置处赋值。
函数提升: 将函数的声明和定义一起提升,且函数的提升都会在变量提升之前。
相关知识:
一、函数声明的方式:函数声明,函数表达式,函数对象的声明
- 函数声明
function fun(){
}- 函数表达式
var a=function fun(){
};- 函数对象的声明
var a=new Function(“形参1”,“形参2”, “方法体”);例子:
总结:三种方式中,只有函数声明才会函数声明提升,其他两种不会函数声明提升。
二、函数声明提升和变量声明提升
- 如果变量和函数是同名的,不论变量的声明在函数声明之前还是之后,函数的提升都在变量的提升之前。可能导致变量覆盖函数,但函数不可能覆盖变量,因为函数的声明提升永远在最上面。
———————————————————————
var test=1;
function test(){
return 0;
}
console.log(test); // 结果是:1
原因: 函数test提升到最上面,接下来是变量test的提升,然后变量test赋值为1,变量test覆盖了函数test,输出结果是变量test的值,为1.
———————————————————————
console.log(test); // 结果是:ƒ test(){ return 0; }
var test=1;
function test(){
return 0;
}
原因: 函数test提升到最上面,接下来是变量test的提升,然后是输出test,此时变量test还没有赋值,所以他不会覆盖函数test,所以输出结果为函数。最后给变量test赋值为1。
———————————————————————- 如果变量和函数是不同名的,函数的提升都会在变量提升之前。
二、Js检测数据类型有几种方式?
方式 | 含义 | 举例 |
---|---|---|
typeof | 只可以检测number,string,boolean,object,function,undefined。但对引用数据类型不能细分 | var obj = {}; console.log(typeof obj); //object |
instanceof | 检测某个对象是不是另一个对象的实例,对对象进行细分 | var arr = [1,2,3]; console.log(arr instanceof Array);//true |
constructor | js中所有对象都继承于Object,constructor是其中的一个属性。默认指向实例的构造函数(可以修改) | function fns (){} let f = new fns; console.log(f.constructor === fns); //true |
Object.prototype.toString.call | 通过Object.prototype.toString方法,判断某个对象值属于哪种内置类型 | var sz = [2,3,4]; console.log(Object.prototype.toString.call(sz));//[object Array] |
jquery.type() | jQuery.type( “test” ) === "string" jQuery.type( [] ) === “array” |
三、什么是事件流?什么是事件捕获、冒泡?
名称 | 含义 |
---|---|
事件流 | 指从页面中接收事件的顺序,也可理解为事件在页面中传播的顺序,包括三个阶段: 1. 事件捕获阶段:由外部动作触发到传递到目标元素最近的父节点的过程,途中不会接受事件。 2. 处于目标元素阶段:从事件传入到目标元素之初,到未接收事件之末的过程。 3. 事件冒泡阶段:从事件接受处理触发之初,到事件递交document节点之末的过程,途中接受事件。 |
捕获 | 当事件发生时,最先得到通知的是window,然后是document,由上至下逐级依次而入,直到真正触发事件的那个元素(目标元素)为止。 |
冒泡 | 事件从目标元素开始起泡,由下至上逐级依次传播,直到window对象为止。 |
四、什么是事件的委托?有什么好处?
含义: 如果一个父节点内含有多个子节点,并且每个子节点都存在同一事件类型,这时可以把原本需要绑定在子节点的事件绑定给父节点,让父节点担当事件监听的职务,这样就不用为每个子代绑定事件,降低内存,提高性能。
优点:
- 管理的事件函数变少了,不需要为每个元素都添加监听函数。
- 可以方便地动态添加和修改元素,不需要为元素修改事件绑定。
- JS和DOM节点之间的关联变少了,这样也就减少了因循环引用而带来的内存泄漏发生的概率。
注意:
使用“事件委托”时,并不是说把事件委托给的元素越靠近顶层就越好。事件冒泡的过程也需要耗时,越靠近顶层,事件的”事件传播链”越长,也就越耗时。如果DOM嵌套结构很深,事件冒泡通过大量祖先元素会导致性能损失。
五、什么是同步?异步?
1. 单线程
JS是一门单线程的语言,JS在同一个时间只能做一件事,如果在同个时间有多个任务的话,这些任务就需要进行排队,前一个任务执行完,才会执行下一个任务。
2. 同步、异步
- 为什么会有同步和异步?
JS是单线程的,如果前一个任务的执行时间很长,比如文件的读取操作或ajax操作,后一个任务就不得不等着,严重影响用户体验。因此,JS在设计的时候,遇到这个问题,主线程可以不用等待,可以先挂起处于等待中的任务,先运行排在后面的任务,等到文件的读取或ajax有了结果后,再回过头执行挂起的任务,因此,任务就可以分为同步任务和异步任务。- 同步任务
同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。- 异步任务
异步任务是指不进入主线程,而进入任务队列,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。- 异步机制
首先说下任务队列,异步任务是不会进入主线程,而是会先进入任务队列,任务队列其实是一个先进先出的数据结构,也是一个事件队列,当异步任务准备好后,就会在任务队列中添加一个事件,表示异步任务完成啦,可以进入执行栈。但是这时主线程不一定有空,当主线程处理完其它任务有空时,就会读取任务队列,读取里面有哪些事件,排在前面的事件会被优先进行处理,如果该任务指定了回调函数,那么主线程在处理该事件时,就会执行回调函数中的代码,也就是执行异步任务。
单线程从从任务队列中读取任务是不断循环的,每次栈被清空后,都会在任务队列中读取新的任务,如果没有任务,就会等到,直到有新的任务,这就叫做任务循环。- JS中异步机制包括以下几个步骤:
(1)所有同步任务都在主线程上执行,行成一个执行栈
(2)主线程之外,还存在一个任务队列,只要异步任务有了结果,就会在任务队列中放置一个事件
(3)一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面还有哪些事件,那些对应的异步任务,结束等待状态,进入执行栈,开始执行。
(4)主线程不断的重复上面的第三步。
3. 异步任务有哪些?
- 定时器都是异步操作
- 事件绑定都是异步操作
- AJAX中一般我们都采取异步操作(也可以同步)
- 回调函数可以理解为异步(不是严 谨的异步操作)
六、什么是内存泄露?
1. 内存泄露:
应用程序不再需要占用内存的时候,由于某些原因,既不能使用,又不能回收,直到浏览器进程结束,就叫做内存泄漏。
2. 引起内存泄漏的情况
- 意外的全局变量引起的内存泄漏
function fn() {
num = 123 // 未声明的隐式变量等同于 window.num (全局变量)
} // 调用完了函数以后,变量仍然存在,导致泄漏- 闭包引起的内存泄漏
function outFn() {
var num = 123 // 被闭包所引用的变量,不会被回收
return function() {
console.log(num)
}
}
// 调用完了函数以后,变量仍然存在,导致泄漏- 没有清理的DOM元素引用
- 被遗忘的定时器或者回调
- 子元素存在引用引起的内存泄漏
3. 内存泄露如何避免
- 注意程序逻辑,避免“死循环”之类的
- 减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收
- 避免创建过多的对象 原则:不用了的东西要及时归还
七、什么是js垃圾回收机制?
对垃圾回收算法来说,核心思想就是如何判断内存已经不再使用了。
方法1 :引用计数法
语言引擎有一张"引用表",保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放(被回收)。
const arr = [1, 2, 3, 4];
// 数组[1, 2, 3, 4]是一个值,会占用内存。变量arr是仅有的对这个值的引用,因此引用次数为 1
// 尽管后面的代码没有用到arr,它还是会持续占用内存
arr = null;
// arr重置为null,就解除了对[1, 2, 3, 4]的引用,引用次数变成了0,内存就可以释放出来了
方法2 :标记清除法(mark-and-sweep)
- 垃圾回收器生成一个根列表。根通常是将引用保存在代码中的全局变量。在JavaScript中,window对象是一个可以作为根的全局变量。
- 所有的根都被检查和标记成活跃的(不是垃圾),所有的子变量也被递归检查。所有可能从根元素到达的都不被认为是垃圾。
- 所有没有被标记成活跃的内存都被认为是垃圾。垃圾回收器就可以释放内存并且把内存还给操作系统。
八、算法题
1. 如何合并两个数组,去掉重复的并排序?
var a=[2,4,5];
var b=[2,3,4];
var d;
var c=[];
d= a.concat(b); // 合并数组
d.sort(); // 排序
for(var i=0;i< d.length;i++){
if(c.indexOf(d[i])==-1){ // 数组去重
c.push(d[i]);
}
}
console.log(c); // 结果:[2, 3, 4, 5]
2. 找出1-100中所有的素数?
for(var i=1;i<=100;i++){
for(var j=2;j<=parseInt(i/2)+1;j++){
if(i%j==0&&i!=j){
break;
}
else{
if(j==parseInt(i/2)+1){
console.log(i);
}
}
}
}
3. 将12345678转化成RMB格式:12,345,678
var a=12345678.33;
var c="";
// 方法 1:
console.log(a.toLocaleString()); // 12,345,678.33
// 方法 2:
var m=a.toString();
var n= m.slice(m.indexOf(".")); // 截取小数部分
var b=m.slice(0, m.indexOf(".")); // 截取整数部分
var index=b.length%3;
var leng=parseInt(b.length/3);
for(var k=0;k<=leng;k++){
if(k==0){
c=c.concat(b.substr(0,index))+",";
}
else{
if(k==leng){
c=c+b.substr(index+3*(k-1),3);
}
else{
c=c+b.substr(index+3*(k-1),3)+",";
}
}
}
console.log(c+n); // 12,345,678.33
4. 在一个未排序数组中[6,4,3,2,1,7]和一个数9,判断数组里面是否有两个数之和为9
var arr=[6,4,3,2,1,7];
var he=9;
// 方法 1:
for(var i=0;i<arr.length-1;i++){
var ind=arr.indexOf(9-arr[i],i+1);
if(ind!=-1){
console.log(arr[i], arr[ind]); // 6 3 2 7
}
}
// 方法 2:
for(var i=0;i<arr.length-1;i++){
for(var j=i+1;j<arr.length;j++){
if(arr[i]+arr[j]==9){
console.log(arr[i], arr[j]); // 6 3 2 7
}
}
}
5. 原位反转 Eg: ”I am the good boy” >“I ma eht doog yob”
var str="I am the good boy";
// 方法 1:
var arr=str.split(" ");
var newstr="";
for(var i=0;i<arr.length;i++){
var a=arr[i].split(""); // 灵活使用数组和字符串的转换及方法对字符串进行操作
var b=a.reverse();
var c=b.join("");
if(i<arr.length-1){
newstr= newstr+c+" ";
}
else{
newstr= newstr+c;
}
}
console.log(newstr); // I ma eht doog yob
// 方法 2:
console.log(str.split("").reverse().join("").split(" ").reverse().join(" "));