作用域
在JS中(非ES6),只有函数作用域,没有块作用域。
例如,for循环,while等{}内部的变量其实是和外部处于同一个作用域的:
for (var i =1; i < 5; i ++) {
var a = 3;
}
console.log(a); //3 此时没有输出undefined,说明a和for循环内部的a是同一个作用域。
所以只有函数作用域:
function fn () {
var a = 1;
if (a > 2) {
var b = 3;
}
console.log(b);
}
fn();
console.log(a);
上述代码的运行结果等价于:
function fn () {
var a; // 进行变量的提升
var b; // 定义b是在if的{}内进行的,但是没有块作用域,实质上作用域还是fn内部,a和b是同一个作用域
a = 1;
if (a > 2) { //a没有满足条件,所以不会执行给b赋值的语句,所以n仅仅声明了但是没有赋值,console.log(b)的结果是undefined
b = 3;
}
console.log(b); //undefined
}
fn();
console.log(a); // a is not defined 报错,因为在当前的作用域即全局作用域中,a并没有定义,a只在fn的作用域里定义了。
var
- var如果重复声明一个已经存在的变量时,原来的变量的值是不会变的。
var a =1;
var a;
var a;
var a;
console.log(a); // 1 重复声明不会改变。
- 不加var的作用
不写var会声明一个全局变量,所以不建议不写var,即使需要全局变量,也要在全局作用域中使用var声明变量。
function fn () {
a = 1; //没有用var声明,a其实是一个全局变量,在外部作用域中也能访问
}
fn();
console.log(a);//1 说明全局作用域中也能访问a
1.函数声明和函数表达式有什么区别
有三种声明函数的方式:
- 构造函数:
var doSomething = new Function("console.log('hello,deejay')");
不推荐使用 - 函数声明:
function doSomething () { // 函数声明
console.log('hello,deejay');
}
doSomething(); // 调用也可以放到声明的前面
- 函数表达式:
var doSomething = function () { //函数表达式
console.log("hello,deejay");
}
doSomething(); // 表达式调用只能写在赋值声明后面
2.什么是变量的声明前置?什么是函数的声明前置
var和function的声明前置:在一个作用域下,var声明的变量和function声明的函数会前置
console.log(a); // undefined
var a = 3;
console.log(a); // 3
sayHello();
function sayHello () {
console.log('hello,deejay');
}
上述代码在解析时其实为
var a;
function sayHello() {console.log('hello,deejay');} //解析时,var声明的变量和function声明的函数 会前置
console.log(a); //undefined
a = 3;
console.log(a); //3
sayHello();
另外,如果有变量名和函数声明的函数名相同的情况,后面的值会覆盖前面的值,产生报错。
对于函数表达式定义的函数,前置的方式跟var一个变量没什么区别。
console.log(sayHello);//undefined
var sayHello = function () {
console.log('hello,deejay');
}
sayHello(); // 特别要注意,对于函数表达式定义的函数,只能先定义,然后再调用,不然会报错。
上述代码等价于
var sayHello;
console.log(sayHello);//undefined
sayHello = function () {
console.log('hello,deejay');
}
sayHello(); // 特别要注意,对于函数表达式定义的函数,只能先定义,然后再调用,不然会报错。
- 函数内部的声明前置
在一个作用域内,var定义的变量和function声明的函数会前置,那么在函数内部的作用域中,前置规则也是一样的。
function doSomething () {
console.log(a); // undefined
var a = 3;
console.log(a); //3
//上面代码其实等价于下面代码:
// var a;
// console.log(a); // undefined
// a = 3;
// console.log(a); //3
}
doSomething(); //调用函数,进入函数作用域
- 变量和函数命名冲突时
当命名发生冲突时,先进行前置,再进行覆盖
var fn = 3;
function fn () {}
console.log(fn); //3
上述代码等价于
var fn;
function fn() {} //此时fn为函数
// console.log(typeof fn); //function
fn = 3;
// console.log(typeof fn); //number
console.log(fn); //3
同理:
function fn() {}
var fn = 3;
console.log(fn); //3
等价于
function fn() {} // fn为一个全局函数
var fn; // 前面fn函数已经存在,此时兵没有给fn赋值,所以fn仍然是一个函数
// console.log(typeof fn); //function
fn = 3; // 此时给fn赋值了之后,fn变为数值
// console.log(typeof fn); //number
console.log(fn); // 3
- 函数名和参数名重名时,即如下情况:
function fn (fn) {
console.log(fn);
var fn = 3;
console.log(fn);
}
fn(5); //5 3
此时运行的过程等价于:
function fn (fn) {
var fn = 5;//这条语句是JS自动隐藏添加的,当传入参数时,给fn赋值
var fn; // 函数内部作用域变量提升
console.log(fn); // 此时输出的为传入的已经赋值的参数fn,而不是undefined
fn = 3;
console.log(fn); // 输出的是当前作用域内的局部变量fn
}
3.arguments 是什么
在函数内部,可以使用arguments对象获取到该函数的所有传入参数,是一个类数组对象。
var getInfo = function () {
console.log(arguments[0]);
console.log(arguments[1]);
console.log(arguments[2]);
}
getInfo('deejay',21,'male');
4. 函数的"重载"怎样实现
JS没有重载! 同名的函数会覆盖,但是可以在函数体内针对不同的参数调用执行相应的逻辑
可以模拟重载,举例说明:
var getInfo = function (name,age,sex) {
if (name) {
console.log(name);
}
if (age) {
console.log(age);
}
if (sex) {
console.log(sex)
}
}
getInfo('deejay',21); //deejay 21
getInfo('deejay',21,'male'); //deejay 21 male
5.立即执行函数表达式是什么?有什么作用
(function () {
console.log('hello,deejay');
})();
创建一个匿名函数并且立即调用它,一般用于隔离作用域(因为其内部的函数作用域不受外部作用域的影响)
6.求n!,用递归来实现
递归
- 自己调用自己
- 设定终止条件
- 优点:算法简单
- 缺点:效率低
求n!的递归实现:
function fn (n) {
if (n <= 0){
console.log('n为正整数');
return;
}
else if (n === 1) {
return 1;
}
else if (n >=1 ){
return n * fn(n - 1);
}
}
var result = fn(5);
console.log(result);
7. 分析输出结果
function getInfo(name, age, sex){
console.log('name:',name);
console.log('age:', age);
console.log('sex:', sex);
console.log(arguments);
arguments[0] = 'valley';
console.log('name', name);
}
getInfo('deejay', 21, '男');
getInfo('dee', 3);
getInfo('男');
输出结果为:
function getInfo(name, age, sex){
console.log('name:',name);
console.log('age:', age);
console.log('sex:', sex);
console.log(arguments);
arguments[0] = 'valley';
console.log('name', name);
}
getInfo('deejay', 21, '男');
// 输出结果为:
// name: deejay
// age: 2
// sex: 男
// ['deejay',21,'男']
// name valley
getInfo('deejay', 3);
// 输出结果为
// name: deejay
// age: 3
// sex: undefined
// ['deejay',3]
// name valley
getInfo('男');
// 输出结果为
// name: 男
// age: undefined
// sex: undefined
// ['男']
// name valley
8. 写一个函数,返回参数的平方和?
function sumOfSquares(){
}
var result2 = sumOfSquares(1,3)
console.log(result) //29
console.log(result2) //10
平方和代码如下:
function sumOfSquares(){
var sum = 0;
for (var i = 1; i <= arguments.length; i ++) {
sum += Math.pow(arguments[i-1],2);
}
return sum;
}
var result = sumOfSquares(2,3,4);
var result2 = sumOfSquares(1,3)
console.log(result) //29
console.log(result2) //10
9.如下代码的输出?为什么
console.log(a);
var a = 1;
console.log(b);
输出解释如下:
console.log(a);
var a = 1;
console.log(b);
// 等价于
var a;
console.log(a); //undefined 预解析,声明了a,但是没赋值,为undefined
a = 1;
console.log(b); //Uncaught ReferenceError: b is not defined 没有声明b,报错
10.如下代码的输出?为什么
sayName('world');
sayAge(10);
function sayName(name){
console.log('hello ', name);
}
var sayAge = function(age){
console.log(age);
};
解释如下:
sayName('world');
sayAge(10);
function sayName(name){
console.log('hello ', name);
}
var sayAge = function(age){
console.log(age);
};
// 表达式定义的函数,在进行前置的时候,跟用var声明的变量规则一样
// 等价于:
// function sayName(name){
// console.log('hello ', name);
// }
// var sayAge;
// sayName('world');//hello, world
// sayAge(10); //Uncaught TypeError: sayAge is not a function 此时sayAge()只是被声明,并不是一个函数, 报错
// sayAge = function (age) {
// console.log(age);
// }
11. 如下代码输出什么? 写出作用域链查找过程伪代码
var x = 10
bar()
function foo() {
console.log(x) //10
}
function bar(){
var x = 30
foo()
}
伪代码如下:
GlobalContext = {
AO: {
x: 10,
foo: function () {},
bar: function () {},
},
}
foo.[[scope]] = GlobalContext.AO;
bar.[[scope]] = GlobalContext.AO;
fooContext = {
AO: {},
scope: GlobalContext.AO
}
barContext = {
AO:{
x:30,
},
scope: GlobalContext.AO
}
可以看出输出的是GlobalContext.AO中的x的值,为10。
12.如下代码输出什么? 写出作用域链查找过程伪代码
var x = 10;
bar()
function bar(){
var x = 30;
function foo(){
console.log(x)
}
foo();
}
伪代码如下:
GlobalContext = {
AO: {
x:10,
bar: function() {}
},
}
bar.[[scope]] = GlobalContext.AO
barContext = {
AO : {
x: 30,
foo: function () {}
},
scope: GlobalContext.AO
}
fooContext = {
AO: {},
scope: barContext.AO
}
很明显输出的是barContext.AO中的x值,为30
13.以下代码输出什么? 写出作用域链的查找过程伪代码
var x = 10;
bar()
function bar(){
var x = 30;
(function (){
console.log(x)
})()
}
伪代码如下:
GlobalContext = {
AO: {
x: 10,
bar: function() {}
}
}
bar.[[scope]] = GlobalContext.AO
barContext = {
AO: {
x: 30,
匿名函数: function () {}
},
scope: GlobalContext.AO
}
匿名函数Context = {
AO: {},
scope: barContext.AO
}
显然输出的是barContext.AO中的x值,为30
14.以下代码输出什么? 写出作用域链查找过程伪代码
var a = 1;
function fn(){
console.log(a)
var a = 5
console.log(a)
a++
var a
fn3()
fn2()
console.log(a)
function fn2(){
console.log(a)
a = 20
}
}
function fn3(){
console.log(a)
a = 200
}
fn()
console.log(a)
伪代码为:
开始执行程序时的状态值为:
GlobalContext = {
AO: {
a:1,
fn: function () {},
fn3: function () {}
},
}
fnContext = {
AO: {
a:undefined,//解析时的值是undefined,
fn2: function () {}
},
scope: GlobalContext.AO // fn的上一级作用域为global
}
fn3Context = {
AO: {
没有任何活动对象,注意:a = 200,没有用var声明,不是当前作用域即fn3的作用域中的活动对象
},
scope: GlobalContext.AO // fn3的上一级为global
}
fn2Context = {
AO: {
没有任何活动对象,注意:a = 20,没有用var声明,不是当前作用域即fn2的作用域中的活动对象
},
scope: fnContext.AO // fn2的上一级作用域为fn
}
最终的输出结果为:
var a = 1;
function fn (){
console.log(a); //undefined
var a = 5;
console.log(a); //5
a++;
var a;
fn3();
fn2();
console.log(a); //20
function fn2() {
console.log(a); //6
a = 20; //改变了fn中的a(6 ----> 20)
}
}
function fn3 () {
console.log(a); //1
a = 200; //改变了全局中的a (1 ----> 200)
}
fn();
console.log(a); //200
按照运行顺序依次输出为: undefined 5 1 6 20 200