🌈个人主页:前端青山
🔥系列专栏:Javascript篇
🔖人终将被年少不可得之物困其一生
依旧青山,本期给大家带来javascript篇专栏内容:JavaScript全面指南(三)
目录
41、构造函数Fn,原型对象,实例对象,三者之间的关系
42、对MVC,MVVM的理解
43、JavaScript中不同类型的错误有几种?
44、 使用正则表达式验证邮箱格式。
45、简述同步和异步的区别
46、对象属性如何分配?
47、获得CheckBox状态的方式是什么?
48、解释window.onload和onDocumentReady?
49、跨域请求资源的方法有哪些?
50、定义事件冒泡?
51、简述一下Sass,Less,请说明区别?
52、数组扁平化,不用api
53、什么样的布尔运算符可以在JavaScript中使用?
54、用javascript实现观察者模式
ES5下的实现
ES6的实现(使用set方法实现)
55、简述一下面象对象的六法则
56、谈谈垃圾回收机制方法以及内存管理
57、开发过程中遇到内存泄漏的问题
58、定义事件冒泡?
59、一个特定的框架如何使用JavaScript中的超链接定位?
60、浏览器有哪些兼容问题,你封装过什么插件
41、构造函数Fn,原型对象,实例对象,三者之间的关系
每创建一个函数,该函数都会自动带有一个prototype属性。该属性是一个指针,指向一个对象,该对象称之为原型对象(后期我们可以使用这个原型对象帮助我们在js中实现继承).
原型对象上默认有一个属性constructor,该属性也是一个指针,指向其相关联的构造函数。
通过调用构造函数产生的实例对象,都拥有一个内部属性,指向了原型对象。其实例对象能够访问原型对象上的所有属性和方法。
总结:三者的关系是,每个构造函数都有一个原型对象,原型对象上包含着一个指向构造函数的指针,而实例都包含着一个指向原型对象的内部指针。通俗的说,实例可以通过内部指针访问到原型对象,原型对象可以通过constructor找到构造函数。
实例:
function People(){
this.type='人'
}
People.prototype.showType=function(){
alert(this.type);
}
var person=new People();
//调用原型对象上面的方法
person.showType();//最后结果弹框弹出人
以上代码定义了一个构造函数People(),People.prototype指向原型对象,其自带属性construtor又指回了People,即People.prototype.constructor==People.实例对象person由于其内部指针指向了原型对象,所以可以访问原型对象上的showType方法。
记住People.prototype只是一个指针,指向的是原型对象,利用这个指针可以帮助我们实现js继承
2.原型链
在第一部分我们说到,所有的实例都有一个内部指针指向他的原型对象,并且可以访问到原型对象上的所有属性和方法。person实例对象指向了People的原型对象,可以访问People原型对象上的所有属性和方法。如果People原型对象变成了某一个类的实例aaa,这个实例又会指向一个新的原型对象AAA,那么person此时能访问aaa的实例属性和AAA原型对象上的所有属性和方法了。同理新的原型对象AAA碰巧有事另外一个对象的实例bbb,这个对象实例指向原型对象BBB,那么person就能访问bbb的实例属性和BBB原型上的属性和方法了。
function People(){
this.type='人'
}
People.prototype.showType=function(){
alert(this.type);
}
function Woman(){
this.sex='女';
this.age=34;
}
Woman.prototype=new People();
var w=new Woman();console.log('大家好,我的种类是:'+w.type+",我的年龄是:"+w.age+",我的性别是:"+w.sex);//输出结果://大家好,我的种类是:人,我的年龄是:34,我的性格是:女//w.type是People上面定义的type
解释一下以上代码.以上代码,首先先定义了People构造函数,通过new People()得到实例,会包含一个实例对象type和一个原型属性showType。另外定义一个Woman构造函数,然后情况发生变化,本来构造函数woman的prototype会执行Woman的原型对象,但是我们这里稍有改变,将Woman构造函数的prototype指向了People实例对象覆盖了woman的原型对象。当Woman的实例对象woman去访问type属性时,js首先在woman实例属性中查找,发现没有定义,接着去woman的原型对象上找,woman的原型对象这里已经被我们改成了People实例,那就是去People实例上去找。先找People的实例属性,发现没有type,最后去People的原型对象上去找,终于找到了。这个查找就是这么一级一级的往上查找。
function People(){
this.type='人'
}
People.prototype.showType=function(){
alert(this.type);
}
function Woman(){
this.sex='女';
this.age=34; this.type='女生';//如果这里定义了type属性,就不会层级查找,最后在People找到该属性
}
Woman.prototype=new People();
var w=new Woman();
console.log('大家好,我的种类是:'+w.type+",我的年龄是:"+w.age+",我的性别是:"+w.sex);
//输出结果:
//大家好,我的种类是:女生,我的年龄是:34,我的性格是:女
这就说明,我们可以通过原型链的方式,实现 Woman继承 People 的所有属性和方法。
总结来说:就是当重写了Woman.prototype指向的原型对象后,实例的内部指针也发生了改变,指向了新的原型对象,然后就能实现类与类之间的继承了。
42、对MVC,MVVM的理解
1、MVC
View 传送指令到 Controller
Controller 完成业务逻辑后,要求 Model 改变状态
Model 将新的数据发送到 View,用户得到反馈
所有的通信都是单向的。
2、MVVM
View:UI界面
ViewModel:它是View的抽象,负责View与Model之间信息转换,将View的Command传送到Model;
Model:数据访问层
43、JavaScript中不同类型的错误有几种?
有三种类型的错误:
-
Load time errors:该错误发生于加载网页时,例如出现语法错误等状况,称为加载时间错误,并且会动态生成错误。
-
Run time errors:由于在HTML语言中滥用命令而导致的错误。
-
Logical Errors:这是由于在具有不同操作的函数上执行了错误逻辑而发生的错误。
44、 使用正则表达式验证邮箱格式。
一.相关的代码
1
function test() 2
{ 3
var temp = document.getElementById("text1"); 4
//对电子邮件的验证 5
var myreg = /^([a-zA-Z0-9]+[|_|.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[|_|.]?)*[a-zA-Z0-9]+.[a-zA-Z]{2,3}$/; 6
if(!myreg.test(temp.value)) 7
{ 8
alert('提示\n\n请输入有效的E_mail!'); 9
myreg.focus(); 10
return false; 11
} 12
} 13
//由于方法相同,一下只写出相关的正则表达式 14
//对于手机号码的验证(提供了两种方法) 15
var mobile=/^((13[0-9]{1})|159|153)+\d{8}$/; 16
var mobile1=/^(13+\d{9})|(159+\d{8})|(153+\d{8})$/; 17
//对于区号的验证 18
var phoneAreaNum = /^\d{3,4}$/; 19
//对于电话号码的验证 20
var phone =/^\d{7,8}$/;
二.解释相关的意义 \1. /^$/ 这个是个通用的格式。 ^ 匹配输入字符串的开始位置;$匹配输入字符串的结束位置 \2. 里面输入需要实现的功能。 * 匹配前面的子表达式零次或多次; + 匹配前面的子表达式一次或多次; ?匹配前面的子表达式零次或一次; \d 匹配一个数字字符,等价于[0-9]
45、简述同步和异步的区别
同步:
同步的思想是:所有的操作都做完,才返回给用户。这样用户在线等待的时间太长,给用户一种卡死了的感觉(就是系统迁移中,点击了迁移,界面就不动了,但是程序还在执行,卡死了的感觉)。这种情况下,用户不能关闭界面,如果关闭了,即迁移程序就中断了。
异步:
将用户请求放入消息队列,并反馈给用户,系统迁移程序已经启动,你可以关闭浏览器了。然后程序再慢慢地去写入数据库去。这就是异步。但是用户没有卡死的感觉,会告诉你,你的请求系统已经响应了。你可以关闭界面了。
同步和异步本身是相对的
同步就相当于是 当客户端发送请求给服务端,在等待服务端响应的请求时,客户端不做其他的事情。当服务端做完了才返回到客户端。这样的话客户端需要一直等待。用户使用起来会有不友好。
异步就是,当客户端发送给服务端请求时,在等待服务端响应的时候,客户端可以做其他的事情,这样节约了时间,提高了效率。
存在就有其道理 异步虽然好 但是有些问题是要用同步用来解决,比如有些东西我们需要的是拿到返回的数据在进行操作的。这些是异步所无法解决的。
46、对象属性如何分配?
属性按以下方式分配给对象:
obj["class"] = 12;
或
obj.class = 12;
47、获得CheckBox状态的方式是什么?
alert(document.getElementById('checkbox1')。checked);
如果CheckBox被检查,此警报将返回TRUE。
48、解释window.onload和onDocumentReady?
在载入页面的所有信息之前,不运行onload函数。这导致在执行任何代码之前会出现延迟。
onDocumentReady在加载DOM之后加载代码。这允许早期的代码操纵。
49、跨域请求资源的方法有哪些?
1.porxy代理 定义和用法:proxy代理用于将请求发送给后台服务器,通过服务器来发送请求,然后将请求的结果传递给前端。 实现方法:通过nginx代理; 注意点:如果你代理的是https协议的请求,那么你的...
2.CORS 【Cross-Origin Resource Sharing】 定义和用法:是现代浏览器支持跨域资源请求的一种最常用的...
3.jsonp 定义和用法:通过动态插入一个script标签。浏览器对script的资源引用...
50、定义事件冒泡?
avaScript允许DOM元素嵌套在一起。在这种情况下,如果单击子级的处理程序,父级的处理程序也将执行同样的工作。
51、简述一下Sass,Less,请说明区别?
他们是动态的样式语言,是CSS预处理器,CSS上的一种抽象层。他们是一种特殊的语法/语言而编译成CSS。 变量符不一样,less是@,而Sass是$; Sass支持条件语句,可以使用if{}else{},for{}循环等等。而Less不支持; Sass是基于Ruby的,是在服务端处理的,而Less是需要引入less.js来处理Less代码输出Css到浏览器
52、数组扁平化,不用api
数组扁平化的自我(手撕)实现【核心:用到了递归,如果当前元素还是数组,继续递归,然后 res=res.concat(myFlat(arr[i])) 因为 concat是没有副作用与原数组的!!,否则是 res.push(arr[i]) 。
代码实现如下:
function myFlat(arr){
let res = [];
for(let i=0; i<arr.length; i++){
if(arr[i] instanceof Array){
res = res.concat(myFlat(arr[i]));
}else {
res.push(arr[i]);
}
}
return res;
}
let arr = [1,[2,3,[4,5]]];
console.log(myFlat(arr))
2、运行结果是符合预期的
53、什么样的布尔运算符可以在JavaScript中使用?
And”运算符(&&),'Or'运算符(||)和'Not'运算符(!)可以在JavaScript中使用。
*运算符没有括号。
54、用javascript实现观察者模式
ES5下的实现
再ES5中主要是通过Object.defineProperties
方法定义对象属性的设置(set)和获取(get),并再进行设置时执行相关的处理函数,如下:
var targetObj={
age:1
}
function observer(oldval,newval){
console.log('name属性的值从 '+oldval+'改变为 '+newval);
}
Object.defineProperty(targetObj,'name',{
enumerable:true,
configurable:true,
get:function(){
return name;
},
set:function(val){
//调用处理函数
observer(name,val);
name=val;
}
});
targetObj.name="www";
targetObj.name="mmm";
console.info('targetObj:',targetObj);
结果为:
name属性的值从 改变为 www
name属性的值从 www改变为 mmm
targetObj:{age:1,name:"mmm"}
ES6的实现(使用set方法实现)
class TargetObj{
constructor(age,name){
this.name=name;
this.age=age;
}
set name(val){
Observer(name,val);
name=val;
}
}
function Observer(oldval,newval){
console.info('name属性的值从 '+ oldval +' 改变为 ' + newval);
}
let targetObj2 = new TargetObj(1,'www');
targetObj2.name="mmm";
console.info(targetObj2);
55、简述一下面象对象的六法则
-
单一职责原则:一个类只做它该做的事情。(单一职责原则想表达的就是”高内聚”,写代码最终极的原则只有六个字”高内聚、低耦合”,所谓的高内聚就是一个代码模块只完成一项功能,在面向对象中,如果只让一个类完成它该做的事,而不涉及与它无关的领域就是践行了高内聚的原则,这个类就只有单一职责。我们都知道一句话叫”因为专注,所以专业”,一个对象如果承担太多的职责,那么注定它什么都做不好。这个世界上任何好的东西都有两个特征,一个是功能单一,好的相机绝对不是电视购物里面卖的那种一个机器有一百多种功能的,它基本上只能照相;另一个是模块化,好的自行车是组装车,从减震叉、刹车到变速器,所有的部件都是可以拆卸和重新组装的,好的乒乓球拍也不是成品拍,一定是底板和胶皮可以拆分和自行组装的,一个好的软件系统,它里面的每个功能模块也应该是可以轻易的拿到其他系统中使用的,这样才能实现软件复用的目标。)
-
开闭原则:软件实体应当对扩展开放,对修改关闭。(在理想的状态下,当我们需要为一个软件系统增加新功能时,只需要从原来的系统派生出一些新类就可以,不需要修改原来的任何一行代码。要做到开闭有两个要点:①抽象是关键,一个系统中如果没有抽象类或接口系统就没有扩展点;②封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而混乱,如果不清楚如何封装可变性,可以参考《设计模式精解》一书中对桥梁模式的讲解的章节。)
-
依赖倒转原则:面向接口编程。(该原则说得直白和具体一些就是声明方法的参数类型、方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不用具体类型,因为抽象类型可以被它的任何一个子类型所替代,请参考下面的里氏替换原则。)
-
里氏替换原则:任何时候都可以用子类型替换掉父类型。(关于里氏替换原则的描述,Barbara Liskov女士的描述比这个要复杂得多,但简单的说就是能用父类型的地方就一定能使用子类型。里氏替换原则可以检查继承关系是否合理,如果一个继承关系违背了里氏替换原则,那么这个继承关系一定是错误的,需要对代码进行重构。例如让猫继承狗,或者狗继承猫,又或者让正方形继承长方形都是错误的继承关系,因为你很容易找到违反里氏替换原则的场景。需要注意的是:子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题。)
-
接口隔离原则:接口要小而专,绝不能大而全。(臃肿的接口是对接口的污染,既然接口表示能力,那么一个接口只应该描述一种能力,接口也应该是高度内聚的。例如,琴棋书画就应该分别设计为四个接口,而不应设计成一个接口中的四个方法,因为如果设计成一个接口中的四个方法,那么这个接口很难用,毕竟琴棋书画四样都精通的人还是少数,而如果设计成四个接口,会几项就实现几个接口,这样的话每个接口被复用的可能性是很高的。Java中的接口代表能力、代表约定、代表角色,能否正确的使用接口一定是编程水平高低的重要标识。)
-
合成聚合复用原则:优先使用聚合或合成关系复用代码。(通过继承来复用代码是面向对象程序设计中被滥用得最多的东西,因为所有的教科书都无一例外的对继承进行了鼓吹从而误导了初学者,类与类之间简单的说有三种关系,Is-A关系、Has-A关系、Use-A关系,分别代表继承、关联和依赖。其中,关联关系根据其关联的强度又可以进一步划分为关联、聚合和合成,但说白了都是Has-A关系,合成聚合复用原则想表达的是优先考虑Has-A关系而不是Is-A关系复用代码,原因嘛可以自己从百度上找到一万个理由,需要说明的是,即使在Java的API中也有不少滥用继承的例子,例如Properties类继承了Hashtable类,Stack类继承了Vector类,这些继承明显就是错误的,更好的做法是在Properties类中放置一个Hashtable类型的成员并且将其键和值都设置为字符串来存储数据,而Stack类的设计也应该是在Stack类中放一个Vector对象来存储数据。记住:任何时候都不要继承工具类,工具是可以拥有并可以使用的,而不是拿来继承的。)
-
迪米特法则:迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。(迪米特法则简单的说就是如何做到”低耦合”,门面模式和调停者模式就是对迪米特法则的践行。对于门面模式可以举一个简单的例子,你去一家公司洽谈业务,你不需要了解这个公司内部是如何运作的,你甚至可以对这个公司一无所知,去的时候只需要找到公司入口处的前台美女,告诉她们你要做什么,她们会找到合适的人跟你接洽,前台的美女就是公司这个系统的门面。再复杂的系统都可以为用户提供一个简单的门面,Java Web开发中作为前端控制器的Servlet或Filter不就是一个门面吗,浏览器对服务器的运作方式一无所知,但是通过前端控制器就能够根据你的请求得到相应的服务。调停者模式也可以举一个简单的例子来说明,例如一台计算机,CPU、内存、硬盘、显卡、声卡各种设备需要相互配合才能很好的工作,但是如果这些东西都直接连接到一起,计算机的布线将异常复杂,在这种情况下,主板作为一个调停者的身份出现,它将各个设备连接在一起而不需要每个设备之间直接交换数据,这样就减小了系统的耦合度和复杂度,如下图所示。迪米特法则用通俗的话来将就是不要和陌生人打交道,如果真的需要,找一个自己的朋友,让他替你和陌生人打交。
56、谈谈垃圾回收机制方法以及内存管理
回收机制方式
1、定义和用法:垃圾回收机制(GC:Garbage Collection),执行环境负责管理代码执行过程中使用的内存。
2、原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。但是这个过程不是实时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行。
3、实例如下:
function` `fn1() {`` ``var` `obj = {name: ``'hanzichi'``, age: 10};``}``function` `fn2() {`` ``var` `obj = {name:``'hanzichi'``, age: 10};`` ``return` `obj;``}``var` `a = fn1();``var` `b = fn2();
fn1中定义的obj为局部变量,而当调用结束后,出了fn1的环境,那么该块内存会被js引擎中的垃圾回收器自动释放;在fn2被调用的过程中,返回的对象被全局变量b所指向,所以该块内存并不会被释放。
4、垃圾回收策略:标记清除(较为常用)和引用计数。
标记清除:
定义和用法:当变量进入环境时,将变量标记"进入环境",当变量离开环境时,标记为:"离开环境"。某一个时刻,垃圾回收器会过滤掉环境中的变量,以及被环境变量引用的变量,剩下的就是被视为准备回收的变量。
到目前为止,IE、Firefox、Opera、Chrome、Safari的js实现使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同。
引用计数:
定义和用法:引用计数是跟踪记录每个值被引用的次数。
基本原理:就是变量的引用次数,被引用一次则加1,当这个引用计数为0时,被视为准备回收的对象。
内存管理
1、什么时候触发垃圾回收?
垃圾回收器周期性运行,如果分配的内存非常多,那么回收工作也会很艰巨,确定垃圾回收时间间隔就变成了一个值得思考的问题。
IE6的垃圾回收是根据内存分配量运行的,当环境中的变量,对象,字符串达到一定数量时触发垃圾回收。垃圾回收器一直处于工作状态,严重影响浏览器性能。
IE7中,垃圾回收器会根据内存分配量与程序占用内存的比例进行动态调整,开始回收工作。
2、合理的GC方案:(1)、遍历所有可访问的对象; (2)、回收已不可访问的对象。
3、GC缺陷:(1)、停止响应其他操作;
4、GC优化策略:(1)、分代回收(Generation GC);(2)、增量GC
57、开发过程中遇到内存泄漏的问题
1、定义和用法:
内存泄露是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束。C#和Java等语言采用了自动垃圾回收方法管理内存,几乎不会发生内存泄露。我们知道,浏览器中也是采用自动垃圾回收方法管理内存,但由于浏览器垃圾回收方法有bug,会产生内存泄露。
2、内存泄露的几种情况:
(1)、当页面中元素被移除或替换时,若元素绑定的事件仍没被移除,在IE中不会作出恰当处理,此时要先手工移除事件,不然会存在内存泄露。
实例如下:
<div id=``"myDiv"``>`` ``<input type=``"button"` `value=``"Click me"` `id=``"myBtn"``>``</div>``<script type=``"text/javascript"``>`` ``var` `btn = document.getElementById(``"myBtn"``);`` ``btn.onclick = ``function``(){`` ``document.getElementById(``"myDiv"``).innerHTML = ``"Processing..."``;`` ``}``</script>
解决方法如下:
<div id=``"myDiv"``>`` ``<input type=``"button"` `value=``"Click me"` `id=``"myBtn"``>``</div>``<script type=``"text/javascript"``>`` ``var` `btn = document.getElementById(``"myBtn"``);`` ``btn.onclick = ``function``(){`` ``btn.onclick = ``null``;`` ``document.getElementById(``"myDiv"``).innerHTML = ``"Processing..."``;`` ``}``</script>
(2)、由于是函数内定义函数,并且内部函数--事件回调的引用外暴了,形成了闭包。闭包可以维持函数内局部变量,使其得不到释放。
实例如下:
function` `bindEvent(){`` ``var` `obj=document.createElement(``"XXX"``);`` ``obj.onclick=``function``(){`` ``//Even if it's a empty function`` ``}``}
解决方法如下:
function` `bindEvent(){`` ``var` `obj=document.createElement(``"XXX"``);`` ``obj.onclick=``function``(){`` ``//Even if it's a empty function`` ``}`` ``obj=``null``;``}
58、定义事件冒泡?
JavaScript允许DOM元素嵌套在一起。在这种情况下,如果单击子级的处理程序,父级的处理程序也将执行同样的工作。
59、一个特定的框架如何使用JavaScript中的超链接定位?
可以通过使用“target”属性在超链接中包含所需帧的名称来实现。
<a href=”newpage.htm” target=”newframe”>>New Page</a>
60、浏览器有哪些兼容问题,你封装过什么插件
1.cssHack 一丶Browserhacks 这个网站可以直接查询各种hack非常方便
2.通过Polyfill和shiv Polyfill就像一个镊子可以帮我们刮平很多浏览器之的兼容性问题 : 比如让不支持picture标签的引入picturefill插件就可以在不支持的浏览器使用picture标签 shiv和Polyfill差不多 htmlshiv : https://github.com/aFarkas/html5shiv 比如这个html5shiv: 作用:在ie678不支持新的html5标签,通过引入这个库,就可以让这些浏览器有能力识别这些标签,其实就是利用了ie的createElement <!--[if lt IE 9]>
<script src="bower_components/html5shiv/dist/html5shiv.js"></script>
<![endif]--> 像这样引入
3.respond 通过引入这样一个库可以让ie678支持媒体查询 连接: https://github.com/scottjehl/Respond
4.通过Modernizr 它可以查询浏览器对css3,html5的支持情况,如果浏览器支持某个特性,那么它就会向浏览器添加相对应的类,如果不支持它就会添加一个no开头的一个类 这是一个主动去检测兼容性的一种方式,对于一些实现性的,或者不确定兼容性的一些特性,建议使用这种主动性检测的方式,这样提供了一种主动性的解决方案 用法 :点击 download ,自己选择特性 ,点击 build ,点击拷贝 ,拷贝到一个新的 js 文件,这个 js 就可以检测是否可以兼容哪个特性 如果支持,那么html标签上就会多一个类比如 svg 就会有 class="svg",这样就可以自己写两类类名 .svg 和 .no-svg 分别引入不同的样式 具体用法参考官方文档