Web 前端面试题整理(不定时更新)

重要知识需要系统学习、透彻学习,形成自己的知识链。万不可投机取巧,临时抱佛脚只求面试侥幸混过关是错误的!

面试有几点需注意:

面试题目: 根据你的等级和职位的变化,入门级到专家级,广度和深度都会有所增加。

题目类型: 理论知识、算法、项目细节、技术视野、开放性题、工作案例。

细节追问: 可以确保问到你开始不懂或面试官开始不懂为止,这样可以大大延展题目的区分度和深度,知道你的实际能力。因为这种知识关联是长时期的学习,临时抱佛脚绝对是记不住的。

回答问题再棒,面试官(可能是你面试职位的直接领导),会考虑我要不要这个人做我的同事?所以态度很重要、除了能做事,还要会做人。

资深的前端开发能把absolute和relative弄混,这样的人不要也罢,因为团队需要的是:你这个人具有可以依靠的才能(靠谱)。

前端开发知识点:

HTML&CSS:
    对Web标准的理解、浏览器内核差异、兼容性、hack、CSS基本功:布局、盒子模型、选择器优先级、
    HTML5、CSS3、Flexbox

JavaScript:
    数据类型、运算、对象、Function、继承、闭包、作用域、原型链、事件、RegExp、JSON、Ajax、
    DOM、BOM、内存泄漏、跨域、异步装载、模板引擎、前端MVC、路由、模块化、Canvas、ECMAScript 6、Nodejs

其他:
    移动端、响应式、自动化构建、HTTP、离线存储、WEB安全、优化、重构、团队协作、可维护、易用性、SEO、UED、架构、职业生涯、快速学习能力

以下是面试题及其答案(这些知识点不要死记硬背,需要深入理解):

 

【HTML + CSS 】

1.对WEB标准以及W3C的理解与认识

标签闭合、标签小写、不乱嵌套,提高搜索机器人搜索几率;使用外链css和js脚本、结构行为表现的分离、文件下载与页面速度更快、内容能被更多的用户所访问、内容能被更广泛的设备所访问、更少的代码和组件,容易维 护、改版方便,不需要变动页面内容、提供打印版本而不需要复制内容、提高网站易用性;

 

2.xhtml和html有什么区别

HTML是一种基本的WEB网页设计语言,XHTML是一个基于XML的置标语言
最主要的不同:
XHTML 元素必须被正确地嵌套。
XHTML 元素必须被关闭。
标签名必须用小写字母。
XHTML 文档必须拥有根元素。

 

3.Doctype? 严格模式与混杂模式-如何触发这两种模式,区分它们有何意义?

历史原因:当早期的浏览器Netscape 4和Explorer 4对css进行解析时,并未遵守W3C标准,这时的解析方式就被我们称之为quirks mode(怪异模式),但随着W3C的标准越来越重要,众多的浏览器开始依照W3C标准解析CSS,仿照W3C标准解析CSS的模式我们叫做strict mode(严格模式) 

区别:严格模式是浏览器根据规范去显示页面;混杂模式是以一种向后兼容的方式去显示

意义:决定浏览器如何渲染网站(浏览器使用那种规范去解析网页)

触发:浏览器根据doctype是否存在和使用的是那种dtd来决定。

所谓的标准模式(严格模式)是指,浏览器按W3C标准解析执行代码;
怪异模式(混杂模式)则是使用浏览器自己的方式解析执行代码,因为不同浏览器解析执行的方式不一样,所以我们称之为怪异模式。
浏览器解析时到底使用标准模式还是怪异模式,与你网页中的DTD声明直接相关,DTD声明定义了标准文档的类型(标准模式解析)文档类型,会使浏览器使用相应的方式加载网页并显示,忽略DTD声明,将使网页进入怪异模式(quirks mode)。 使用 window.top.document.compatMode 可显示当前的模式,取值有
CSS1Compat 和 BackCompat

CSS1Compat 为标准模式,浏览器宽度 = document.documentElement.clientWidth; 元素的宽度则是元素的实际宽度,内容宽度 = width - (padding-left + padding-right + border-left-width + border-right-width)
BackCompat 为怪异模式,浏览器宽度 = document.body.clientWidth; 元素实际的宽度 = border-left-width + padding-left + width + padding-right + border-right-width;

我们通过简单代码试验下:

<div id="element"></div>
<style>
    #element{ width: 100px; height: 60px; padding: 10px;margin: 20px;border: 1px solid #9f1c24;}
</style>
<script>
    var compatMode = window.top.document.compatMode;
    var elem = document.getElementById("element");
    document.write("The compatMode is "+compatMode+".The Element's width is " + elem.offsetWidth + " px and it's height is "+elem.offsetHeight+" px");
</script>

IE6以上显示(标准模式):

The compatMode is CSS1Compat.The Element's width is 122 px and it's height is 82 px 

IE5显示(怪异模式):

The compatMode is BackCompat.The Element's width is 100 px and it's height is 60 px 

相关链接:严格模式与混杂模式-如何触发这两种模式,区分它们有何意义 || 深入理解浏览器兼容性模式

 

4.行内元素有哪些?块级元素有哪些?CSS的盒模型?

块级元素:div p h1-h6 form ol ul li 
行内元素: a b br i img span lable input select textarea
CSS盒模型:内容,border ,margin,padding

补充:块级元素和行内元素的区别

 

相关链接:行内元素与块级元素比较全面的区别和转换  

 

5.CSS引入的方式有哪些? link和@import的区别是?

 

引入的方式:内联、内嵌、外链、导入。
区别 :
1.link属于XHTML标签,而@import完全是CSS提供的一种方式。
link标签除了可以加载CSS外,还可以做很多其它的事情,比如定义RSS,定义rel连接属性,等,
@import就只能加载CSS了。

2.加载时间及顺序不同
使用link链接的css是客户端浏览你的网页时先将外部的CSS文件加载到网页当中,然后再进行编译显示,所以这种情况下显示出来的网页跟我们预期的效果一样,即使一个页面link多个css文件,网速再慢也是一样的效果;
而使用@import导入的CSS就不同了,客户端在浏览网页时是先将html的结构呈现出来,再把外部的CSS文件加载到网页当中,当然最终的效果也是跟前者是一样的,只是当网速较慢时会出现先显示没有CSS统一布局时的html网页,这样就会给阅读者很不好的感觉。这也是现在大部分网站的CSS都采用链接方式的最主要原因。

3.兼容性不同
由于@import是CSS2.1提出的所以老的浏览器不支持,
@import只有在IE5以上的才能识别,而link标签无此问题。

补充1:

CSS的浏览器支持如下:

CSS1:

CSS2:

CSS3:

相关链接:CSS选择器的浏览器支持

补充2:

CSS有三种引用方式:

a.行间样式,也称行内样式

最简单直接,直接对HTML标签使用style="..."

<div style="width: 300px;height: 100px;"></div>

缺点:HTML页面不纯净,文件体积太大,不利于蜘蛛爬行,后期维护不方便。

b.内部样式,也称内嵌样式

将CSS代码在写在<head></head>之间,并且用<style></style>进行声明

<style>
   #div { background-color: red;width: 300px;height: 100px;}
</style>

缺点:使用公共CSS代码时,每个页面都要定义。如果有很多页面,那么每个文件都会很大,后期维护难度也会很大。

c.外部样式,也称链接样式

只需要在在<head></head>之间通过link标签引用CSS样式表的链接就行。

<link rel="stylesheet" href="css/import-css-03.css">

优点:实现了页面框架代码和表现CSS代码的完全分离,使得前期制作和后期维护都十分方便。

 

另外,还有一种导入CSS样式的方法,采用@import导入CSS样式。在HTML初始化时,会被导入到HTML或者CSS文件中,称为文件的一部分。

HTML中使用如下:

<style>
      @import "css/import-css-03.css";
</style>

CSS样式表中使用如下:

@import "import-css-03.css";

不过不建议使用这种方法。

一直在HTML页面使用@import是可以的,但是你必须时刻记得要将@import 放到样式表的最前面,否则它将不会起作用。

而且如果在HTML中混合 link方式和@import方式,会破坏并行下载,而去进行逐个加载,这样会导致页面需要更多的时间才能加载完成。

另外在link的样式表里嵌套@import,同样会阻止并行加载代码,这样这是因为@import引用的文件只有在引用它的那个文件被下载、解析后,浏览器才会知道还有另一个CSS样式表需要下载,然后才去下载、构建 render tree等一系列操作。因此 css @import引起的CSS解析延迟会加长页面留白期。所以,要尽量避免使用CSS @import 而采用 link 标签的方式引入CSS样式表。

相关链接:不要使用@import (英文版:don’t use @import

 

6.css的position属性有哪些取值,它们的行为是什么?

position属性常用的取值static、relative、absolute、fixed
static:
元素框正常生成。块级元素生成一个矩形框,作为文档流的一部分,行内元素则会创建一个或多个行框,置于其父元素中。
relative(相对定位):
元素框偏移某个距离。元素仍保持其未定位前的形状,它原本所占的空间仍保留。
absolute(绝对定位):
元素框从文档流完全删除,并相对于其包含块定位。
包含块可能是文档中的另一个元素或者是初始包含块。元素原先在正常文档流中所占的空间会关闭,就好像元素原来不存在一样。元素定位后生成一个块级框,而不论原来它在正常流中生成何种类型的框。 fixed(固定定位): 元素框的表现类似于将 position 设置为 absolute,不过其包含块是视窗本身。
PS:fixed旧版本IE不支持;absolute的containing block计算方式跟正常流不同

相关链接:谈谈面试与面试题

 

7.CSS 中text-align:center在IE7和IE8下的区别

在IE7中,元素与文字都居中;

而在IE8下仅文字居中,元素并不居中

解决方法:可以在其子元素中加入CSS属性:margin:0 auto;

  

8.CSS选择符有哪些?哪些属性可以继承?优先级算法如何计算?内联和important哪个优先级高?

1.id选择器( # myid)
2.类选择器(.myclassname)
3.标签选择器(div, h1, p)
4.相邻选择器(h1 + p)
5.子选择器(ul > li)
6.后代选择器(li a)
7.通配符选择器( *8.属性选择器(a[rel = "external"])
9.伪类选择器(a:hover, li:nth-child)
visibility和cursor能够被所有元素继承;
letter-spacing,word-spacing,white-space,line-height,colo,font,font-family,font-size,font-style,font-variant,font-weight,text-decoration,text-transform,direction能够被内联元素继承;
text-indent和text-align会被终端元素继承;
list-style,list-style-type,list-style-posi-tion,list-style-image会被列表元素所继承;
border-col-lapse会被表格元素所继承
以下属性是不可继承的:border,height,display,margin,background,padding,min-height,overflow,max-height,width,min-width,max-width,position,right,top,left,bottom,float,z-index,clear,table-lay-out,ertical-align,nicode-bidi,age-break-after,age-break-before
每个ID选择符(#someid),加 0,1,0,0。
每个class选择符(.someclass)、每个属性选择符(形如[attr=”"]等)、每个伪类(形如:hover等)加0,0,1,0
每个元素或伪元素(:firstchild)等,加0,0,0,1
其他选择符包括全局选择符*,加0,0,0,0。相当于没加,不过这也是一种specificity,后面会解释。
按这些规则将数字串逐位相加,就得到最终计算得的specificity,然后在比较取舍时按照从左到右的顺序逐位比较。
!important 比内联样式的优先级高

相关链接:css优先级计算规则

 

9. img与background的区别

简单来说,img是内容部分的东西,background-image是修饰性的东西。

img:从页面元素来说,如果是页面中的图片是作为内容出现的,比如广告图片,比如产品图片,那么这个必然是用img了,因为这个是页面元素内容。页面元素内容最关键的一点就是,当页面没有样式的时候,还是能一眼看过去就知道什么是什么
background
-image:背景图片,修饰性的内容,在页面中可有可无。有,是为了让页面中视觉感受上更美;无,并不影响用户浏览网页获取内容。 其实说白了,背景图片就是通过样式加载后,让页面更漂亮而已,内容图片就是为了展示给用户的。假设有一天你的网页没有任何样式的时候,那么这个时候请想想你的网站上哪些图片是给用户看的,这样就足够了。

 

10.前端页面有哪三层构成,分别是什么?作用是什么?

网页分成三个层次,即:结构层、表示层、行为层

网页的结构层(structural layer)由 HTML 或 XHTML 之类的标记语言负责创建。
标签,也就是那些出现在尖括号里的单词,对网页内容的语义含义做出了描述,但这些标签不包含任何关于如何显示有关内容的信息。例如,P 标签表达了这样一种语义:“这是一个文本段。” 网页的表示层(presentation layer) 由 CSS 负责创建。 CSS 对“如何显示有关内容”的问题做出了回答。 网页的行为层(behavior layer)负责回答“内容应该如何对事件做出反应”这一问题。这是 Javascript 语言和 DOM 主宰的领域。

相关链接:http://www.cnblogs.com/hellman/p/4172220.html

 

11.css的基本语句构成是?

选择器{属性1:值1;属性2:值2;……}

 

12.你做的页面在哪些流览器测试过?这些浏览器的内核分别是什么?

IE(Trident)  火狐(Gecko) 谷歌(webkit) Opera(Presto)

相关链接:四种主要浏览器内核简介(Trident/Gecko/webkit/Presto)

 

13.写出几种IE6 BUG的解决方法

a. IE6双倍边距bug

当页面内有多个连续浮动时,如本页的图标列表是采用左浮动,此时设置li的左侧margin值时,在最左侧呈现双倍情况。如外边距设置为10px, 而左侧则呈现出20px,解决它的方法是在浮动元素上加上display:inline;的样式,这样就可避免双倍边距bug。

b.IE6下这两个层中间怎么有间隙(3像素问题及解决办法)

 

当使用float浮动容器后,在IE6下会产生3px的空隙,解决的办法是给.right也同样浮动 float:left 或者相对IE6定义.left margin-right:-3px;

c. 当子元素浮动且未知高度时,怎么使父容器适应子元素的高度?

这种情况可在父窗口加上 overflow:auto;zoom:1;这两个样式属性,overflow:auto;是让父容器来自适应内部容器的高度,zoom:1;是为了兼容IE6而使用的CSS HACK。zoom:1;通不过W3C的验证,这也是遗憾的一点,幸好IE支持<!–[if IE]>这种写法,可以专门针对IE来写单独的样式,所以可以把这个属性写在页面内的<!–[if IE]>中,这样就可以通过验证了。

d. 超链接访问过后hover样式就不出现的问题

解决方法是改变CSS属性的排列顺序: L-V-H-A
a:link { }   a:visited { }   a:hover { }   a:active { }

e.IE6文字溢出BUG

修正注释的写法。将 <!-- 这里是注释内容 -->写成<!--[if !IE]>这里是注释内容<![endif]-->

相关链接:IE6文字溢出bug解决办法

f.一个空格引发CSS失效

这段代码对<p>的首字符样式定义在IE6上看是没有效果的(IE7没测试),而在p:first-letter和{font- size:300%}加上空格,也就是p:first-letter {font-size:300%}后,显示就正常了。但是同样的代码,在FireFox下看是正常的。按道理说,p:first- letter{font-size:300%}的写法是没错的。那么问题出在哪里呢?答案是伪类中的连字符”-”。IE有个BUG,在处理伪类时,如果伪 类的名称中带有连字符”-”,伪类名称后面就得跟一个空格,不然样式的定义就无效。而在FF中,加不加空格都可以正常处理。

g. IE6中奇数宽高的BUG

解决方案就是将外部相对定位的div宽度改成偶数。

h.IE6下为什么图片下方有空隙产生

解决这个BUG的方法也有很多,可以是改变html的排版,或者定义img 为display:block
或者定义vertical-align属性值为vertical-align:top | bottom |middle |text-bottom
还可以设置父容器的字体大小为零,font-size:0

i. ie6下空标签高度问题

一个空div如果高度设置为0到19px,IE6下高度默认始终19PX。
例如:
.c{background-color:#f00;height:0px;/*给定任何小于20px的高度 */}
<div></div>
如果不让它默认为19PX。而是0PX的话
解决方法有3种:
1.css里面加上overflow:hidden;
2.div里面加上注释,<div><!– –></div>
3.css里面加上line-height:0;然后div里面加上&nbsp;,<div>&nbsp;</div>

j.修正重复文字bug

复杂的布局可以触发在浮动元素的最后一些字符可能出现在出现在清除元素下面的bug。这里有几个解决方法,有些是完美的,但是做一些反复试验也是必须的:确保所有的元素使用”display:inline;”在最后一个元素上使用一个”margin-right:-3px;”# 为浮动元素的最后一个条目使用一个条件注释,比如:<!–[if !IE]>Put your commentary in here…<![endif]–>在容器的最后元素使用一个空的div(它也有必要设置宽度为90%或类似宽度。)

 

14.标签上title与alt属性的区别是什么? 

1.含义不同
alt是当图片不存在时的替代文字;title是对图片的描述与进一步说明

2.在浏览器中的表现不同
在firefox和ie8中,当鼠标经过图片时title值会显示,而alt的值不会显示;只有在ie7以下版本中,如果没有设置title,当鼠标经过图片时alt的值会显示,如果设置了,优先显示title的值,即使是空值

对于网站seo优化来说,title与alt还有最重要的一点:
搜索引擎对图片意思的判断,主要靠alt属性。所以在图片alt属性中以简要文字说明,同时包含关键词,也是页面优化的一部分。条件允许的话,可以在title属性里,进一步对图片说明。

 

15.描述css reset的作用和用途

因为浏览器的品种很多,每个浏览器的默认样式也是不同的。通过重新定义标签样式。“覆盖”浏览器的CSS默认属性。
最简单的就是 *{margin:0 ;  padding:0}

 

16.解释css sprites,如何使用

CSS Sprites 将许过小的图片组合在一起,使用css定义背景属性,再利用 CSS的"background-image","background-repeat","background-position"的组合来控制图片的显示位置和方式,background-position可以用数字能精确的定位出背景图片的位置。 

这样就大大减少了HTTP请求的次数,减轻服务器压力,同时缩短了悬停加载图片所需要的时间延迟,使效果更流畅

相关链接:CSS Sprites: CSS图片合并技术详解

  

17.如何对网站的文件和资源进行优化?

1.尽可能减少http请求数(文件合并) 
2.使用CDN(内容分发网络) 
3.添加Expire/Cache-Control头 
4.启用Gzip压缩 
5.css放在页面最上面 
6.scrip放在页面最下面 
7.避免在css中使用Expressions 
8.把js和css放在外部文件中 
9.减少dns查询 
10.压缩javascript和css 
11.避免重定向 
12.移除重复脚本 
13.配置实体标签 
14.使用ajax缓存

 

18.什么是语义化的HTML?

根据内容的结构化(内容语义化),选择合式的标签(代码语义化),便于开发者的阅读和写出更加优雅的代码的同时让浏览器的爬虫和机器更好地解析

 

19.清除浮动的几种方式,各自的优缺点

1.使用空标签清除浮动:.clear{clear:both}(会添加大量无语义标签,结构与表现未分离,不利于维护)
2.父元素设置overflow:hidden(内容增多时容易造成不会自动换行的后果,导致内容被隐藏,无法显示需要一处的元素)
3.父元素设置overflow:auto (多个嵌套后,FF在某种情况下会造成内容全选;IE在mouseover造成宽度改变时会造成最外层模块出现滚动条)
4.父元素设置height(只适合高度固定的布局,要给出精确的高度,不利于维护)
3.是用:afert伪元素清除浮动(用于低版本IE浏览器无法识别)

相关链接:清除浮动的几种方法

 

 

【JavaScript】

1.javascript的typeof返回哪些数据类型

Object number function boolean underfind symbol(ES6)

 

2.例举3种强制类型转换和2种隐式类型转换?

强制(parseInt,parseFloat,Number)
隐式(== –)

1)我们先来进行强制转换操作:

<script>
    var strNum = '1.645';
    var strAnotherNum = "-1";
    var strAnotherNum2 = "2";
    var isTrue = true;

    //parseInt(参数1,参数2)将字符串转换成整数
    console.log(parseInt(strNum)); // 1
    console.log(parseInt(strAnotherNum + strNum)); //  -11
    console.log(parseInt(strNum + strAnotherNum)); // 1

    //parseFloat()将字符串转换成浮点数字
    console.log(parseFloat(strNum));//  1.645
    console.log(parseFloat(strAnotherNum + strNum));//  -11.645
    console.log(parseFloat(strNum + strAnotherNum));//  1.645
    console.log(parseFloat(strNum + strAnotherNum2));//  1.6452

    //Number() 函数把对象的值转换为数字
    console.log(Number(strNum));// 返回 1.645
    console.log(Number(strAnotherNum + strNum)); // -11.645
    console.log(Number(strNum + strAnotherNum)); // NaN
    console.log(Number(strNum + strAnotherNum2)); // 1.6452

    //string(参数):可以将任何类型转换成字符串
    console.log(String(isTrue)); // "true"
    console.log(String(strAnotherNum + strNum)); // "-11.645"
    console.log(String(strNum + strAnotherNum)); // "1.645-1"
    console.log(String(strNum + strAnotherNum2)); // "1.6452"

    // Boolean()可以将任何类型的值转换成布尔值
    console.log(Boolean(strNum)); //  true
    console.log(Boolean(strAnotherNum + strNum)); //  true
    console.log(Boolean(strNum + strAnotherNum)); //  true
</script>

2)我们再来进行隐式转换

<script>
    // (1) 四则运算
    // 加法运算符+是双目运算符,只要其中一个是string类型,表达式的值便是一个String
    // 对于其他的四则运算,只有其中一个是Number类型,表达式的便是一个Number
    // 对于非法字符的情况通常会返回NaN:'1'*'a'    // => NaN,这是因为parseInt(a)值为NaN,1*NaN还是NaN
    var a = "11";
    var b = 11;
    console.log(a+b); // "1111"
    console.log(a-b); // 0
    console.log(a*b); // 121
    console.log(a/b); // 1

    //(2).判断语句
    // 判断语句中的判断条件需要是 Boolean类型,所以条件表达式会被隐式转换为Boolean。其转换规则则同Boolean的构造函数。
    console.log(a==b);// true
    console.log(a!==b); // true
    console.log(a===b); //  false

    //(3) Native代码调用
    // JavaScript宿主环境都会提供大量的对象,它们往往不少通过JavaScript来实现的。
    // JavaScript给这些函数传入的参数也会进行隐式转换。
    // 例如BOM提供的alert方法接受String类型的参数:alert({a:1});  //=>[object Object]
    console.log({a:1}); // Object {a: 1}
</script>

补充:NaN这个特殊的Number与所有其他值都不相等,包括它自己:

NaN === NaN; // false

唯一能判断NaN的方法是通过isNaN()函数:

isNaN(NaN); // true

 

3.join() 和 split()的区别

join() 函数是把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔。
split() 函数是把字符串分割为字符串数组。

下面简单试验下:

<script>
    var arr = ["How","Are","You!"]
    var strArr = "1,2,3,4,5,6,7,8";
    console.log(arr.join()); // How,Are,You!
    console.log(arr.join('')); // HowAreYou!
    console.log(arr.join(' ')); // How Are You!
    console.log(strArr.split('')); // ["1", ",", "2", ",", "3", ",", "4", ",", "5", ",", "6", ",", "7", ",", "8"]
    console.log(strArr.split(' '));// ["1,2,3,4,5,6,7,8"]
    console.log(strArr.split(',')); // ["1", "2", "3", "4", "5", "6", "7", "8"]
</script>

 

4.数组方法pop()、push()、unshift()、shift()

pop()  -  删除并返回数组的最后一个元素
push() - 向数组的末尾添加一个或更多元素,并返回新的长度
unshift() - 向数组的开头添加一个或更多元素,并返回新的长度
shift() - 删除并返回数组的第一个元素

同样,我们简单试验下:

<script>
    var arr = [1,2,3,4,5];
    console.log(arr.pop()); // 5
    console.log(arr.join(","));// 1,2,3,4
    console.log(arr.push("Hello","Luka")); // 6
    console.log(arr.join(",")); // 1,2,3,4,Hello,Luka
    console.log(arr.unshift("Hi","Luka")); // 8
    console.log(arr.join(",")); // Hi,Luka,1,2,3,4,Hello,Luka
    console.log(arr.shift()); // Hi
    console.log(arr.join(",")); // Luka,1,2,3,4,Hello,Luka
</script>

 

5.事件绑定和普通事件有什么区别 

普通事件中的onclick是DOM 0级事件只支持单个事件,会被其他onclick事件覆盖
事件绑定中的addEventListener是DOM 2级事件可以添加多个事件而不用担心被覆盖

同样,我们简单试验下:

<input type="button" id="theButton" value="Click it"/>
<script>
    var btnClick = document.getElementById('theButton');
    // 用普通方法添加两个事件
    btnClick.onclick = function () {
        console.log("The is the first click event!"); // 不执行,不显示
    }
    btnClick.onclick = function () {
        console.log("The is the second click event!"); // 执行,会显示
    }
    // 用事件绑定添加两个事件
    btnClick.addEventListener('click',function () {
        console.log("The is the first click event listener!");// 执行,会显示
    })
    btnClick.addEventListener('click',function () {
        console.log("The is the second click event listener!");// 执行,会显示
    })
    // PS:这里只使用W3C的标准写法添加事件,没有兼容低版本的IE。
</script>

  

 

6.IE和DOM事件流的区别

a.事件流执行顺序的区别

IE采用冒泡型事件,Netscape使用捕获型事件,DOM使用先捕获后冒泡型事件.

示例:

<body> 
<div> 
<button>点击这里</button> 
</div> 
</body> 

冒泡型事件模型: button->div->body (IE事件流) 

捕获型事件模型:body->div->button (Netscape事件流) 

DOM 事件模型:body->div->button->button->div->body (先捕获后冒泡)

b.事件侦听函数的区别

IE 使用:

target.attachEvent(event, listener); //绑定函数 
target.detachEvent(event, listener); //移除绑定  

target: 文档节点、document、window 或 XMLHttpRequest

type: 字符串,事件名称,含“on”,比如“onclick”、“onmouseover”、“onkeydown”等。

listener :实现了 EventListener 接口或者是 JavaScript 中的函数。

this 指向 window对象。

示例:

<input type="button" id="theButton" value="Click it"/>
<script>
    var btnClick = document.getElementById('theButton');
    // 用事件绑定添加两个事件
    btnClick.attachEvent('onclick',function () {
        console.log("The is the first click event listener!");// 执行
    })
    btnClick.attachEvent('onclick',function () {
        console.log("The is the second click event listener!");// 执行
    })
    btnClick.attachEvent('onclick',LogThrid);
    btnClick.detachEvent('onclick',LogThrid);
    function LogThrid() {
        console.log("The is the third click event listener!");
    }
</script>

IE8及以下显示:

The is the second click event listener!
The is the first click event listener!

IE9-IE10显示:

The is the first click event listener!
The is the second click event listener!

IE11以上不支持此方法。 

DOM 使用:

target.addEventListener(type, listener, useCapture);  //绑定函数 
target.removeEventListener(type, listener, useCapture); //移除绑定 

target: 文档节点、document、window 或 XMLHttpRequest。

type: 字符串,事件名称,不含“on”,比如“click”、“mouseover”、“keydown”等。

listener :实现了 EventListener 接口或者是 JavaScript 中的函数。

useCapture :是否使用捕捉,一般用 false 。true为捕获阶段,false为冒泡阶段。

this 触发该事件的对象。

示例

<input type="button" id="theButton" value="Click it"/>
<script>
    var btnClick = document.getElementById('theButton');
    // 用普通方法添加两个事件
    btnClick.onclick = function () {
        console.log("The is the first click event!"); // 不执行,不显示
    }
    btnClick.onclick = function () {
        console.log("The is the second click event!"); // 执行,会显示
    }
    // 用事件绑定添加两个事件
    btnClick.addEventListener('click',function () {
        console.log("The is the first click event listener!");// 执行,会显示
    })
    btnClick.addEventListener('click',function () {
        console.log("The is the second click event listener!");// 执行,会显示
    })
    btnClick.addEventListener('click',LogThrid);
    btnClick.removeEventListener('click',LogThrid);
    function LogThrid() {
        console.log("The is the third click event listener!");
    }
    // PS:这里只使用W3C的标准写法添加事件,没有兼容低版本的IE。
</script>

控制台显示:

The is the second click event!
The is the first click event listener!
The is the second click event listener!

简单来说,区别有以下几点:

事件流执行顺序不一样;
参数不一样;
this 指向不一样。

 

 7.IE和标准下有哪些兼容性的写法

var ev = event || window.event;
var target = ev.srcElement||ev.target;
var clientWidth = document.documentElement.clientWidth || document.body.clientWidth;
var clientHeight = document.documentElement.clientHeight || document.body.clientHeight;

 

8.Ajax请求的时候 get 和 post 方式的区别

简单区别说明如下:

1.Get请求会将数据添加到URL中,通过这种方式传递到服务器,通常利用一个问号?代表URL地址的结尾与数据参数的开端,后面的参数每一个数据参数以“名称=值”的形式出现,参数与参数之间利用一个连接符&来区分。
而Post请求是将数据在HTTP主体中的,其组织方式不只一种,有&连接方式,也有分割符方式,可隐藏参数,传递大批数据,比较方便。
2.get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。但理论上,因服务器的不同而异.
3.get安全性非常低,post安全性较高。
4.应用不同。一般我们使用get进行简单的数据查询操作,比如通过产品ID查询对应的产品信息;而使用post进行复杂的增删查改数据操作,比如把产品添加到购物车。

我们来举个例子,可以进行简单的GET和POST操作,HTML代码如下:

    <div>
        <label for="txt_username">姓名:</label>
        <input id="txt_username" type="text" />
        <input type="button" value="Get" id="btnGet" />
        <input type="button" value="Post" id="btnPost" />
    </div>
    <div id="result"></div>

然后是我们的GET方法:

        document.getElementById('btnGet').onclick = handleGet;
        function handleGet() {
            //创建一个新的 XMLHttpRequest 对象
            var httpRequest = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
            var username = document.getElementById("txt_username").value;
            //给 onreadystatechange 事件设置一个事件处理器
            httpRequest.onreadystatechange = handleResponse;
            //添加参数,以求每次访问不同的url,以避免缓存问题
            var url = "/ajax/html4getpost.aspx?username=" + encodeURIComponent(username) + "&random=" + Math.random();
            //使用 open 方法来指定 HTTP 方法和需要请求的 URL (即告诉 httpRequest 对象你想要做的事)
            httpRequest.open("GET", url);
            //这里没有向服务器发送任何数据,所以 send 方法无参数可用
            httpRequest.send();
        }

POST方法:

        document.getElementById('btnPost').onclick = handlePost;
        function handlePost() {
            var httpRequest = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
            var username = document.getElementById("txt_username").value;
            httpRequest.onreadystatechange = handleResponse;
            var data = "username=" + encodeURIComponent(username) + "&random=" + Math.random();
            //不用担心缓存问题
            httpRequest.open("POST", "/ajax/html4getpost.aspx");
            //必须设置,否则服务器端收不到参数
            httpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
            //发送请求,要data数据
            httpRequest.send(data);
        }

公用的处理响应的方法:

        //处理响应
        //一旦脚本调用了 send 方法,浏览器就会在后台发送请求到服务器。因为请求是在后台处理的,所以Ajax 依靠事件来通知这个请求的进展情况。
        function handleResponse(e) {
            //当 onreadystatechange 事件被触发后,浏览器会把一个 Event 对象传递给指定的处理函数,target 属性则会被设为与此事件关联的XMLHttpRequest
            if (e.target.readyState == XMLHttpRequest.DONE && e.target.status == 200) { //请求成功
                document.getElementById("result").innerHTML = e.target.responseText; //显示被请求文档的内容
            }
        }

通过JavaScript代码我们可以看到区别如下:

a.因为如果通过get方式获取数据,如果Url和之前的一致,IE浏览器会从缓存中获取数据,而不会去请求服务器端,所以get方法在IE浏览器下有缓存问题;而post方法不需担心这个问题。

b.上面post方法需要设置Content-Type的值为application/x-www-form-urlencoded (窗体数据被编码为名称/值对)。

c.发送请求时,因为get请求的参数都在URL里,所以send函数没有发送的参数,而post请求在使用send方法时需要赋予参数。

 

我们看下客户端中请求的 html4getpost.aspx 的代码:

        protected void Page_Load(object sender, EventArgs e)
        {
            string username = string.Empty;
            if (Request.HttpMethod.ToUpper().Equals("GET"))
            {
                username = Request.QueryString["username"];
            }
            else {
                username = Request.Form["username"];
            }
            Response.Clear();
            string str = "姓名:"+username+"<br />时间:"+DateTime.Now.ToString();
            Response.Write(str);
            Response.End();
        }

这里服务器端是通过ASP.NET实现的,可以看到其区别:

在客户端使用get请求,服务器端使用 Request.QueryString 来获取参数,而post请求,服务器端使用 Request.Form 来获取参数。

另外当然服务器端还可以使用一个通用的获取参数的方式,即 Request["username"]。但是此方法存在一个问题,它会对QueryString,Form,ServerVariable进行遍历,如果发现符合要求的数据,那么就会停止向后搜寻。

比如我们简单的修改下上面post方法:

httpRequest.open("POST", "/ajax/html4request.aspx?username=LukaChao");

如果使用Request["username"]方法,即使我们在文本框中填写任何其他名称,输出的名称也会是 LukaChao:

 

下面我们简单看看在network下get请求和post请求的数据:

GET请求:

POST请求:

a.从请求URL,我们可以看出 GET请求是带着参数,而POST请求是不带参数的。

b.另外POST的请求头文件比GET请求多了三个参数,分部为Content-Type、Content-Length 和 Origin

c.而如果请求数据是一样的,那响应得到的数据也是一样的:

 

9.call和apply的区别

简单来说,其区别主要就是传递参数的不完全相同。

对于第一个参数意义都一样,但对于第二个参数:apply传入的是一个参数数组,也就是将多个参数合成为一个数组传入,而call则作为call的参数传入(从第二个参数开始)。

func.call(func1,var1,var2,var3) 对应的 apply写法为:func.apply(func1,[var1,var2,var3]);

所以当你的参数是明确知道数量时用call,而不确定时用apply,然后把通过 push 方法把参数传递进数组。

相关链接:JS中call和apply区别

 

10. B继承A的方法 

   function A(name,age) {
        this.name = name;
        this.age = age;
        this.showName = function () {
            console.log(this.name+"'s age is "+this.age+".")
        }
    }

    //1. 原型链(prototype)实现继承
    function B1() { }
    B1.prototype = new A("Luka",27);
    var b1 = new B1();
    B1.prototype.zodiac = "Snake";
    B1.prototype.showZodiac = function () {
        console.log(this.name +"'s zodiac is "+this.zodiac +".")
    }
    b1.showName();
    b1.showZodiac();

    //2. 构造函数实现继承
    function B2(name, age,starSign) {
        this.temp = A;
        this.temp(name,age);
        delete this.temp;
        this.name = name;
        this.age = age;
        this.starSign = starSign;
        this.showZodiac = function () {
            console.log(this.name+"'s star sign is "+this.starSign +".")
        }
    }
    var b2 = new B2("Luka",27,"Sagittarius");
    b2.showName();
    b2.showZodiac();

    //3. call , apply实现继承

    //call与aplly的异同:
    //1,第一个参数this都一样,指当前对象
    //2,第二个参数不一样:call的是一个个的参数列表;apply的是一个数组(arguments也可以)
    function B3(name,age) {
        A.call(this,name,age);
    }
    var b3 = new B3("Luka",27);
    b3.showName();

    function B4(name,age) {
        A.apply(this,[name,age]); // 等于 A.apply(this.arguments);
    }
    var b4 = new B3("Luka",27);
    b4.showName();

显示效果:

相关链接:js中继承的几种用法总结(apply,call,prototype)

 

11.AJAX请求时,如何解释 JSON 数据

eval();  //此方法不推荐,因为此方法解析时不会判断字符串是否合法,而且JSON对象中的JS方法也会被执行,这是非常危险的。
JSON.parse();  //推荐方法

简单举个栗子:

<script>
    var jsonData1 = '{"name":"Luka","age":"18"}';
    var jsonObj11 = eval('('+jsonData1+')'); // eval() 方法
    console.log(jsonObj11.name+"'age is "+jsonObj11.age); // 输出:Luka'age is 18
    var jsonObj12 = JSON.parse(jsonData1); // JSON.parse()方法
    console.log(jsonObj12.name+"'age is "+jsonObj12.age); // 输出:Luka'age is 18

    var jsonData2 = '{"name":console.log("Hello,Luka!"),"age":"18"}';
    var jsonObj21 = eval('('+jsonData2+')');
    console.log(jsonObj21.name+"'age is "+jsonObj21.age); // 会先输出:Hello,Luka!  然后输出  undefined'age is 18
    var jsonObj22 = JSON.parse(jsonData2);
    console.log(jsonObj22.name+"'age is "+jsonObj22.age); // 直接报错
</script>

相关链接:Ajax中解析Json的两种方法详解

 

12.写一个获取非行间样式的函数

    function getStyle(obj, attr) { //获取非行间样式,obj是对象,attr是值
        if(obj.currentStyle){ //针对IE 获取非行间样式
            return obj.currentStyle[attr];
        }else {
            return getComputedStyle(obj,false)[attr];
        }
    }

示例如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>获取非行间样式的函数</title>
    <style>
        * { text-align: center;}
        input { margin-top: 30px;padding:10px 20px;}
        #div { width: 500px;height: 300px;background: red;margin: 10px auto;}
    </style>
</head>
<body>
<input type="button" value="Style" id="btn" />
<div id="div"></div>
<script>
    function getStyle(obj, attr) { //获取非行间样式,obj是对象,attr是值
        if(obj.currentStyle){ //针对IE 获取非行间样式
            return obj.currentStyle[attr];
        }else {
            return getComputedStyle(obj,false)[attr];
        }
    }

    function css(obj, attr, value) { //对象,样式,值。传两个参数的时候为获取样式,3个是设置样式
        if(arguments.length == 2){ //arguments 参数数组。当参数数组长度为2 时,表示获取获取css样式
            return getStyle(obj,attr); // 通过getStyle 函数返回对象的非行间样式
        }else if(arguments.length == 3){ //传递参数参数时,设置某个对象的值
            obj.style[attr] = value;
        }
    }

    window.onload = function () {
        var oDiv = document.getElementById("div");
        var oBtn = document.getElementById("btn");
        oBtn.onclick = function () {
            console.log("width:"+getStyle(oDiv,"width")); // width:500px
            css(oDiv,"width","300px");
            console.log("width:"+css(oDiv,"width")); // width:300px
        }
    }
</script>
</body>
</html>

补充:

下面看一个例子:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>获取CSS的值</title>
    <style>
        #div1 { width: 200px;height: 80px;background: red;}
    </style>
</head>
<body>
<div id="div1"></div>
<div id="div2" style="width: 100px;height: 40px;background: blue;"></div>
<script>
    var objDiv1 = document.getElementById("div1");
    console.log("Div1's width:"+objDiv1.style.width); // Div1's width:

    var objDiv2 = document.getElementById("div2");
    console.log("Div2's width:"+objDiv2.style.width); // Div2's width:100px

    objDiv1.style.width = "250px";
    console.log("Div1's width:"+objDiv1.style.width); // Div1's width:250px
</script>
</body>
</html>

通过最终控制台的显示结果,我们可以发现用document.getElementById(‘element').style.xxx可以获取元素的样式信息,可是它获取的只是DOM元素style属性里的样式规则,对于通过class属性引用的外部样式表,就拿不到我们要的信息了。 而通过js来改变对象的样式时,改变的则是对象的行间样式。

但是我们通过上面的方法就可以获取我们想要的CSS数据:

<script>
    function getStyle(obj, attr) { //获取非行间样式,obj是对象,attr是值
        if(obj.currentStyle){ //针对IE 获取非行间样式
            return obj.currentStyle[attr];
        }else {
            return getComputedStyle(obj,false)[attr];
        }
    }
    var objDiv1 = document.getElementById("div1");
    console.log("Div1's width:"+getStyle(objDiv1,"width")); // Div1's width:200px
    var objDiv2 = document.getElementById("div2");
    console.log("Div2's width:"+getStyle(objDiv2,"width")); // Div2's width:100px
</script>

 

 

13.事件委托是什么

事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

通俗的讲,事件就是onlick、onmouseover、onmouseout等;而委托就是让别人来做,这个事件本来是加在某些元素上的,然而你可以加到别人身上来做,完成这个事件。就像收快递一样,你不必自己亲自去收,可以让别人代收。

这原理就是利用事件冒泡,把事件加到父级元素上,触发执行效果。

我们来举个例子(每个li元素都可以触发改变其背景色):

<ul id="names">
    <li>刘备</li>
    <li>关羽</li>
    <li>张飞</li>
</ul>
<script>
    window.onload = function () {
        var oUl = document.getElementById("names");
        var aLi = oUl.getElementsByTagName("li");
        for(var i=0;i<aLi.length;i++){
            aLi[i].onmousemove = function () {
                this.style.backgroundColor = "red";
            }
            aLi[i].onmouseout = function () {
                this.style.backgroundColor = "";
            }
        }
    }
</script>

但是如果我们可能有很多个li元素,然后使用for循环的话会比较影响性能。

下面我们通过事件委托的方法来实现,HTML不变:

<script>
    window.onload = function () {
        var oUl = document.getElementById("names");/*
        * 这里要用到事件源:event对象,事件源不管在哪个事件中,只要你操作的元素就是事件源
        * IE: window.event.srcElement
        * 标准下:event.target
        * nodeName:元素的标签名
        * */
        oUl.onmouseover = function (e) {
            var target = TargetGet(e);
            if(target.nodeName.toLowerCase() === "li"){
                target.style.backgroundColor = "red";
            }
        }
        oUl.onmouseout = function (e) {
            var target = TargetGet(e);
            if(target.nodeName.toLowerCase() === "li"){
                target.style.backgroundColor = "";
            }
        }

        function TargetGet(e) {
            // 兼容性的写法,替代if...else...
            e = e||window.event;
            var target = e.target||e.srcElement;
            return target;
        }
    }
</script>

很明显,这里没有通过for去循环添加事件,而是把事件委托给了其父元素ul,这样大大提高了性能。

我们再来举个例子(在上面的基础上,我们添加一个按钮,点击按钮,可以动态添加li元素):

不用委托,我们的代码是这样的:

<input type="button" id="btn" value="添加武将" />
<ul id="names">
    <li>刘备</li>
    <li>关羽</li>
    <li>张飞</li>
</ul>
<script>
    var oUl = document.getElementById("names");
    var oLi = oUl.getElementsByTagName("li");
    var oBtn = document.getElementById("btn");
    var index = 1;
    for(var i=0;i<oLi.length;i++){
        oLi[i].onmouseover = function () {
            this.style.backgroundColor = "red";
        }
        oLi[i].onmouseout = function () {
            this.style.backgroundColor ="";
        }
    }
    oBtn.onclick = function () {
        var oLi = document.createElement("li");
        oLi.innerText = "武将"+index;
        index++;
        oUl.appendChild(oLi);
    }
</script>

但是这样,你会发现新添加的li元素并不能通过鼠标的移入移出改变其背景色。这是因为变色事件在添加li元素之前已经遍历完了。

我们再来通过事件委托来实现这个例子,HTML代码一样(我们只需要在原先的基础上添加li元素事件就行了,不需要改变事件委托事件):

<script>
    window.onload = function () {
        var oUl = document.getElementById("names");
        var oBtn = document.getElementById("btn");
        var index = 1;
        oUl.onmouseover = function (e) {
            var target = TargetGet(e);
            if(target.nodeName.toLowerCase() === "li"){
                target.style.backgroundColor = "red";
            }
        }
        oUl.onmouseout = function (e) {
            var target = TargetGet(e);
            if(target.nodeName.toLowerCase() === "li"){
                target.style.backgroundColor = "";
            }
        }

        oBtn.onclick = function () {
            var oLi = document.createElement("li");
            oLi.innerText = "武将"+index;
            index++;
            oUl.appendChild(oLi);
        }
        function TargetGet(e) {
            // 兼容性的写法,替代if...else...
            e = e||window.event;
            var target = e.target||e.srcElement;
            return target;
        }
    }
</script>

你可以看到即使我们新添了li元素,但是我们把事件委托给了其父元素ul,所以li元素的改变背景色的效果还在。

相关链接:js中的事件委托或是事件代理详解

 

14.闭包是什么,有什么特性,对页面有什么影响

官方的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

通俗的说法是,函数定义和函数表达式位于另一个函数的函数体内。而且这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。也就是说,内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必须访问其外部函数的局部变量、参数以及其他内部函数。这些局部变量、参数和函数声明(最初时)的值是外部函数返回时的值,但也会受到内部函数的影响。

简单来说,就是闭包能够读取其他函数内部变量的函数,即在外面可以调用函数中的函数变量,其实它就是将函数内外连接起来的桥梁。

举个例子:

<script>
    function a() {
        var i = 0;
        function b() {
            console.log(++i);
        }
        return b;
    }
    var c = a();
    c();
</script>

这段代码有两个特点:a.函数b嵌套在函数a内部;b.函数a返回函数b;

引用关系图如下:

这样在执行完 var c = a()  后,变量c 实际上是指向了函数b,b中用到了变量i,在执行c() 后就会在控制台显示i的值(第一次为1)。这段代码其实就创建了一个闭包。因为函数a 外的变量c 引用了函数a 内的函数b,也就是说:

当函数a的内部函数b被函数a外的一个变量引用时,就创建了我们通常所谓的“闭包”。

当函数b执行的时候也会像以上步骤一样。因此,执行时b的作用域链包含了三个对象:b的活动对象、a的活动对象和window对象,如下图所示:

如图所示,当在函数b中访问一个变量的时候,搜索的顺序是:

a.先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数a的活动对象,以此查找,直到找到为止。

b.如果函数b存在 prototype 原型对象,则在查找完自身的活动对象后查找自身的原型对象,再继续查找。这就是JavaScript中的变量查找机制。

c.如果整个作用域链上都无法找到,则返回 underfined。

用途:

a.闭包可以读取函数内部变量

b.将函数内部变量的值始终保持在内存中

再举个例子:

<script>
    function a() {
        var i = 1;
        iAdd = function () {
            i++;
        }
        function b() {
            console.log(i);
        }
        return b;
    }
    var c = a();
    c(); // 1
    iAdd();
    c(); // 2
</script>

此例中的c 实际上就是闭包函数b,它一共运行了两次,第一次值为1,第二次值为2,这就说明i 一直在内存中,而不是第一次a函数调用之后就自动清除了。

另外注意 iAdd = function(){ i++; } ,这里的iAdd 是全局变量,且它的值为匿名函数,其实也是一个闭包。

优势:

a.保护函数内的变量安全,以最开始的例子为例。函数a 中变量i 只有函数b才能访问,而无法通过其他途径访问到,因此保护了i 的安全性。

b.在内存中一直维持一个变量。以前面的例子为例,由于闭包,函数a中的变量i一直存在与内存中,因此每次执行c(),都会给i 自增加1。

c.通过保护变量的安全实现了JS私有属性和私有访问(不能被外部访问)。

特性:

a.封闭性:外界无法访问闭包内部的数据,如果在闭包内声明变量,外界是无法访问的,除非闭包主动向外界提供访问接口;

b.持久性:一般的函数,调用完毕后,系统自动注销,而对于闭包来说,在外部函数被调用后,闭包结构依然保存着。系统中,闭包中的数据依然存在,从而实现对数据的持久利用。

优点:

a.减少全局变量

b.减少传递函数的参数量

c.封装

缺点:

使用闭包会占有内存资源,过多的使用会导致内存溢出等。所以用时需要及时删除变量或者少用。


相关链接:深入理解JavaScript的闭包特性 如何给循环中的对象添加事件 

补充 - 变量的作用域:

当JS当中的一个变量的作用域(scope)是程序中定义这个变量的区域。变量分为两类:全局(global)变量和局部变量。其中全局变量的作用域是全局性的,即在JavaScript代码中,它处处有定义。

而在函数之内声明的变量,就只有在函数体内有定义。它们是局部变量,作用域是局部性的。函数的参数也是局部参数,它们只在函数体内部有定义。

我们可以借助JavaScript的作用域链(scope chain)更好地了解变量的作用域。每个JavaScript执行环境都有一个和它关联在一起的作用域。这个作用域是一个对象列表或对象链。当JavaScript代码需要查询变量x(如下图)的值时(这个过程叫做变量解析( variable name resolution)),它就开始查看该链的第一个对象。如果那个对象有一个名为x的函数,那么就采用那个属性的值。如果第一个对象没有名为x的属性,JavaScript就会继续查询链中的第二个对象。如果第二个对象仍然没有名为x的属性,那么就继续查询下一个对象,以此类推。如果查到最后(指顶层代码中)不存在这个属性,那么这个变量的值就是未定义的。

我们来举个简单的例子(例子1):

//定义全局变量
var i = 1;
function one(){
  console.log(i);
}
one();

很明显这是个全局变量,最后控制台会显示1

再来看一个例子(例子2):

function two(){
  var i = 1;
}
console.log(i);

这里i是个局部变量,外部不能引用。所以会报错。

我们在看一个例子(例子3):

var i = 1;
function one(){
   console.log(i);  
   var i = 2;
}
one();

乍一看,你也许会觉得结果会显示1,但结果其实是 undefined。

作用域链图中很明确的表示出:

在变量解析过程中,首先查找局部的作用域,然后再查找上层作用域。在例子1中的函数中没有定义变量i,所以查找上层作用域(全局作用域),进而进行输出其值。但是在例子2中的函数内定义了变量i (无论在after之后还是之前定义变量,都认为在此作用域拥有变量i),于是不再向上层的作用域进行查找,直接输出i。然而在例子3中,函数内部依然定义了变量i,于是不会查找全局作用域,然而此时的局部变量没有赋值,所以输出是 undefined。

 

15.如何阻止事件冒泡和默认事件

在使JavaScript编程时会遇到一个问题,就是当你给HTML添加事件时,由于浏览器默认的冒泡型事件触发机制,所以会触发你不想触发的事件,那么通过下面的函数可以解决这个问题:

a.阻止事件冒泡,使成为捕获型事件触发机制:

function stopBubble(e) {
      // 如果提供了事件对象,则这是个非IE浏览器
      if(e && e.stopPropagation){
          // 因此它支持W3C的stopPropagation()方法
          e.stopPropagation();
      }else {
          // 否则,我们需要使用IE的方式来取消事件冒泡
            window.event.cancelBubble = true;
      }
 }

b.当按键后,不希望按键继续传递给如HTML文本框对象时,可以取消返回值.即停止默认事件默认行为. 

// 阻止浏览器的默认行为
  function stopDefault(e) {
      // 阻止默认浏览器动作(W3C)
      if(e && e.preventDefault){
          e.preventDefault();
      }else {
          //IE 中阻止函数器默认动作的方式
          window.event.returnValue = false;
      }
      return false;
 }

我们来通过下面的例子看下效果:

<div id="c1">测试的文字,这里是样式C1,单击以冒泡的形式触发事件.</div><hr/>
<div id="c2">测试的文字,这里是样式C2,单击以捕获的形式触发事件.</div><hr/>
<div><input id="txt1" name="txt1" type="text" /></div><hr/>
<script>
    window.onload = function () {
        var txt = document.getElementById("txt1");
        document.getElementById("c1").onclick = function () {
            console.log("你点击了第一个div");
        }
        document.getElementById("c2").onclick = function (e) {
            console.log("你点击了第二个div");
            stopBubble(e);
        }
        document.onclick = function () {
            console.log("你点击了document");
        }
        txt.value = "123";
        txt.onclick = function (e) {
            stopBubble(e);
        }
        txt.onkeydown = function (e) {
            stopDefault(e);
            var lKeyCode = (navigator.appname=="Netscape")?event.which:event.keyCode;
            console.log("你按下了键值:"+ lKeyCode);
        }
    };
    function stopBubble(e) {
        // 如果提供了事件对象,则这是个非IE浏览器
        if(e && e.stopPropagation){
            // 因此它支持W3C的stopPropagation()方法
            e.stopPropagation();
        }else {
            // 否则,我们需要使用IE的方式来取消事件冒泡
            window.event.cancelBubble = true;
        }
    }
    function stopDefault(e) {
        // 阻止默认浏览器动作(W3C)
        if(e && e.preventDefault){
            e.preventDefault();
        }else {
            //IE 中阻止函数器默认动作的方式
            window.event.returnValue = false;
        }
        return false;
    }
</script>

我们点击第一个div文本,会发现控制台会显示:

你点击了第一个div
你点击了document

而我们点击第二个div文本时,控制台会显示:

你点击了第二个div

这是因为 stopBubble()方法阻止了事件冒泡。

然后,我们点击文本框,然后输入点击数字键1,我们会发现,控制台会显示:

你按下了键值:49

并且文本框内还是原来的文本"123",没有改变。这是因为 stopDefault 函数阻止了浏览器的默认动作。

相关链接:HTML DOM Event 对象

 

16.添加、删除、替换、插入到某个接点的方法

element.appendChild(); //向元素添加新的子节点,作为最后一个子节点
element.removeChild(); //从元素中移除子节点
element.replaceChild(); //替换元素中的子节点
element.insertBefore(); //指定的已有的子节点之前插入新节点

a.添加节点

<input type="button" id="btnAdd" value="添加武将" />
<ul id="names">
    <li>刘备</li>
    <li>关羽</li>
    <li>张飞</li>
</ul>
<script>
    var index = 1;
    document.getElementById("btnAdd").onclick = function () {
        // appendChild() 方法向节点添加最后一个子节点
        var node = document.createElement("li");
        var textnode = document.createTextNode("武将"+index++);
        node.appendChild(textnode);
        document.getElementById("names").appendChild(node);
    }
</script>

您也可以使用 appendChild() 方法从一个元素向另一个元素中移动元素,示例如下:

<ul id="names1">
    <li>刘备</li>
    <li>关羽</li>
    <li>张飞</li>
</ul>
<ul id="names2">
    <li>诸葛亮</li>
    <li>庞统</li>
</ul>
<p>请点击按钮把文臣从第二个列表移动到第一个列表中。</p>
<input type="button" id="btnAdd" value="移动" />
<script>
        document.getElementById("btnAdd").onclick = function () {
            // appendChild() 方法向节点添加最后一个子节点
            var eleFrom = document.getElementById("names2");
            var node = eleFrom.firstElementChild||eleFrom.firstChild;
            document.getElementById("names1").appendChild(node);
        }
</script>

b.删除节点

<input type="button" id="btnDelete" value="删除" />
<ul id="names">
    <li>刘备</li>
    <li>关羽</li>
    <li>张飞</li>
</ul>
<script>
    document.getElementById("btnDelete").onclick = function () {
        var names = document.getElementById("names");
        var node = names.firstElementChild||names.firstChild;
        // removeChild() 方法指定元素的某个指定的子节点。
        // 以 Node 对象返回被删除的节点,如果节点不存在则返回 null。
        names.removeChild(node);
    }
</script>

c.替换节点

<input type="button" id="btnUpdate" value="替换主公" />
<ul id="names"><li>刘备</li><li>关羽</li><li>张飞</li>
</ul>
<script>
    document.getElementById("btnUpdate").onclick = function () {
        // 首先创建一个新的文本节点
        var textNode = document.createTextNode("曹操");
        // 然后替换首个列表项中的首个子节点
        var item = document.getElementById("names").childNodes[0];
        // node.replaceChild(newnode,oldnode) 方法用新节点替换某个子节点
        // 这个新节点可以是文档中某个已存在的节点,或者您也可创建新的节点
        item.replaceChild(textNode,item.childNodes[0]);
    }
</script>

d.插入节点

<input type="button" id="btnDelete" value="插入主公" />
<ul id="names">
    <li>关羽</li>
    <li>张飞</li>
</ul>
<script>
    document.getElementById("btnDelete").onclick = function () {
        // 首先请创建一个 LI 节点
        var newItem = document.createElement("li");
        // 然后创建一个文本节点
        var textNode = document.createTextNode("刘备");
        // 然后向这个 LI 节点追加文本节点
        newItem.appendChild(textNode);
        // 最后在列表中的首个子节点之前插入此 LI 节点
        var names = document.getElementById("names");
        // insertBefore() 方法在您指定的已有子节点之前插入新的子节点。
        names.insertBefore(newItem,names.childNodes[0]);
    }
</script>

您也可以使用 insertBefore 方法插入/移动已有元素

<input type="button" id="btnDelete" value="插入主公" />
<ul id="names"><li>刘备</li><li>关羽</li><li>张飞</li></ul>
<script>
    document.getElementById("btnDelete").onclick = function () {
        var names = document.getElementById("names");
        var node= names.lastChild;
        var list=document.getElementById("names");
        list.insertBefore(node,list.childNodes[0]);
    }
</script>

 

17.解释jsonp的原理,以及为什么不是真正的ajax

原理:

静态资源是不受域策略限制的,可以加载任意域的脚本、样式、图片等静态资源,JSOP就是利用加载<script>标签没有跨域限制,来实现跨域获取数据的。

区别:

1、ajax和jsonp这两种技术在调用方式上“看起来”很像,目的也一样,都是请求一个url,然后把服务器返回的数据进行处理,因此jquery和ext等框架都把jsonp作为ajax的一种形式进行了封装;
2、但ajax和jsonp其实本质上是不同的东西。ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加<script>标签来调用服务器提供的js脚本。
3、所以说,其实ajax与jsonp的区别不在于是否跨域,ajax通过服务端代理一样可以实现跨域,jsonp本身也不排斥同域的数据的获取。
4、还有就是,jsonp是一种方式或者说非强制性协议,如同ajax一样,它也不一定非要用json格式来传递数据,如果你愿意,字符串都行,只不过这样不利于用jsonp提供公开服务。

简单函数封装如下:

function getJSONP(url) {
    var oScript = document.createElement('script');
    oScript.setAttribute('type', 'text/javascript');
    oScript.src = url;
    document.body.appendChild(oScript);
     oScript.onload = function () {   //  请求成功后移除标签
    console.log('请求成功');
    oScript.remove();
    }
    oScript.onerror = function () {   //  失败的时候也要移除标签
     console.log('请求错误, 请重试');
     oScript.remove();
    };
};

相关链接:简单描述JSON跟JSONP的区别以及实战

 

18.Javascript的本地对象,内置对象和宿主对象 

简单来说,
本地对象为 Array,Object,RegExp等可以通过 New 实例化的类;
内置对象为Global 和Math 这两个不必明确实例化的对象,它们已经被实例化了;
宿主对象为浏览器自带的document,window等对象,所有BOM 和DOM 对象都是宿主对象。

相关链接:ECMAScript 对象类型

 

19.document.load 和document.ready的区别

1.load是当页面所有资源全部加载完成后(包括DOM文档树,css文件,js文件,图片资源等),执行一个函数
问题:如果图片资源较多,加载时间较长,onload后等待执行的函数需要等待较长时间,所以一些效果可能受到影响
2.$(document).ready()是当DOM文档树加载完成后执行一个函数 (不包含图片,css等)所以会比load较快执行
在原生的jS中不包括ready()这个方法,只有load方法就是onload事件

 

20.”==”和“===”的不同

前者会自动转换类型
后者不会

 

21.Javascript的同源策略

同源策略,即拥有相同的协议(protocol),端口(如果指定),主机(域名)的两个页面是属于同一个源。
页面只能在同一个源中进行资源的交互。

相关链接:JavaScript 的同源策略

 

22.编写一个数组去重的方法

// 方法一(常规方法)
// 思路:
// 1.构建一个新的数组存放结果
// 2.for循环中每次从原数组中取出一个元素,用这个元素循环与结果数组对比
// 3.若结果数组中没有该元素,则存到结果数组中
Array.prototype.UniqArray1 = function () {
    var res = [this[0]];
    for(var i=1;i<this.length;i++){
        var repeat = false;
        for(var j=0;j<res.length;j++){
            if(this[i] === res[j]){
                repeat = true;
                break;
            }
        }
        if(!repeat){
            // 向数组的末尾添加一个或多个元素,并返回新的长度
            res.push(this[i]);
            // 要想数组的开头添加一个或多个元素,请使用 unshift() 方法
        }
    }
    return res;
}

// 方法二(比方法一效率要高)
//  思路:
// 1.先将原数组进行排序
// 2.检查原数组中的第i个元素与结果数组中的最后一个元素是否相同,因为已经排序,所以重复元素会在相邻位置
// 3.如果不相同,则将该元素存入结果数组中
// 此方法也有一定的局限性,因为在去重前进行了排序,所以最后返回的去重结果也是排序后的。如果要求不改变数组的顺序去重,那这种方法便不可取了。
Array.prototype.UniqArray2 = function () {
    this.sort(); //对数组的元素进行排序
    var res = [this[0]];
    for(var i=0;i<this.length;i++){
        if(this[i] !== res[res.length-1]){
            res.push(this[i]);
        }
    }
    return res;
}

// 方法三(推荐使用)
// 思路:
// 1.创建一个新的数组存放结果
// 2.创建一个空对象
// 3.for循环时,每次取出一个元素与对象进行对比,如果这个元素不重复,则把它存放到结果数组中,
// 同时把这个元素的内容作为对象的一个属性,并赋值为1,存入到第2步建立的对象中。
// 说明:至于如何对比,就是每次从原数组中取出一个元素,然后到对象中去访问这个属性,如果能访问到值,则说明重复。
Array.prototype.UniqArray3 = function () {
    var res = [];
    var obj = {};
    for(var i=0;i<this.length;i++){
        if(!obj[this[i]]){
            res.push(this[i]);
            obj[this[i]] = 1;
        }
    }
    return res;
}

 我们来运行下:

<script>
    var array1 = [1,2,8,6,4,7,6,12,8];
    console.log(array1.UniqArray1());
    var array2 = [1,2,"Hello",6,4,2,6,"Hello",8];
    console.log(array2.UniqArray2());
    var array3 = ["Hello","Luka","Nice to meet you","Luka","Hi","Luka"];
    console.log(array3.UniqArray3());
</script>

运行结果:

 

23.JavaScript中const、var和let区别

(1)const定义的变量不可以修改,而且必须初始化

const a = 2;
//const b;//会报语法错误 Uncaught SyntaxError: Missing initializer in const declaration
console.log('a的值为:'+a); //会输出:a的值为:2
//a = 5; //会报类型错误:Uncaught TypeError: Assignment to constant variable.

(2) var定义的变量可以修改,如果不初始化会输出undefined,不会报错

var b = 2;
var c;
console.log("b的值为:"+b+",c的值为:"+c);//b的值为:2,c的值为:undefined
function changeValue1(){
 var b = 6;
 c = 9;
 console.log("b的值为:"+b+",c的值为:"+c);//b的值为:6,c的值为:9
}
changeValue1();
console.log("b的值为:"+b+",c的值为:"+c);//b的值为:2,c的值为:9

(3) let是块级作用域,函数内部使用let定义后,对函数外部无影响。

var d = 3;var e = 3;
console.log("d的值为:"+d+",e的值为:"+e);//d的值为:3,d的值为:3
function changeValue2(){
 var d = 6;
 e = 9;
 console.log("d的值为:"+d+",e的值为:"+e);//d的值为:6,d的值为:9
}
changeValue2();
console.log("d的值为:"+d+",e的值为:"+e);//d的值为:3,d的值为:9

PS:

(1) let一定在严格模式下执行!

var varText1 = "varText OK.";
let letText1 = "letText OK.";
console.log(varText1);//varText OK.
console.log(letText1);//letText OK.

console.log(varText2);//undefined
console.log(letText2);//Uncaught ReferenceError: letText2 is not defined
var varText2 = "varText OK.";
let letText2 = "letText OK.";

(2) 声明后未赋值,表现相同;

var varText3;
let letText3;
console.log(varText3);//undefined
console.log(letText3);//undefined

(3) 重复声明同一个变量let报错,而 var 不会;

let letText4 = "letText OK.";
let letText4 = "letText changed.";// 直接报错 Uncaught SyntaxError: Identifier 'letText4' has already been declared
var varText4 = "varText OK.";
var varText4 = "varText changed.";
console.log(varText4);//varText changed.

 

24.Math.round(),Math.ceil(),Math.floor()的区别

(1) Math.round():把数四舍五入为最接近的整数。

根据“round”的字面意思“附近、周围”,可以猜测该函数是求一个附近的整数,看下面几个例子就明白。

小数点后第一位<5
正数:Math.round(11.46)=11
负数:Math.round(-11.46)=-11

小数点后第一位>5
正数:Math.round(11.68)=12
负数:Math.round(-11.68)=-12

小数点后第一位=5
正数:Math.round(11.5)=12
负数:Math.round(-11.5)=-11

总结:(小数点后第一位)大于五全部加,等于五正数加,小于五全不加。

(2) Math.ceil():对数进行上舍入。

根据“ceil”的字面意思“天花板”去理解;

例如:
Math.ceil(11.46)= Math.ceil(11.68)= Math.ceil(11.5)=12
Math.ceil(-11.46)= Math.ceil(-11.68)= Math.ceil(-11.5)=-11

(3) Math.floor():对数进行下舍入。

根据“floor”的字面意思“地板”去理解;

例如:
Math.ceil(11.46)= Math.ceil(11.68)= Math.ceil(11.5)= 11
Math.ceil(-11.46)= Math.ceil(-11.68)= Math.ceil(-11.5)= -12

 

 

(待续...)

posted @ 2017-04-25 20:49  叶超Luka  阅读(1456)  评论(0编辑  收藏  举报