非常详细的自己收集理解的前端面试题(二)

1.JS原型链

JS里万物皆对象,对象又分为普通对象函数对象。每当定义一个对象时,对象中都会包含一些预定义的属性。其中每个对象都有一个_proto_属性,这个属性用来指向创建它的构造函数的原型对象;每个函数对象都有一个prototype 属性,这个属性指向的是该函数的原型对象

先说一下函数对象的prototype属性

function f(){}
f.prototype = { name: 'myfriend '};//f的原型对象

var a = new f();
var b = new f();

a.name  //myfriend 
b.name  //myfriend

原型对象可以实现类似继承的机制和完成数据的共享。所有实例对象需要共享的属性和方法,都放在这个原型对象里面;那些不需要共享的属性和方法,就放在构造函数里面。

//接着上面的代码
f.prototype.name = 'hello';

a.name  //hello
b.name  //hello

原型对象的属性不是实例对象自身的属性。当实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法。这就是原型对象的特殊之处。如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。

a.name = "luse"

a.name // 'luse'
b.name // 'hello'
f.prototype.name // 'hello';

其次是所有对象都有_proto_,指向的是其构造函数的原型对象。

//接着上面的代码
a._proto_ == f.prototype // true
b._proto_ == f.prototype // true

现在再来看一下什么是原型链,所有对象的原型最终都可以上溯到Object.prototype这就是所有对象都有valueIf和toString方法的原因,因为这是从Object.prototype继承的。

//接着上面的代码
// a是一个对象,又_proto_属性,指向a的构造函数的原型对象
a._proto_ == f.prototype 

// f.prototype是一个对象,也会有_proto_属性,指向f的构造函数的原型对象
f.prototype._proto_ == Function.prototype

// Function.prototype是一个普通对象,也会有_proto_属性,指向Function.prototype的构造函数的原型对象
Function.prototype._proto_ == Object.prototype

//直到原型链的最顶端为null,整个为一个链形结构
Object.prototype._proto_ == null

2.call、apply和bind

函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。call的第一个参数就是this所要指向的那个对象,后面的参数则是函数调用时所需的参数。

var name = 'hello';

var obj = { name: 'world'};

var f = function (arg1,arg2) {
  return alert(this.name + arg1 + arg2);
}

//function.call(obj,arg1,arg2...argx)
f.call(obj,"Is","Big")// worldisbig

apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数

//function.apply(obj,[arg1,arg2...argx])
f.apply(obj,["Is","Big"])// worldisbig

bind方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。

var name = 'hello';

var obj = { name: 'world'};

var f = function (arg1,arg2) {
  return alert(this.name + arg1 + arg2);
}

//function.bind(obj,arg1,arg2...argx)
var newf = f.bind(obj,"Is","Big"); 
newf(); // worldIsBig
//或者
var newf = f.bind(obj,"Is"); 
newf("Big"); // worldIsBig

3.css浮动崩塌

当一个元素浮动之后,不会影响到块级元素的布局而只会影响内联元素(通常是文本)的排列,以下举例:

未设置任何浮动的文档普通流

给text1框设置向左浮动的时候,text1脱离了文档流靠左,而text2框和text3框是块级元素仍然保持原来的文档流,使得text1框把text2框覆盖了,并且把text2框内联的文本挤到了text3文本的位置,导致两者重叠。

如果三个元素的浮动都设置靠左,父元素只包含浮动元素,且父元素未设置高度和宽度的时候,那么它的高度就会踏缩为零。这是因为浮动元素脱离了文档流,包围它们的父块中没有内容了,所以就造成了“浮动塌陷”了。

解决办法:1.在父元素下加一个空div的子元素,并加上css属性clear:both清除浮动。

                  2.对父元素设置属性overflow: hidden或overflow: auto,这样父级的高度就随子级容器及子级内容的高度而自适应。

4.异步任务、同步任务、任务队列和事件循环

程序里面所有的任务,可以分成两类:同步任务(synchronous)和异步任务。
同步任务:就是在主线程排队执行的任务,只有前一个任务完成了,才会接着执行下一个任务。
异步任务:就是不在主线程、而是被挂起放在一个任务队列的任务。到该异步任务可以执行了(例如ajax从服务器获取到了结果),该任务才会到主线程执行。排在异步任务后面的代码,不用等到异步任务执行结束才执行,它可以立即执行。

任务队列:放置当前程序处理的异步任务的地方。
事件循环:首先,主线程会去执行所有的同步任务。等到同步任务全部执行完,就会去看任务队列里面的异步任务。如果满足条件,那么异步任务就重新进入主线程开始执行,这时它就变成同步任务了。等到执行完,下一个异步任务再进入主线程开始执行。一旦任务队列清空,程序就结束执行。

5.W3C的WEB标准

网页由三部分组成,结构(html),表现(css)和行为(js)。Web标准一般是将这三部分独立分开,使其更具有模块化。
    W3C对web标准提出规范化要求:
1、  对于结构要求:标签闭合,标签字母小写,标签不允许随意嵌套
2、  对于css,js来说尽量使用外链css样式和js脚本。使结构,表现和行为分为三块,符合规范。同时提高页面渲染速度,提高用户体验。样式尽量少使用行间样式,使结构与表现分离,标签id和class等属性命名要做到见明知意,标签越少,加载越快,用户体验提高,代码维护简单,便于改版。

6.js的内存泄露

内存泄漏:由于疏忽或错误造成程序未能释放已经不再使用的内存。

常见js的几种内存泄露:

1.在函数内使用未声明的变量
        js对未声明变量会在全局最高对象上创建它的引用,根据垃圾回收机制,一个变量的生命周期的结束要等到不再使用该变量,而全局变量则要到关闭浏览器页面才会被回收,所以及其占用内存。

function leakage() {
    under="我是未声明就使用的变量"
}
leakage()
console.log(window)

 2.开发时的console.log语句未去掉
        在传递给console.log的对象是不能被垃圾回收,因为在代码运行之后需要在开发工具能查看对象信息。所以需要养成使用完
console.log后就马上去掉。

3.闭包
        闭包就是能够读取其他函数内部变量的函数,比如下面的f2函数就是闭包
        我之前的面试题(一)有写过闭包https://blog.csdn.net/m0_37820751/article/details/98224901

function f1(){
    n=999;
    function f2(){
      alert(n);
  }
  return f2;
}
 
var result=f1();
result();

         f2被赋给全局变量result,就处于随时被调用的状态,所以不会被回收,需要释放其内存,可以让reault=null。闭包会比普通函数占用更多的内存,所以不能过分使用。

 4.DOM的引用

var main = document.getElementById("main");
var test = document.getElementById("test");
remove.onclick = function () {
        main.removeChild(test);
}

       尽管我们把main父节点下的test子节点remove掉了,test仍然保存着对DOM的引用,导致不会被垃圾回收,造成内存泄露。我们可以在最后使用完之后给test赋值null,使其内存释放。

5.定时器使用后未关闭
       
这也是很多人在使用完定时器setTimeout或者setInterval后,没有进行clearTimeout或者clearIterval来关闭对于的计时器,导致计时器一直在执行占用内存。

7.js的垃圾回收机制

        在js中有引用计数标记清除这两种垃圾回收机制。首先要知道垃圾回收只作用于对象,因为原始类型的值是存在于栈中,会自动回收,而引用类型值存在于堆中,需要垃圾回收。

引用计数,即跟踪记录每个值被引用的次数。
        1.当声明了一个变量并将一个引用类型的值赋给该变量时,则这个引用类型值的引用次数就是1;
        2.又将这个引用类型的值赋给另一个变量时,则这个引用类型值的引用次数加1;
        3.当一个包含这个引用类型值的变量又取得了另外一个值,那么这个引用类型值的引用次数就减1;
        4.当引用次数变为0的时候,则说明没有办法再访问到这个值了,所以,就代表可以将其所占用的内存空间给收回来。
        5.垃圾收集器下次再运行时,他就会释放引用次数为0的值所占的内存。
引用计数的bug:循环引用,指的是对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。如下例子:

function problem(){     
    var objA = new Object();
    var objB = new Object(); 
 
    objA.someOtherObject = objB;
    objB.anotherObject = objA; 
}

        循环引用实际上是在堆中的两个引用类型之间的循环引用,即使释放了objA和objB对这两个引用类型的引用,他们内部还存在着互相引用,引用次数为1不会被垃圾回收。假如这个函数被重复多次调用,就会导致大量内存得不到回收。引用计数已经属于不常见的垃圾回收策略,但是BOM和DOM中的对象是使用C++以COM(Component Ojbect Model,组件对象)对象的形式实现的,而COM对象的垃圾回收机制是引用计数。 因此,即使IE的JavaScript引擎使用的是标记清除的策略,但是JavaScript访问的COM对象依然是基于引用计数的策略的,只要IE中涉及到了COM对象,就会存在循环引用的问题。

标记清除,JavaScript中最重用的垃圾收集方式。
        当变量进入环境时,就标记这个变量为“进入环境”,逻辑上说,永远不能释放  进入环境的变量所占用的内存,因为一旦进入环境就有可能随时用到他们,当变量离开环境的时候,将其标记为“离开环境”。

function addTen(num){  
   var sum += num;  //垃圾收集已将这个变量标记为“进入环境”。
   return sum;      //垃圾收集已将这个变量标记为“离开环境”。
}
addTen(10);  //输出20

        标记清除的好处就是可以解决循环引用问题。如上面循环引用,引用计数都是1,所以只用引用计数的话两个都没办法回收。但是用标记清除只要是有标记就可以直接清除,如何标记变量其实并不重要,关键在于采取什么策略。需要更深入了解标记清除可以看https://www.ituring.com.cn/book/tupubarticle/10955
        垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量(正在使用的变量)以及被环境中的变量引用的变量的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值