最近我也是经历过面试别人和去面试的人了,总结几个常被说起的面试问题,作一下解答和备忘。css
先来看看这个题目:前端
var x = 0;
var foo = {
x:1,
bar:{
x:2,
baz: function () {
console.log(this.x)
}
}
}
var a = foo.bar.baz
foo.bar.baz() // 2
a() //0
复制代码
上面的例子中,baz被bar调用因此指向的指bar. a 运行时所在的对象是 window,因此指向的是window。git
理解执行环境和上下文github
函数调用都有与之相关的做用域和上下文。从根本上说,做用域是基于函数(function-based)而上下文是基于对象(object-based)。换句话说,做用域是和每次函数调用时变量的访问有关,而且每次调用都是独立的。上下文老是关键字 this 的值,是调用当前可执行代码的对象的引用。面试
执行上下文分有global、function、eval,一个函数能够产生无数个执行上下文,一系列的执行上下文从逻辑上造成了 执行上下文栈,栈底老是全局上下文,栈顶是当前(活动的)执行上下文。bash
执行上下文三属性:this指针,变量对象(数据做用域),做用域链闭包
做用域链 即:一变量在本身的做用域中没有,那么它会寻找父级的,直到最顶层。过程以下:hexo
上面的文字你们能够好好琢磨一下,能够更好的理解函数做用域。
app
咱们先来了解js编译器在执行代码的过程:
以执行一段function代码为例:
第一步:建立可执行上下文(如下简称为EC),压入当前的EC栈中。EC中包括了如下信息:函数
第二步:收集函数声明、变量声明和形参,保存在环境记录项内。这个收集的过程,就是通常所谓的声明提高现象的本质。若是发现了重复的标识符,则优先级为函数声明 、形参 、变量声明(优先级低的会被无视)。
第三步:开始执行代码,环境记录项内没有的标识符会根据做用域链查找标识符对应的值,环境记录项亦有可能因赋值语句而被修改。
第四步:函数执行完毕,EC栈被弹出、销毁。
好了,第二步说的很清楚了 声明提高(Hoisting)现象就是在收集函数、变量声明和形参的过程会根据函数声明、形参、变量声明的顺序优先级来收集。
例子:
var a = 1;
function b() {
a = 10;
return;
function a() {}
}
b();
console.log(a);
// 输出1 因为函数声明提高,b内的实际是这样:
// function b() {
// function a() {}; 这里是函数声明提高
// a = 10;
// return;
// function a() {}
// }
复制代码
勘误:谢谢github上有同窗的指正 关于博客中的一个问题 · Issue #1 · stephenzhao/hexo-theme-damon,上面的正确执行应该为先进行预编译,因此先执行function a(){},而后会进行对a的赋值操做。
//正确的顺序应该为:
// function b() {
// function a() {}
// a = 10;
// return;
// }
复制代码
仍是上面的题目,作个变形。
var x = 0;
var foo = {
x:1,
bar:function () {
console.log(this.x);
var that = this;
return function () {
console.log(this.x)
console.log(that.x)
}
}
}
foo.bar() // 1
foo.bar()() // this: 0, that: 1
复制代码
上面的例子中ba'r里面返回了一个匿名函数,这个匿名函数能够在外部被调用即:foo.bar()() 读取到了bar的执行上下文的变量对象 that,这个函数就造成了一个闭包。
好了,咱们理解了上面的套路,下面来解释闭包就好理解了。
闭包就是可以读取其它函数内部变量的函数
在Javascript语言中,只有函数内部的子函数才能读取局部变量,所以能够把闭包简单理解成“定义在一个函数内部的函数”
var x = 0;
var bar:function () {
var n = 999;
return function () {
return n;
}
}
var outer = bar();
outer() // 999
复制代码
用途:
咱们修改一下上面的代码
var add;
var bar = function () {
var n = 999;
add = function () {
n += 1;
}
return function () {
return n;
}
}
var outer = bar();
outer() // 999
add();
outer(); // 1000
复制代码
说明,n一直保存在内存当中,而没有在bar()执行完成以后被销毁;
缘由:
bar里面的匿名函数被赋值给了outer,这个致使在outer没有被销毁的时候,该匿名函数一直存在内存当中,而匿名函数的存在依赖于bar,因此bar须要使用都在内存当中,因此bar并不会在调用结束后呗垃圾回收机制给收回。
而上面的add接受的也是一个匿名函数,该匿名函数自己也是闭包,因此也能够在外部操做里面的变量。
注意点
单例模式的定义是产生一个类的惟一实例
单例模式在js中常常会遇到,好比 var a = {}; 其实就是一个单例子。
可是咱们写一个更有意义的单例:
var singleton = function( fn ){
var result;
return function(){
return result || ( result = fn .apply( this, arguments ) );
}
}
复制代码
更简洁一点的:
var singleton = (function () {
var instance;
return function (object) {
if(!instance){
instance = new object();
}
return instance;
}
})();
复制代码
又是半夜,这两天在看里约奥运会的比赛,林丹和李宗伟的那场比赛是今年看过的经次于nba总决赛最后一场的精彩程度。一个伟大的英雄,须要另外一个伟大的对手来成就,感谢林丹,感谢李宗伟世界会记住大家。晚安。