javascript篇
1.说一下闭包
一句话可以概括:闭包就是能够读取其他函数内部变量的函数,或者
子函数在外调用,子函数所在的父函数的作用域不会被释放。
2.说说前端中的事件流
HTML中与javascript交互是通过事件驱动来实现的,例如鼠标点击事件onclick、页面的滚动事件onscroll等等,可以向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在什么时候进行调用的,就需要了解一下“事件流”的概念。
什么是事件流:事件流描述的是从页面中接收事件的顺序,DOM2级
事件流包括下面几个阶段。
事件捕获阶段处于目标阶段事件冒泡阶段
addEventListener:addEventListener是DOM2级事件新增的指定事件处理程序的操作,这个方法接收3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。IE只支持事件冒泡。
3.说一下事件委托
简介:事件委托指的是,不在事件的发生地(直接dom)上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生元素DOM的类型,来做出不同的响应。
举例:最经典的就是ul和li标签的事件监听,比如我们在添加事件时候,采用事件委托机制,不会在li标签上直接添加,而是在ul父元素上添加。
好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,
也可以有事件触发机制。
4.改变函数内部this指针的指向函数bind,apply,call的区别
通过apply和call改变函数的this指向,他们两个函数的第一个参数都是一样的表示要改变指向的那个对象,第二个参数,apply是数组,而call则是arg1,arg2...这种形式。通过bind改变this作用域会返回一个新的函数,这个函数不会马上执行。
5.JS的各种位置,比clientHeight,scrollHeight,offsetHeight,以及scrollTop,offsetTop,clientTop的区别?
clientHeight:表示的是可视区域的高度,不包含border和滚动条
offsetHeight:表示可视区域的高度,包含了border和滚动条
scrollHeight:表示了所有区域的高度,包含了因为滚动被隐藏的部分。
clientTop:表示边框border的厚度,在未指定的情况下一般为
0scrollTop:滚动后被隐藏的高度,获取对象相对于由offsetParent属性指定的父坐标(css定位的元素或body元素)距离顶端的高度。
6.JS拖拽功能的实现
首先是三个事件,分别是mousedown,mousemove,mouseup当鼠标点击按下的时候,需要一个tag标识此时已经按下,可以执行mousemove里面的具体方法。clientX,clientY标识的是鼠标的坐标,分别标识横坐标和纵坐标,并且我们用offsetX和offsetY来表示元素的元素的初始坐标,移动的举例应该是:鼠标移动时候的坐标-鼠标按下去时候的坐标。也就是说定位信息为:鼠标移动时候的坐标-鼠标按下去时候的坐标+元素初始情况下的offetLeft.还有一点也是原理性的东西,也就是拖拽的同时是绝对定位,我们改变的是绝对定位条件下的left以及top等等值。补充:也可以通过html5的拖放(Drag和drop)来实现
7.JS中的垃圾回收机制
必要性:由于字符串、对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。
这段话解释了为什么需要系统需要垃圾回收,JS不像C/C++,他有自己的一套垃圾回收机制(GarbageCollection)。JavaScript的解释器可以检测到何时程序不再使用一个对象了,当他确定了一个对象是无用的时候,他就知道不再需要这个对象,可以把它所占用的内存释放掉了。例如:vara="helloworld";varb="world";
vara=b;
//这时,会释放掉"helloworld",释放内存以便再引用垃圾回收的方法:标记清除、计数引用。
标记清除
这是最常见的垃圾回收方式,当变量进入环境时,就标记这个变量为”进入环境“,从逻辑上讲,永远不能释放进入环境的变量所占的内存,永远不能释放进入环境变量所占用的内存,只要执行流程进入相应的环境,就可能用到他们。当离开环境时,就标记为离开环境。垃圾回收器在运行的时候会给存储在内存中的变量都加上标记(所有都加),然后去掉环境变量中的变量,以及被环境变量中的变量所引用的变量(条件性去除标记),删除所有被标记的变量,删除的变量无法在环境变量中被访问所以会被删除,最后垃圾回收器,完成了内存的清除工作,并回收他们所占用的内存。
引用计数法
另一种不太常见的方法就是引用计数法,引用计数法的意思就是每个值没引用的次数,当声明了一个变量,并用一个引用类型的值赋值给改变量,则这个值的引用次数为1,;相反的,如果包含了对这个值引用的变量又取得了另外一个值,则原先的引用值引用次数就减1,当这个值的引用次数为0的时候,说明没有办法再访问这个值了,因此就把所占的内存给回收进来,这样垃圾收集器再次运行的时候,就会释放引用次数为0的这些值。
用引用计数法会存在内存泄露,下面来看原因:
functionproblem(){ varobjA=newObject();varobjB=newObject(); objA.someOtherObject=objB;objB.anotherObject=objA; }
在这个例子里面,objA和objB通过各自的属性相互引用,这样的话,两个对象的引用次数都为2,在采用引用计数的策略中,由于函数执行之后,这两个对象都离开了作用域,函数执行完成之后,因为计数不为0,这样的相互引用如果大量存在就会导致内存泄露。
特别是在DOM对象中,也容易存在这种问题:var
element=document.getElementById(’‘);varmyObj=new Object(); myObj.element=element;element.someObject=myObj;
这样就不会有垃圾回收的过程。
8.JS监听对象属性的改变
参考回答:
我们假设这里有一个user对象,
在ES5中可以通过Object.defineProperty来实现已有属性的监听
Object.defineProperty(user,'name',{set:function(key,value){ } })
缺点:如果id不在user对象中,则不能监听id的变化(2)在ES6中可
以通过Proxy来实现
varuser=newProxy({},{ set:function(target,key,value,receiver){ } })
这样即使有属性在user中不存在,通过user.id来定义也同样可以这样
监听这个属性的变化哦。
9.自己实现一个bind函数
原理:通过apply或者call方法
来实现。(1)初始版本
Function.prototype.bind=func tion(obj,arg){ vararg=Array.prototype.slice.call(arguments,1); varcontext=this; return function(newArg){arg=arg.concat(Arr ay.prototype.slice.call(newArg)); returncontext.apply(obj,arg); } }
(2)考虑到原型链
为什么要考虑?因为在new一个bind过生成的新函数的时候,必
须的条件是要继承原函数的原型
Function.prototype.bind=function(obj,arg){ var arg=Array.prototype.slice.call(argu ments,1);varcontext=this; var bound=function(newArg){arg=arg.co ncat(Array.prototype.slice.call(newArg) );returncontext.apply(obj,arg); } varF=function(){} //这里需要一个寄生组合继承 F.prototype=context. prototype; bound.prototype=ne wF();returnbound; }
10.JS怎么控制一次加载一张图片,加载完后再加载下一张
(1)方法1<script
type="text/javascript ">varobj=new Image(); obj.src="http://www. phpernote.com/uplo adfiles/editor/20110 7240502201179.jpg"; obj.οnlοad=function() { alert('图片的宽度为: '+obj.width+';图片的高度为:'+obj.height+'); document.getElemen tById("mypic").innner HTML="<img src='"+this.src+"' />"; } </script> <div id="mypic">onloadi ng……</div>
方法2
<script type="text/javascript ">varobj=new Image(); obj.src="http://www.phpernote.com/uplo adfiles/editor/20110 7240502201179.jpg";obj.onreadystatecha nge=function(){ if(this.readyState=="complete") alert('图片的宽度为: '+obj.width+';图片的高度为:'+obj.height); document.getElemen tById("mypic").innner HTML="<img src='"+this.src+"' />"; } } id="mypic">onloadi ng……</div>
11.实现JS中所有对象的深度克隆(包装对象,Date对象,正则对象)
通过递归可以简单实现对象的深度克隆,但是这种方法不管是ES6
还是ES5实现,都有同样的缺陷,就是只能实现特定的object的
深度复制(比如数组和函数),不能实现包装对象Number,String,
Boolean,以及Date对象,RegExp对象的复制。
(1)前文的方法
functiondeepClone(obj){ varnewObj=objinstanceof Array?[]:{};for(variinobj){ newObj[i]=typeof obj[i]=='object'? deepClone(obj[i]):obj[i]; } returnnewObj; }
这种方法可以实现一般对象和数组对象的克隆,比如:
vararr=[1,2,3]; varnewArr=deepClone(arr); // newArr->[1,2, 3]varobj={ x:1, y:2 } varnewObj=deepClone(obj); //newObj={x:1,y:2} 但是不能实现例如包装对象Number,String,Boolean,以及正则对 象RegExp和Date对象的克隆,比如: //Number包装对象 varnum=newNumber(1);typeofnum//"object" varnewNum=deepClone(num); //newNum->{}空对象 //String包装对象 varstr=newString("hello");typeofstr//"object" varnewStr=deepClone(str); //newStr->{0:'h',1:'e',2:'l',3:'l',4:'o'}; //Boolean包装对象 varbol=newBoolean(true);typeofbol//"object" varnewBol=deepClone(bol); //newBol->{}空对象
(2)valueof()函数
所有对象都有valueOf方法,valueOf方法对于:如果存在任意原始值,它就默认将对象转换为表示它的原始值。对象是复合值,而且大多数对象无法真正表示为一个原始值,因此默认的valueOf()方法简单地返回对象本身,而不是返回一个原始值。数组、函数和正则表达式简单地继承了这个默认方法,调用这些类型的实例的valueOf()
方法只是简单返回这个对象本身。
对于原始值或者包装类:functionbaseClone(base){return
base.valueOf(); } //Number varnum=newNumber(1); varnewNum=baseClone(num); //newNum->1 //String varstr=new String('hello');var newStr=baseClone (str); //newStr->"hello" //Boolean varbol=new Boolean(true);var newBol=baseClone( bol); //newBol->true
其实对于包装类,完全可以用=号来进行克隆,其实没有深度克隆一说,这里用valueOf实现,语法上比较符合规范。
对于Date类型:
因为valueOf方法,日期类定义的valueOf()方法会返回它的一个内部表示:1970年1月1日以来的毫秒数.因此我们可以在Date的原型上定义克隆的方法:Date.prototype.clone=function(){
returnnewDate(this.valueOf()); } vardate=new Date('2010');var newDate=date.clon e(); //newDate->FriJan01201008:00:00GMT+0800
对于正则对象
RegExp:
RegExp.prototype.clone= function(){varpattern= this.valueOf(); varflags=''; flags+=pattern.global?'g':''; flags+= pattern.ignoreCase?'i':''; flags+=pattern.multiline? 'm':''; returnnewRegExp(pattern.source,flags); }; varreg=new RegExp('/111/');var newReg=reg.clone() ; //newReg->/\/111\//
12.来讲讲JS的闭包吧
闭包是指有权访问另外一个函数作用域中的变量的函数。闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在。闭包就是就是函数的“堆栈”在函数返回后并不释放,我们也可以理解为这些函数堆栈并不在栈上分配而是在堆上分配。当在一
个函数内定义另外一个函数就会产生闭包。
(2)为什么要用:
匿名自执行函数:我们知道所有的变量,如果不加上var关键字,则默认的会添加到全局对象的属性上去,这样的临时变量加入全局对象有很多坏处,比如:别的函数可能误用这些变量;造成全局对象过于庞大,影响访问速度(因为变量的取值是需要从原型链上遍历的)。除了每次使用变量都是用var关键字外,我们在实际情况下经常遇到这样一种情况,即有的函数只需要执行一次,其内部变量无需维护,可以用闭包。
结果缓存:我们开发中会碰到很多情况,设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。
13.能来讲讲JS的语言特性吗
运行在客户端浏览器上;
不用预编译,直接解析执行代码;是弱类型语言,较为灵活;与操作系统无关,跨平台的语言;脚本语言、解释性语言
14.JS的全排列
functionpermutate(str) {varresult= []; if(str.length >1){var left=str[0]; varrest=str.slice(1, str.length);var preResult= permutate(rest); for(vari=0; i<preResult.length;i++) {for(varj=0; j<preResult[i].length;j++){ vartmp=preResult[i],slice(0,j)+left+preResult[i].slice(j, preResult[i].length);result.push(tmp); } } }elseif(str.length ==1){return [str]; } returnresult; }