前端面试之js相关问题(一)
2016-08-22 10:56
351 查看
原文链接:https://zhuanlan.zhihu.com/p/22109292
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• this 永远指向函数运行时所在的对象,而不是函数创建时所在的对象
• 匿名函数和不处于仍和对象中的函数,This指向window
• call, apply, with指的This是谁就是谁。
• 普通函数调用,函数被谁调用,This就指向谁
执行上下文分有global、function、eval,一个函数可以产生无数个执行上下文,一系列的执行上下文从逻辑上形成了 执行上下文栈,栈底总是全局上下文,栈顶是当前(活动的)执行上下文。
执行上下文三属性:this指针,变量对象(数据作用域),作用域链
作用域链 即:一变量在自己的作用域中没有,那么它会寻找父级的,直到最顶层。过程如下:
• 任何在执行上下文时刻的作用域都由作用域链来实现
• 在一个函数被定义的时候, 会将它定义时刻的scope chain链接到这个函数对象的[[scope]]属性
• 在一个函数对象被调用的时候,会创建一个活动对象(也就是一个对象), 然后对于每一个函数的形参,都命名为该活动对象的命名属性, 然后将这个活动对象做为此时的作用域链(scope chain)最前端, 并将这个函数对象的[[scope]]加入到scope chain中.
上面的文字大家可以好好琢磨一下,可以更好的理解函数作用域。
以执行一段function代码为例:
第一步:创建可执行上下文(以下简称为EC),压入当前的EC栈中。EC中包括了以下信息:
•词法环境(=环境记录项(保存变量、函数声明和形参)+ 外部词法环境(function的[[scope]]属性,作用域链的本质))
•this的指针
•变量环境(与环境记录项的值相同,但不再发生变动。)
第二步:收集函数声明、变量声明和形参,保存在环境记录项内。这个收集的过程,就是一般所谓的声明提升现象的本质。如果发现了重复的标识符,则优先级为函数声明 、形参 、变量声明(优先级低的会被无视)。
第三步:开始执行代码,环境记录项内没有的标识符会根据作用域链查找标识符对应的值,环境记录项亦有可能因赋值语句而被修改。
第四步:函数执行完毕,EC栈被弹出、销毁。
好了,第二步说的很清楚了 声明提升(Hoisting)现象就是在收集函数、变量声明和形参的过程会根据函数声明、形参、变量声明的顺序优先级来收集。
var a = 1;
function b() {
a = 10;
return;
function a() {}
}
b();
console.log(a);
// 输出1 由于函数声明提升,b内的实际是这样:
// function b() {
// var a = function a() {}; 这里是函数声明提升
// a = 10;
// return;
// function a() {}
// }
好了,我们理解了上面的套路,下面来解释闭包就好理解了。
闭包就是能够读取其它函数内部变量的函数
在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”
1. 读取函数内部的变量
2. 让这些变量的值始终保持在内存中
我们修改一下上面的代码
原因:
bar里面的匿名函数被赋值给了outer,这个导致在outer没有被销毁的时候,该匿名函数一直存在内存当中,而匿名函数的存在依赖于bar,所以bar需要使用都在内存当中,所以bar并不会在调用结束后呗垃圾回收机制给收回。
而上面的add接受的也是一个匿名函数,该匿名函数本身也是闭包,所以也可以在外部操作里面的变量。
注意点
1. 会导致内存泄漏,慎用
2. 闭包会修改内部变量的值,所以在使用闭包作为对象的公用方法时要谨慎。
闭包的一个应用,单例模式
JavaScript 中 this 是如何工作的 ?
先来看看这个题目: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• this 永远指向函数运行时所在的对象,而不是函数创建时所在的对象
• 匿名函数和不处于仍和对象中的函数,This指向window
• call, apply, with指的This是谁就是谁。
• 普通函数调用,函数被谁调用,This就指向谁
作用域链?
理解执行环境和上下文
函数调用都有与之相关的作用域和上下文。从根本上说,作用域是基于函数(function-based)而上下文是基于对象(object-based)。换句话说,作用域是和每次函数调用时变量的访问有关,并且每次调用都是独立的。上下文总是关键字 this 的值,是调用当前可执行代码的对象的引用。执行上下文分有global、function、eval,一个函数可以产生无数个执行上下文,一系列的执行上下文从逻辑上形成了 执行上下文栈,栈底总是全局上下文,栈顶是当前(活动的)执行上下文。
执行上下文三属性:this指针,变量对象(数据作用域),作用域链
作用域链 即:一变量在自己的作用域中没有,那么它会寻找父级的,直到最顶层。过程如下:
• 任何在执行上下文时刻的作用域都由作用域链来实现
• 在一个函数被定义的时候, 会将它定义时刻的scope chain链接到这个函数对象的[[scope]]属性
• 在一个函数对象被调用的时候,会创建一个活动对象(也就是一个对象), 然后对于每一个函数的形参,都命名为该活动对象的命名属性, 然后将这个活动对象做为此时的作用域链(scope chain)最前端, 并将这个函数对象的[[scope]]加入到scope chain中.
上面的文字大家可以好好琢磨一下,可以更好的理解函数作用域。
函数声明提升和变量声明提升(Hoisting) ?
我们先来了解js编译器在执行代码的过程:以执行一段function代码为例:
第一步:创建可执行上下文(以下简称为EC),压入当前的EC栈中。EC中包括了以下信息:
•词法环境(=环境记录项(保存变量、函数声明和形参)+ 外部词法环境(function的[[scope]]属性,作用域链的本质))
•this的指针
•变量环境(与环境记录项的值相同,但不再发生变动。)
第二步:收集函数声明、变量声明和形参,保存在环境记录项内。这个收集的过程,就是一般所谓的声明提升现象的本质。如果发现了重复的标识符,则优先级为函数声明 、形参 、变量声明(优先级低的会被无视)。
第三步:开始执行代码,环境记录项内没有的标识符会根据作用域链查找标识符对应的值,环境记录项亦有可能因赋值语句而被修改。
第四步:函数执行完毕,EC栈被弹出、销毁。
好了,第二步说的很清楚了 声明提升(Hoisting)现象就是在收集函数、变量声明和形参的过程会根据函数声明、形参、变量声明的顺序优先级来收集。
var a = 1;
function b() {
a = 10;
return;
function a() {}
}
b();
console.log(a);
// 输出1 由于函数声明提升,b内的实际是这样:
// function b() {
// var a = function a() {}; 这里是函数声明提升
// a = 10;
// return;
// function a() {}
// }
什么是闭包,如何使用它,为什么要使用它?
还是上面的题目,做个变形。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上面的例子中bar里面返回了一个匿名函数,这个匿名函数可以在外部被调用即:foo.bar()() 读取到了bar的执行上下文的变量对象 that,这个函数就形成了一个闭包。
好了,我们理解了上面的套路,下面来解释闭包就好理解了。
闭包就是能够读取其它函数内部变量的函数
在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”
var x = 0; var bar:function () { var n = 999; return function () { return n; } } var outer = bar(); outer() // 999用途:
1. 读取函数内部的变量
2. 让这些变量的值始终保持在内存中
我们修改一下上面的代码
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接受的也是一个匿名函数,该匿名函数本身也是闭包,所以也可以在外部操作里面的变量。
注意点
1. 会导致内存泄漏,慎用
2. 闭包会修改内部变量的值,所以在使用闭包作为对象的公用方法时要谨慎。
闭包的一个应用,单例模式
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; } })();
相关文章推荐
- (转) 前端面试之js相关问题(一)
- 前端工作面试HTML相关问题
- 前端JS面试中常见的算法问题总结
- 前端工作面试HTML相关问题
- 前端开发者面试问题 - JS 部分
- 前端跨域问题相关知识详解(原生js和jquery两种方法实现jsonp跨域)
- 后台与前端JS的传值交互问题
- JS有关与现在时间比较的问题,和服务器时间相关的
- 转载:OS 面试相关问题
- 细品JS的寻址,闭包,对象模型和相关问题
- js 解决隐藏与显示div的相关问题
- JS窗口问题处理:使弹出窗口保持前端显示的几种方法,及window窗体对象open()和showModalDialog()用法
- 多态,虚函数,纯虚函数,抽象类的相关问题(c++面试常见题目)
- 面试相关-非技术问题
- 完整的前端工程师面试问题列表
- 前端工程师面试问题列表
- 腾讯Web前端(Js)笔试面试之谈
- 细品JS的寻址,闭包,对象模型和相关问题
- 面试相关-非技术问题
- 前端一些面试问题3