续 “前端工作面试问题(上)”
JS相关问题:
- 解释下事件代理。
在传统的事件处理中,你按照需要为每一个元素添加或者是删除事件处理器。然而,事件处理器将有可能导致内存泄露或者是性能下降——你用得越多这种风险就越大。JavaScript事件代理则是一种简单的技巧,通过它你可以把事件处理器添加到一个父级元素上,这样就避免了把事件处理器添加到多个子级元素上。
事件代理用到了两个在JavaSciprt事件中常被忽略的特性:事件冒泡以及目标元素。当一个元素上的事件被触发的时候,比如说鼠标点击了一个按钮,同样的事件将会在那个元素的所有祖先元素中被触发。这一过程被称为事件冒泡;这个事件从原始元素开始一直冒泡到DOM树的最上层。任何一个事件的目标元素都是最开始的那个元素,在我们的这个例子中也就是按钮,并且它在我们的元素对象中以属性的形式出现。使用事件代理,我们可以把事件处理器添加到一个元素上,等待一个事件从它的子级元素里冒泡上来,并且可以得知这个事件是从哪个元素开始的。
1 // 获取父节点,并为它添加一个click事件 2 document.getElementById("parent-list").addEventListener("click",function(e) { 3 // 检查事件源e.targe是否为Li 4 if(e.target && e.target.nodeName.toUpperCase == "LI") { 5 // 真正的处理过程在这里 6 console.log("List item ",e.target.id.replace("post-")," was clicked!"); 7 } 8 });
这样做的好处:那些需要创建的以及驻留在内存中的事件处理器少了,这样我们就提高了性能,并降低了崩溃的风险。在DOM更新后无须重新绑定事件处理器了。如果你的页面是动态生成的,比如说通过Ajax,你不再需要在元素被载入或者卸载的时候来添加或者删除事件处理器了。
- 解释下 JavaScript 中 this 是如何工作的。
this 永远指向函数运行时所在的对象,而不是函数被创建时所在的对象。匿名函数或不处于任何对象中的函数指向 window 。函数中的this的值取决于函数调用的模式:
方法调用模式
当函数被保存为对象的一个属性时,成该函数为该对象的方法。函数中this的值为该对象。
var foo = { name: 'fooname', getName: function (){ return this.name } } foo.getName(); // this => foo
函数调用模式
当函数并不是对象的属性。函数中this的值为全局对象
note:某个方法中的内部函数中的this的值也是全局对象,而非外部函数的this
function foo(){ this.name = 'fooname'; } foo(); // this => window
构造器调用模式
即使用new调用的函数,则其中this将会被绑定到那个新构造的对象。构造器调用将一个全新的对象作为this变量的值,并隐式返回这个新对象作为调用结果。
function Foo(){ this.name = 'fooname'; } var foo = new Foo(); // this => foo
如果你不是使用new来调用构造器,那其实你就是在使用一个实函数。因此this就不会是你预期的值。在Sloppy模式中,this 指向的就是window 而你将会创建全局变量。不过如果使用的是strict模式,那你还是会得到警告(this===undefined)。
使用apply或call调用模式
该模式调用时,函数中this被绑定到apply或call方法调用时接受的第一个参数。
function getName(name){ this.name = name; } var foo = {}; getName.call(foo, name); // this =>foo
在方法中this 的用法更倾向于传统的面向对象语言:this 指向的接收方,也就是包含有这个方法的对象。
箭头函数就是没有自己的this 的函数。在这样的函数中你可以随便使用this,也不用担心有没有隐式的存在。下面提供了三种思路来解决这个问题:
1)that=this,将this 赋值到一个变量上,这样就把this 显性地表现出来了(除了that,self 也是个很常见的用于存放this的变量名),之后就使用那个变量。
2)bind()。使用bind()来创建一个函数,这个函数的this 总是存有你想要传递的值(下面这个例子中,方法的this):
this.friends.forEach(function (friend) { console.log(this.name+' knows '+friend); }.bind(this));
3)用forEach的第二个参数。forEach的第二个参数会被传入回调函数中,作为回调函数的this 来使用。
this.friends.forEach(function (friend) { console.log(this.name+' knows '+friend); }, this);
- 解释下原型继承的原理。
在javascript中,类(定义类是模块开发和重用代码的有效方式之一)的实现是基于其原型继承机制的。如果两个实例都从一个原型对象上继承了属性,我们说它们是同一个类的实例。如果两个对象继承自同一个原型,往往意味着(但不是绝对)它们是由同一个构造函数创建并初始化的。
1)类和对象
在javascript中,类的所有实例对象都从一个类型对象上继承属性。因此,原型对象是类的核心。
2)类和构造函数
构造函数是用来初始化和创建对象的。使用new关键字来调用构造函数,创建一个新对象。调用构造函数的一个重要特征是,构造函数的prototype属性被用做新对象的原型(var object.prototype = new Object())。这意味着通过同一个构造函数创建的对象都是继承自一个相同的对象,因此它们都是一个类的成员。
javascript中的类牵扯三种不同的对象,三种对象的属性的行为和下面三种类成员非常相似:
构造函数对象
Js所有的函数都有一个prototype属性,这个属性引用了一个对象,即原型对象,也简称原型。这个函数包括构造函数和普通函数,判断一个函数F是否是Object对象的实例:F.prototype instanceof Object //->true
原型对象属性被类的所有实例所继承,如果原型对象的属性值是函数的话,这个函数就作为类的实例方法来调用
实例对象,类的每个实例对象都是一个独立的对象,直接 给这个实例定义的属性是不会为所有实例对象锁共享的。定义在实例上的非函数属性,实际上是实例的字段。
在javascript中定义类的步奏可以缩减为一个分三步的算法。第一步,先定义一个构造函数,并设置初始化新对象的实例属性。第二步,给构造函数的prototype对象定义实例的方法。第三步:给构造函数定义类字段和类属性。
(面试官可能会问到“javascript继承和其他语言继承的区别”,可以从基于对象和访问修饰符分析)
javascript中基于原型的继承机制是动态的:对象从其原型继承属性,如果创建对象之后原型的属性发生改变,也会影响到继承这个原型的所有实例对象。这意味着我们可以通过给原型对象添加新的方法来扩充javascript类。
- 你是如何测试 JavaScript 代码的?
1)使用浏览器自带的控制台调试,详细可参照“使用console进行 性能测试 和 计算代码运行时间”
2)chrome的单步调试功能
JavaScript 断点设置和调试功能和java编辑工具的调试方法类似
- *AMD vs. CommonJS?
CommonJs 是服务器端模块的规范,Node.js采用了这个规范。根据CommonJS规范,一个单独的文件就是一个模块。加载模块使用require方法,该方法读取一个文件并执行,最后返回文件内部的exports对象。
//require方法默认读取js文件,所以可以省略js后缀 var test = require('./boobar').foobar; //test为boobar文件中的foobar对象的实例 test.bar(); //调用方法bar()
CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以CommonJS规范比较适用。
AMD CMD 采用异步模式,方便浏览器环境要从服务器加载模块。AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。AMD异步加载模块。它的模块支持对象、函数、构造器、字符串、JSON等各种类型的模块。适用AMD规范适用define方法定义模块。
//通过数组引入依赖 ,回调函数通过形参传入依赖 define(['someModule1', ‘someModule2’], function (someModule1, someModule2) { function foo () { /// something someModule1.test(); } return {foo: foo} });
本题内容整理自:http://my.oschina.net/felumanman/blog/263330?p=1
- 什么是哈希表?
类比数组,数组是编程上的哈希表,哈希表是一种数据结构,关键点就是用一个key值来取对应数据,就像数组的下标。
https://github.com/floraLam/dailyLearn/blob/master/dataStructure/31哈希表.html
- 解释下为什么接下来这段代码不是 IIFE(立即调用的函数表达式):function foo(){ }();.
而函数定义(语句以function关键字开始)是不能被立即执行的,这无疑会导致语法的错误(SyntaxError)。当函数定义代码段包裹在括号内,使解析器可以将之识别为函数表达式,然后调用。IIFE:(function foo(){})()
之前面试,遇到问题,区分(function(){})();和(function(){}());其实两者实现效果一样。
函数字面量:首先声明一个函数对象,然后执行它。(function () { alert(1); })(); 优先表达式:由于Javascript执行表达式是从圆括号里面到外面,所以可以用圆括号强制执行声明的函数。(function () { alert(2); }());
- 描述以下变量的区别:null,undefined 或 undeclared?
'undefined'是未定义,在变量没有赋值的时候的值即为undefined。"缺少值",就是此处应该有一个值,但是还没有定义。
'underclared'即为被污染的命名,访问没有被声明的变量,会抛出异常,终止执行。
'null'是一个空的对象引用。"没有对象",即该处不应该有值
undefined和null在if语句中,都会被自动转为false,相等运算符甚至直接报告两者相等。typeof undefined会返回undefined ,而typeof null 总返回 object(typeof有六种可能:"number"、"string"、"boolean"、"object"、"function"、"undefined")
false == undefined;//false false == null;//false null == undefined;//true
- 什么是闭包,如何使用它,为什么要使用它?
当某个函数调用时会创建一个执行环境以及作用域链,然后根据arguments和其它命名参数初始化形成活动对象。在外部函数调用结束后,其执行环境与作用域链被销毁,但是其活动对象保存在了闭包之中,最后在闭包函数调用结束后才销毁。简单的说,闭包就是能够读取其他函数内部变量的函数。
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。
注意:
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
用法
//第1种写法 :这种写法没什么特别的,只是给函数添加一些属性。 function Circle(r) { this.r = r; } Circle.PI = 3.14159; Circle.prototype.area = function() { return Circle.PI * this.r * this.r; } var c = new Circle(1.0); alert(c.area()); //第2种写法 :这种写法是声明一个变量,将一个函数当作值赋给变量。 var Circle = function() { var obj = new Object(); obj.PI = 3.14159; obj.area = function( r ) { return this.PI * r * r; } return obj; } var c = new Circle(); alert( c.area( 1.0 ) ); //第3种写法 :这种方法最好理解,就是new 一个对象,然后给对象添加属性和方法。 var Circle = new Object(); Circle.PI = 3.14159; Circle.Area = function( r ) { return this.PI * r * r; } alert( Circle.Area( 1.0 ) ); //第4种写法 :这种方法使用较多,也最为方便。var obj = {}就是声明一个空的对象。 var Circle={ "PI":3.14159, "area":function(r){ return this.PI * r * r; } }; alert( Circle.area(1.0) );
闭包的用途
事实上,通过使用闭包,我们可以做很多事情。比如模拟面向对象的代码风格;更优雅,更简洁的表达出代码;在某些方面提升代码的执行效率。
1)匿名自执行函数
全局对象过于庞大,影响访问速度(因为变量的取值是需要从原型链上遍历的)。
除了每次使用变量都是用var关键字外,我们在实际情况下经常遇到这样一种情况,即有的函数只需要执行一次,其内部变量无需维护,我们可以使用闭包。
我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在函数执行完后会立刻释放资源,关键是不污染全局对象。
2)结果缓存
我们开发中会碰到很多情况,设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。
3)封装
4)实现类和继承
- 你喜欢的使用闭包的模式是什么?两种模式用在不同场合。参见jQuery源码,立即调用模式,把$的jQuery源码放在了全局作用域下。返回函数类型的,制作一个随时可以使用的函数。
- 请举出一个匿名函数的典型用例?
$.("input").each(function(e){this.val('OK')});
- ------------------------------解释 “JavaScript 模块模式” 以及你在何时使用它。
我们在做radf库的时候,把所有的函数写在var function = radf(){}里,为的是在全局作用域下,只有一个radf对象,所有的属性和方法全在radf命名空间下面。这样就是一个无污染的环境。
- 如果有提到无污染的命名空间,可以考虑加分。
- 如果你的模块没有自己的命名空间会怎么样?
- 与其它库或内容造成冲突。
- 如果有提到无污染的命名空间,可以考虑加分。
- 如果你的模块没有自己的命名空间会怎么样?
- 你是如何组织自己的代码?是使用模块模式,还是使用经典继承的方法?
在模块模式中使用继承。例如我们的库中有pannel布局型组件和data型组件,其余都依照这两个基本组件继承而来。
具体可以参考 ExtJS4 便捷三层开发模式
- 请指出 JavaScript 宿主对象和原生对象的区别?
原生对象,独立于宿主环境的 ECMAScript 实现提供的对象。为array obj regexp date function等可以new实例化的对象。
内置对象为gload Math 等,开发者不必明确实例化内置对象,它已被实例化了。类似于isNaN()、parseInt()和parseFloat()方法等,看起来都是函数,而实际上,它们都是Global对象的方法。具体可以参考 JavaScript 全局对象
宿主对象。即由 ECMAScript 实现的宿主环境(操作系统和浏览器)提供的对象。所有的BOM和DOM对象都是宿主对象。因为其对于不同的“宿主”环境所展示的内容不同(这就是兼容性和特性检测的缘由)。ECMAScript官方未定义的对象都属于宿主对象。
- 指出下列代码的区别:
function Person(){}
var person = Person();
var person = new Person();
1、定义一个函数为Person()
2、定义一个匿名函数指向person
3、实例化一个person、原型来自于函数Person
- .call 和 .apply 的区别是什么?
call和apply都是调用一个对象的一个方法,以另一个对象替换当前对象。它们都属于Function.prototype的一个方法,所以每个function实例都有call和apply属性。这两个方法可以用来代替另一个对象调用一个方法,可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。
区别在于,两者传递的参数不同,虽然函数第一个参数都是要传入给当前对象的对象,但是,apply的第二个参数是一个参数数组,将多个参数组合成为一个数组传入;而call第二个参数则是直接的参数列表。
- 请解释 Function.prototype.bind?
Function.prototype.bind()其实就是函数绑定。函数的接收者取决于他是如何被调用,可以通过调用.bind()给函数绑定作用域上下文,即函数的接收者。
var foo = { x: 3} var bar = function(){console.log(this.x);} bar(); // undefined var boundFunc = bar.bind(foo);//隐式看作是在foo作用域里调用bar方法 boundFunc(); // 3
我们创建了一个新的函数,当它被执行的时候,它的 this 会被设置成 foo —— 而不是像我们调用 bar() 时的全局作用域。
对于改变上下文作用域(具体可以查看上题“解释下 JavaScript 中 this 是如何工作的”),可以将this设置到一个变量上,这样改变了上下文之后继续引用到它。同时可以选择 self, _this 或者 context 作为变量名称(也有人使用 that)。
.bind()创建了一个函数,当这个函数在被调用的时候,它的 this 关键词会被设置成被传入的值(这里指调用bind()时传入的参数)也就是我们传入想要的上下文。
简单的用法:
关于 Function.prototype.bind() 内部,这里有个非常简单的例子:
Function.prototype.bind = function (scope) { var fn = this; return function () { return fn.apply(scope);//使用call效果一样 }; }
- 你何时优化自己的代码?
优化代码是在不改变程序行为的基础上进行小的改动,是代码逐渐改善的过程。移除长期累积下来的烂码,以得到更清晰和更容易维护,除错以及添加新功能的代码,这做法不能单纯只出现在编码的后期,甚至是你意识到你的代码已经无从再下手非重写不可的时候,而是从开始开发起,逐渐积累,逐渐修改。以前因为日常编码的随意性,导致问题日益积累,逐步扩散,最后只能推倒重来。如果时间经受不起推倒重来,你别无选择,唯一实现的选择就是重构。整体的优化设计虽然惹人注目令人难忘,但没有平日的积累,何以收获庞大的成就?你的目标应该是让代码每天都有新变化。坚持几个月,我相信我们都能拥有骄傲地,清晰代码。
- 在什么时候你会使用 document.write()?
记住,在载入页面后,浏览器输出流自动关闭。在此之后,任何一个对当前页面进行操作的document.write()方法将打开—个新的输出流,它将清除当前页面内容(包括源文档的任何变量或值)。因此,假如希望用脚本生成的HTML替换当前页面,就必须把HTML内容连接起来赋给一个变量,使用一个document.write()方法完成写操作。不必清除文档并打开一个新数据流,一个document.write()调用就可完成所有的操作。
关于document.write()方法还有一点要说明的是它的相关方法document.close()。脚本向窗口(不管是本窗口或其他窗口)写完内容后,必须关闭输出流。在延时脚本的最后一个document.write()方法后面,必须确保含有document.close()方法,不这样做就不能显示图像和表单。并且,任何后面调用的document.write()方法只会把内容追加到页面后,而不会清除现有内容来写入新值。为了演示document.write()方法,我们提供了同一个应用程序的两个版本。
- 大多数生成的广告代码依旧使用 document.write(),虽然这种用法会让人很不爽。
- 请指出浏览器特性检测,特性推断和浏览器 UA 字符串嗅探的区别?
检测浏览器的特殊名称和版本(用户代理检测)即浏览器UA字符串嗅探。浏览器嗅探技术可以快捷的将代码进行分支,以便针对不同的浏览器应用不同的指令;针对特定浏览器的特定版本,超出范围之外都是不可靠的
if (navigator.userAgent.indexOf("MSIE 7") > -1){
//do something
}
var sUserAgent = navigator.userAgent; //检测Opera、KHTML var isOpera = sUserAgent.indexOf(“Opera”) > -1; var isKHTML = sUserAgent.indexOf(“KHTML”) > -1 || sUserAgent.indexOf(“Konqueror”) > -1 || sUserAgent.indexOf(“AppleWebKit”) > -1; //检测IE、Mozilla var isIE = sUserAgent.indexOf(“compatible”) > -1 && sUserAgent.indexOf(“MSIE”) > -1 && !isOpera; var isMoz = sUserAgent.indexOf(“Gecko”) > -1 && !isKHTML; //检测操作系统 var isWin = (navigator.platform == “Win32″) || (navigator.platform == “Windows”); var isMac = (navigator.platform == “Mac68K”) || (navigator.platform == “MacPPC”) || (navigator.platform == “Macintosh”); var isUnix = (navigator.platform == “X11″) && !isWin && !isMac;
检测浏览器的特性(特性检测)
if(document.all){
//do something
}
另外IE独有children,parentElement,innerText,outerText,outerHTML,FF没有;
直接进行特性检测是个很好的方法,并且大部分情况下能满足需求。一般只要在检测前知道这个特性是否被实现即可,而不会去考虑它们之间的关系。
另外,针对CSS3中新特性@font-face、border-radius、 border-image、box-shadow、rgba() 等,HTML5的特性——比如audio、video、本地储存、和新的 <input>标签的类型和属性等,必要时要进行优雅降级。
- 请尽可能详尽的解释 AJAX 的工作原理。
非ajax是把要提交的内容放在submit里面,浏览器刷新提交数据。ajax即异步数据刷新,将要提交的数据与服务器接口交换数据,将得到的数据返回用于重组dom元素,以及改变一些页面效果。
Ajax的原理简单来说通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用javascript来操作DOM而更新页面。这其中最关键的一步就是从服务器获得请求数据。
XMLHttpRequest是ajax的核心机制,它是在IE5中首先引入的,是一种支持异步请求的技术。简单的说,也就是javascript可以及时向服务器提出请求和处理响应,而不阻塞用户。达到无刷新的效果。
简单地说,我们可以把服务器端看成一个数据接口,它返回的是一个纯文本流,当然,这个文本流可以是XML格式,可以是Html,可以是Javascript代码,也可以只是一个字符串。这时候,XMLHttpRequest向服务器端请求这个页面,服务器端将文本的结果写入页面,这和普通的web开发流程是一样的,不同的是,客户端在异步获取这个结果后,不是直接显示在页面,而是先由javascript来处理,然后再显示在页面。
- 请解释 JSONP 的工作原理,以及它为什么不是真正的 AJAX。
JSONP动态创建script标签,回调函数。Ajax是页面无刷新请求数据操作
动态添加一个<script>标签,而script标签的src属性是没有跨域的限制的。这样说来,这种跨域方式其实与ajax XmlHttpRequest协议无关了。当GET请求从被调用页面返回时,可以返回一段JavaScript代码,这段代码会自动调用主页面中的一个callback函数。
Jsonp优点不受同源策略的影响,它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果
Jsonp缺点,它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。
- 你使用过 JavaScript 模板系统吗?
没用过,但是知道:在生成各种页面内容结合javascript模板技术,能让逻辑和数据之间更加清晰。逻辑是写在"<%"与"%>"之间,如果是注释,则用"<%#"与"%>",后台传过来的变量使用@来标记。
- 如有使用过,请谈谈你都使用过哪些库,比如 Mustache.js,Handlebars 等等。
- 请解释变量声明提升。
javascript不支持块级作用域,即变量定义的作用域并不是离其最近的封闭语句或代码块,而是包含它的函数:
var foo = 1; function bar() { if (!foo) {var foo = 10;} alert(foo);//10 } bar();
var a = 1; function b() {a = 10;return;function a() {}} b();alert(a);//1
对于被函数作用域包围的变量的作用域为函数,函数内部访问变量,将会返回函数体内最近的变量值;函数外部访问变量将会函数体外所声明的变量值。
也但是,对于被if语句包裹的代码段,不能看作是另外一个独立的作用域,也就是说,对于被非函数的{}所包围的代码段内所定义的变量,变量的声明将会提升到{}所在的作用域。
function f(){{var x =0;}}等同于function f(){var x ;{x =0;}}
function foo() {bar();var x = 1;}会被解释为function foo() {var x;bar();x = 1;}
变量赋值并没有被提升,只是声明被提升了。但是,函数的声明有点不一样,函数体也会一同被提升。但是请注意,函数的声明有两种方式:
function test() { foo(); // TypeError "foo is not a function" bar(); // "this will run!" var foo = function () { // 变量指向函数表达式 alert("this won't run!"); } function bar() { // 函数声明 函数名为bar alert("this will run!"); } } test();
对于var a=1; function a(){ } alert(a);function a(){ } var a=1; alert(a);都是会打印出1
对于全局作用于范围的变量,var与不var是有区别的. 没有var的写法,其变量不会被提升。比如下面的程序会报错:alert(a);a=1;
eval中创建的局部变量是不会被提升var a = 1;function t(){console.info(a);eval('var a = 2');console.info(a);}t();console.info(a);结果按照顺序为1,2,1
- 请描述下事件冒泡机制。
从目标元素开始,往顶层元素传播。途中如果有节点绑定了相应的事件处理函数,这些函数都会被一次触发。如果想阻止事件起泡,可以使用e.stopPropagation()(Firefox)或者e.cancelBubble=true(IE)来组织事件的冒泡传播。
- "attribute" 和 "property" 的区别是什么?
DOM元素的attribute和property两者是不同的东西。attribute翻译为“特性”,property翻译为“属性”。
attribute是一个特性节点,每个DOM元素都有一个对应的attributes属性来存放所有的attribute节点,attributes是一个类数组的容器,说得准确点就是NameNodeMap,不继承于Array.prototype,不能直接调用Array的方法。attributes的每个数字索引以名值对(name=”value”)的形式存放了一个attribute节点。<div class="box" id="box" gameid="880">hello</div>
property就是一个属性,如果把DOM元素看成是一个普通的Object对象,那么property就是一个以名值对(name=”value”)的形式存放在Object中的属性。要添加和删除property和普通的对象类似。
很多attribute节点还有一个相对应的property属性,比如上面的div元素的id和class既是attribute,也有对应的property,不管使用哪种方法都可以访问和修改。
总之,attribute节点都是在HTML代码中可见的,而property只是一个普通的名值对属性。
- 为什么扩展 JavaScript 内置对象不是好的做法?
因为你不知道哪一天浏览器或javascript本身就会实现这个方法,而且和你扩展的实现有不一致的表现。到时候你的javascript代码可能已经在无数个页面中执行了数年,而浏览器的实现导致所有使用扩展原型的代码都崩溃了。
需要给Array原型添加一个distinct的方法,最好检查是否存在同名的方法,避免自定义方法覆盖原生方法:
Arrray.prototype.distinct = Arrray.prototype.distinct || function(){/*.....*/}
- 请指出 document load 和 document ready 两个事件的区别。
document.ready和onload的区别——JavaScript文档加载完成事件。页面加载完成有两种事件,一是ready,表示文档结构已经加载完成(不包含图片等非文字媒体文件),二是onload,指示页面包含图片等文件在内的所有元素都加载完成。
jQuery中$(function(){/* do something*/});他的作用或者意义就是:在DOM加载完成后就可以可以对DOM进行操作。一般情况先一个页面响应加载的顺序是,域名解析-加载html-加载js和css-加载图片等其他信息。
- == 和 === 有什么不同?
”==”:判断值是否相等。应用一套难以理解的隐式强制转换规则。
”===”判断值及类型是否完全相等。读者不需要涉及任何的隐式转换。
注意:
1)如果两个值的类型不同,它们就不相同。
2)如果两个值是数字,而且值相同,那么除非其中一个或两个都是NaN(这种情况它们不是等同的),否则它们是等同的。值NaN永远不会与其他任何值等同,包括它自身(奇怪的家伙),要检测一个值是否是NaN,可以使用全局函数isNaN()。
3)如果两个值都是字符串,而且在串中同一位置上的字符完全相同,那么它们就完全等同。如果字符串的长度或内容不同,它们就不是等同的。
4)如果两个值都是布尔型true,或者两个值都是布尔型false,那么它们等同。
5)如果两个值引用的是同一个对象、数组或函数,那么它们完全等同。如果它们引用的是不同的对象(数组或函数),它们就不完全等同,即使这两个对象具有完全相同的属性,或两个数组具有完全相同的元素。
6)如果两个值都是null或都是undefined,“==”返回true,“===”返回false。
- 请解释一下 JavaScript 的同源策略。
同源策略是客户端脚本(尤其是Javascript)的重要的安全度量标准。所谓同源是指,域名,协议,端口相同。如果我们又想利用XMLHTTP的无刷新异步交互能力,又不愿意公然突破Javascript的安全策略,可以选择的方案就是给XMLHTTP加上严格的同源限制。
同源策略阻止从一个源加载的文档或脚本获取或设置另一个源加载的文档的属性。
处理跨域方法:
1)document.domain+iframe的设置
2)动态创建script
3)利用iframe和location.hash
4)window.name实现的跨域数据传输
5)使用HTML5 postMessage
- 如何实现下列代码:
[1,2,3,4,5].duplicator(); // [1,2,3,4,5,1,2,3,4,5]
Array.prototype.duplicator = function(){
var l = this.length,i;
for(i=0;i<l;i++){
this.push(this[i])
}
}
- 什么是三元表达式?“三元” 表示什么意思?
三元运算符需要三个操作数。
语法是 条件 ? 结果1 : 结果2;. 这里你把条件写在问号(?)的前面后面跟着用冒号(:)分隔的结果1和结果2。满足条件时结果1否则结果2。
- 什么是 "use strict"; ? 使用它的好处和坏处分别是什么?
在所有的函数 (或者所有最外层函数) 的开始处加入 "use strict"; 指令启动严格模式。
"严格模式"有两种调用方法
1)将"use strict"放在脚本文件的第一行,则整个脚本都将以"严格模式"运行。如果这行语句不在第一行,则无效,整个脚本以"正常模式"运行。如果不同模式的代码文件合并成一个文件,这一点需要特别注意。
2)将整个脚本文件放在一个立即执行的匿名函数之中。
好处
- 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
- 消除代码运行的一些不安全之处,保证代码运行的安全;
- 提高编译器效率,增加运行速度;
- 为未来新版本的Javascript做好铺垫。
坏处
同样的代码,在"严格模式"中,可能会有不一样的运行结果;一些在"正常模式"下可以运行的语句,在"严格模式"下将不能运行
jQuery 相关问题:
- 解释"chaining"。
Chaining 允许我们在一条语句中允许多个 jQuery 方法(在相同的元素上)。这样的话,浏览器就不必多次查找相同的元素。链式调用,这是因为jQuery内部在方法调用之后,都返回本身(在无状态的方法中返回新对象来支持方法链,有状态的方法中返回this来支持方法链)。虽然如此,但是如果直接把全部代码都写在一行,可读性会变差,不利于维护,因此要加上必要的缩进和换行。
$("#p1").css("color","red") .slideUp(2000) .slideDown(2000);
- 解释"deferreds"。
deferred对象就是jQuery的回调函数解决方案。deferred对象的含义就是"延迟"到未来某个点再执行。
对于那些某些耗时很长的javascript操作比如异步的操作(比如ajax读取服务器数据),和同步的操作(比如遍历一个大型数组),并不是马上能够得到结果,因此,为它们指定回调函数(callback)。
$.ajax("test.html").done(function(){ alert("成功!"); }).fail(function(){ alert("出错!"); });同时回调函数可以添加任意多个,它们按照添加顺序执行。
- 你知道哪些针对 jQuery 的优化方法。
1)总是从ID选择器开始继承
在jQuery中最快的选择器是ID选择器,因为它直接来自于JavaScript的getElementById()方法。当然 这只是对于单一的元素来讲。如果你需要选择多个元素,这必然会涉及到 DOM遍历和循环,为了提高性能,建议从最近的ID开始继承。如下所示:var traffic_lights = $(“#traffic_light input”)
可以使用console测试程序性能,比较id选择器和class选择器的效率。
2)在class前使用tag(标签名)
在jQuery中第二快的选择器是tag(标签)选择器( 比如:$(“head”) ),因为它来自原生的getElementsByTagName() 方法。
在使用tag来修饰class的时候,我们需要注意以下几点:
(1) 不要使用tag来修饰ID,如下所示:var content = $(“div#content”);这样一来,选择器会先遍历所有的div元素,然后匹配#content。
(2)不要使用ID来修饰ID,如下所示:var traffic_light = $(“#content #traffic_light”);
3)将jQuery对象缓存起来
把jQuery对象缓存起来,不要让相同的选择器在你的代码里出现多次。
注意:(1)为了区分普通的JavaScript对象和jQuery对象,可以在变量首字母前加上 $ 符号。
(2)代码可以使用jQuery的链式操作加以改善。
4)对直接的DOM操作进行限制
这里的基本思想是在内存中建立你确实想要的东西,然后更新DOM ,因为直接的DOM操作速度很慢。例如,动态的创建一组列表元素,尽量不要在循环中,调用append:
for (var i=0, l=top_100_list.length; i<l; i++){ $mylist.append("<li>" + top_100_list[i] + "</li>");}
而应该将整套元素字符串创建完毕后,再在插入进dom中
for (var i=0, l=top_100_list.length; i<l; i++){top_100_li += "<li>" + top_100_list[i] + "</li>";} $mylist.html(top_100_li);
5)冒泡
除非在特殊情况下, 否则每一个js事件(例如:click, mouseover等.)都会冒泡到父级节点。当我们需要给多个元素调用同个函数时这点会很有用。代替这种效率很差的多元素事件监听的方法就是, 你只需向它们的父节点绑定一次。
$("#entryform").bind("focus", function(e){//非单独获取#entryform中的input var $cell = $(e.target); // e.target 捕捉到触发的目标元素 $cell.addClass("selected"); }).bind("blur", function(e){ var $cell = $(e.target); $cell.removeClass("selected"); });
6)推迟到 $(window).load
$(document).rady 确实很有用, 它可以在页面渲染时,其它元素还没下载完成就执行。其实可以通过将jQuery函数绑定到$(window).load 事件的方法来减少页面载入时的cpu使用率。它会在所有的html(包括iframe)被下载完成后执行。一些特效的功能,例如拖放, 视觉特效和动画, 预载入隐藏图像等等,都是适合这种技术的场合。
7)压缩JavaScript
压缩之前,请保证你的代码的规范性(语句执行结束后添加分号),否则可能失败,导致Js错误。
8)给选择器一个上下文
jQuery选择器中有一个这样的选择器,它能指定上下文。jQuery( expression, context );
通过它,能缩小选择器在DOM中搜索的范围,达到节省时间,提高效率。
普通方式:$(‘.myDiv’)改进方式:$(‘.myDiv’ , $(“#listItem”) )
- 请解释 .end() 的用途。
end() 方法结束当前链条中的最近的筛选操作,并将匹配元素集还原为之前的状态。
大多数 jQuery 的遍历方法会操作一个 jQuery 对象实例,并生成一个匹配不同 DOM 元素集的新对象。当发生这种情况时,应该会把新的元素集推入维持在对象中的堆栈内。每次成功的筛选方法调用都会把新元素推入堆栈中。如果我们需要老的元素集,可以使用 end() 从堆栈中弹出新集合。但由于进行了额外的调用,会有一点点性能损失。
- 你如何给一个事件处理函数命名空间,为什么要这样做?
用 .bind('click.myCustomRoutine',function(){...}); 把匿名函数绑定到 click 事件(使用命名空间多次绑定不同的行为方法);使用.unbind('click.myCustomRoutine') 即可 释放所有绑定到 .myCustomRoutine 命名空间的 click 事件,而不会解除其他通过 .bind('click') 或另外的命名 空间所绑定的事件行为。
但是,考虑一种情况就是:需要在运行时根据用户交互的结果进行不同click事件处理逻辑的绑定,因而理论 上会无数次对某一个事件进行 bind / unbind 操作。但又希望 unbind 的时候只把自己绑上去的处理逻辑给释放掉而不是所有其他地方有 可能的额外的同一事件绑定逻辑。
这时候如果直接用 .click() / .bind('click') 加 上 .unbind('click') 来进行重复绑定的话,被unbind 掉的将是所有绑定在元素上的 click 处理逻辑,潜在会影响到该元素 其他第三方的行为。
对于这种问题,jQuery的解决方案是使用事件绑定的命名空间。即在事件名称后添加.something 来区分自己这部分行为逻辑范围。
- 请说出你可以传递给 jQuery 方法的四种不同值。
选择器(字符串),HTML(字符串),回调函数,HTML元素,对象,数组,元素数组,jQuery对象等。
- 什么是效果队列?
jQuery中有个动画队列的机制。当对一个对象添加多次动画效果时后添加的动作就会被放入这个动画队列中,等前面的动画完成后再开始执行。可是用户的操作往往都比动画快,如果用户对一个对象频繁操作时不处理动画队列就会造成队列堆积,影响到效果。
jQuery中有stop这个方法可以停止当前执行的动画,并且它有两个布尔参数,默认值都为false。第一个参数为true时会清空动画队列,第二个参数为true时会瞬间完成掉当前动画。;第二个参数为true,把当前在执行的动画跳转到完成状态。这时第一个参数如果也为true,后面的队列就会被清空。
- 请指出 .get(),[],eq() 的区别。
eq返回的是一个jquery对象 get返回的是一个html 对象数组。
进一步说,返回的是jQuery对象,就可以继续调用其他方法,返回的是html数组就不能调用jQuery的其他方法,例如:
$("ul li").eq(1).css("color", "red"); //这个是正确的
$("ul li").get(1).css("color", "red"); //这个是错误的
当$()所获取的对象不存在,即为[]时,get()返回undefined,而eq()返回m.fn.init[0],jQuery文档对象。
- 请指出 .bind(),.live() 和 .delegate() 的区别。
对于bind():$('a').bind('click',function(){alert('That tickles!');})jQuery扫描文档找出所有的$(‘a')元素,并把alert函数绑定到每个元素的click事件上。
对于live():$('a').live('click',function(){alert('That tickles!')})jQuery把alert函数绑定到$(document)元素上,并使用'click'和'a'作为参数。任何时候只要有事件冒泡到document节点上,它就查看该事件是否是一个click事件,以及该事件的目标元素与'a'这一CSS选择器是否匹配,如果都是的话,则执行函数。
对于.delegate() :$('#container').delegate('a','click',function(){alert('That tickles!')})jQuery扫描文档查找$('#container'),并使用click事件和'a'这一CSS选择器作为参数把alert函数绑定到$('#container')上。任何时候只要有事件冒泡到$('#container')上,它就查看该事件是否是click事件,以及该事件的目标元素是否与CSS选择器相匹配。如果两种检查的结果都为真的话,它就执行函数。
bind看上去更加明确直接,但是delegate和live执行的效率会更高。
bind首先要扫描整个的文档查找所有的$(‘a')元素,把它们存成jQuery对象。尽管live函数仅需要把'a'作为串参数传递以用做之后的判断,但是$()函数并未“知道”被链接的方法将会是.live()。
delegate方法仅需要查找并存储$(document)元素。 一种寻求避开这一问题的方法是调用在$(document).ready()之外绑定的live,这样它就会立即执行。在这种方式下,其会在DOM获得填充之前运行,因此就不会查找元素或是创建jQuery对象了。
- 请指出 $ 和 $.fn 的区别,或者说出 $.fn 的用途。
jQuery对方法的拓展,从调用声明创建方法的方式来看,可以归结为两类:一类直接由$符调用;另一类由$("")来调用。$拓展的方法是静态方法,可以使用$直接调用,其拓展的方式一般使用$.extend({});;而$.fn拓展的方法是实例方法,必须由“对象”$("")来调用,一般使用$.fn.extend({ })。
$.fn是指jquery的命名空间,加上fn上的方法及属性,会对jquery实例每一个有效。 如扩展$.fn.abc() ;使用:$("#div").abc(); 。
- 请优化下列选择器:
$(".foo div#bar:eq(0)")->$(".foo #bar:first-child")
代码相关的问题:
modulo(12, 5) // 2
问题:实现满足上述结果的modulo函数
1 (function module(a,b){ 2 var r; 3 if( !isNaN(a) && !isNaN(b)){ 4 (a>b)?(r= a%b):(r= b%a); 5 return r; 6 }else{ 7 throw new Error("arguments are not numbers"); 8 } 9 })(12,5);
实际上就是求模运算。注意:检查参数的合理性(数字且长度为2)否则抛出异常
"i'm a lasagna hog".split("").reverse().join("");
问题:上面的语句的返回值是什么? 答案:"goh angasal a m'i"
这道题目提醒了一点:"i'm a lasagna hog"不继承于Array.prototype不能直接调用Array的reverse()方法。因此先要使用split("")将字符串转变为数组,最后使用join("")将已经逆序的结果转换为字符串。
博主想补充一点是:通过dom操作(ducument.getElementByTayName和document.getByClassName)获取的NodeList和函数中的arguments对象都是伪数组,不能不继承Array.prototype,不能直接调用Array的方法。可以使用遍历,将伪数组中的元素push到新数组中或使用[].slice.call(arguments)将伪数组转换为数组。
( window.foo || ( window.foo = "bar" ) );
问题:window.foo 的值是什么? 答案:"bar" 只有 window.foo 为假(定义的值类型转换为false或者没定义,即为undefined)时的才是上面答案,否则就是它本身的值(提前声明定义的值)。
var foo = "Hello"; (function() { var bar = " World"; alert(foo + bar); })(); alert(foo + bar);
问题:上面两个 alert 的结果是什么 答案: "Hello World" 和 ReferenceError: bar is not defined
var foo = "Hello"; (function() { var bar = " World"; console.info(foo + bar); //代码段包裹在“立即调用函数”中,获取全局的foo和函数内的bar })(); console.info(foo + bar);//“立即调用函数”执行完毕后,外部无法访问其内部的局部变量,因此,在此作用域内的bar未定义
var foo = [];
foo.push(1);
foo.push(2);
问题:foo.length 的值是什么? 答案:2
个人感觉这道题目考得太简单了,最后,大家思考一下这道题目:
var array1 = [1,2]; var array2 = array1; array1[0] = array2[1]; array2.push(3); console.log(array1); console.log(array2);
array2 = array1将array2和array1引用同一片存储区域,对其中一个变量进行赋值和修改操作都会影响到另外一个变量的值。
因此答案是:Array1的值为[2,2,3];Array2的值为[2,2,3]