函数,作用域链
1.函数声明和函数表达式有什么区别
函数声明语法:
function functionName(arg0,arg1,arg2){ //函数体 }
函数表达式语法
var function = function(arg0,arg1,arg2){ //函数体 }
区别:
使用function关键字可以声明一个函数,它的特征是函数声明提升,执行代码前会先读取函数声明,即函数声明不必放在调用的前面,它可以放在当前作用域任何位置;函数表达式在使用前必须先赋值,所以声明必须放在调用前面,不然浏览器解析代码时会认为函数还不存在而抛出错误,理解函数提升的关键就是理解函数声明与函数表达式之间的区别。
错误做法
//函数调用
sayHi();
//函数声明
var sayHi = function(){
console.log('Hi!');
};
会抛出错误:函数还不存在
正确写法:
//函数声明
function sayHi(){
console.log('hello');
}
//调用
sayHi()
//调用可以在声明之前
var sayHi = function() {
console.log();
}
sayHi()
//声明必须在调用前,
2.什么是变量的声明前置?什么是函数的声明前置
- 变量声明前置
变量声明出现在代码中的任何位置都会在该代码执行前处理,这意味着变量可以在声明之前使用。这个行为叫"hoisting",即把在指定作用域内声明的变量提升到函数或全局代码的顶部。
声明变量的作用域限制在其声明位置的上下文中,而未声明变量总是全局的,所以总在作用域最开始声明变量可以使变量的作用域变得清晰。
console.log(a); //undefined
var a=1;
console.log(b); //ReferenceError:b is not defined
由上到下执行代码之前,解析器会先找关键字var,找到了var a,就提升var a并将a初始化为undefined
再由上往下执行,读到consolo.log(a),控制台打印出来的就是undefined
接着给变量a赋值为1,如果这个时候后面再加一句consolo.log(a),那么控制台就会多打印出一个1
console.log(b)的结果很好理解,因为没有声明变量b所以抛出错误
也就是说变量声明会在代码执行之前就创建、初始化并赋值undefined
注意:
提升的是var a而不是var a=1,var a=1是后面赋的值。
这也印证了:变量声明会提升,变量的赋值不会提升!!!
- 函数的声明前置
使用function关键字可以声明一个函数,它的特征是函数声明提升,执行代码前会先读取函数声明,即函数声明不必放在调用的前面,它可以放在当前作用域的任何位置。
示例:
a();
function a(){
console.log("hello world")
};
js引擎有以下过程:
1找到所有用function声明的变量,在环境中创建这些变量
2将这些变量初始化并赋值为function(){console.log("hello world")}
3开始执行代码a()
也就是说function声明会在代码执行之前就创建、初始化、赋值。
注意:变量声明和函数声明都会提升,函数声明提升的优先级高于变量声明提升。
示例:
console.log(a);
var a=1;
function a(){};
//输出结果a是一个函数即:ƒ a(){}
3.arguments 是什么?
arguments是一个类数组对象,除了length属性外没有任何数组属性,是所有函数中可用的局部变量,仅在函数内部有效。
-
可通过arguments对象来访问函数的参数列表,使用方括号语法访问参数列表的每一个元素,第一个条目的索引为0,即第一项为arguments[0],第二项为arguments[1],以此类推。
-
通过访问arguments对象的length属性可以确有多少个参数传递给了函数。
-
arguments对象中的值可以被重写并会自动反映到对应的命名参数,所以值与对应命名参数的值保持同步。如果只传入了一个参数,那么为arguments[1]设置的值不会反映到命名参数中,因为arguments对象的长度由传入的参数个数决定,而不由定义函数时的命名参数的个数决定。
-
在严格模式下重写arguments的值会导致语法错误,代码不会执行。
使用场景:调用一个函数时,当这个函数的参数数量比它显式声明的
参数数量更多时,就可以使用 arguments 对象。
4.函数的"重载"怎样实现
-
概念:函数重载指同一函数名对应着多个函数的实现。即每种实现对应一个函数体,这些函数名字相同,但参数类型或个数或顺序不同。
-
函数重载主要是为了解决几个问题
可变参数类型
可变参数个数
可变参数顺序 -
基本设计原则:当两个函数除了参数类型和参数个数不同以外其他功能完全相同时,利用函数重载;两个函数功能不同时不应使用重载,而应使用一个名字不同的函数。
js是弱类型语言,参数不是固定的某个类型,所以在js中没有重载,同名函数后面的会覆盖前面的。但我们也可以实现重载所需要的功能。
- 实现:
写一个函数,在函数体内针对不同的参数调用执行不同的逻辑。
function printPeopleInfo(name,age,sex){
if(name){
console.log(name);
}
if(age){
console.log(age);
}
if(sex){
console.log(sex);
}
}
printPeopleInfo("dot",23); //dot 23
printPeopleInfo("dot","female",23); //dot female 23
function add(){
var num=0;
for(var i=0;i<arguments.length;i++){
num+=arguments[i];
}
console.log(num);
}
add(1); //1
add(1,2,3); //6
始终记住函数名只是一个指向函数对象的指针,并不会与某个函数绑定
5.立即执行函数表达式是什么?有什么作用
- 立即执行函数表达式:
缩写IIFE,是一种利用javascript函数生成新作用域的编程方法,也叫自执行函数。 - 作用:
1令函数中声明的变量绕过js的变量置顶声明规则
2避免新的变量被解释成全局变量或函数名占用全局变量名的情况
3在禁止访问函数內变量声明的情况下允许外部对函数的调用 - 实现:因js里的()里不能包含语句,所以解析器会将()里的代码解析成function表达式并立即执行。
// 以下都能实现立即执行
(function(){ /* code */ }());
(function(){ /* code */ })();
// function前加一元运算符也可实现
!function () { /* code */ } ();
~function () { /* code */ } ();
-function () { /* code */ } ();
+function () { /* code */ } ();
6.求n!,用递归来实现
1.方法一
var factorial = (function f(n){
if (n <= 0){
return 1;
} else {
return n * f(n-1);
}
});
factorial(5); //120
2.方法二
function factorial(n){
if(n === 1) {
return 1;
}
return n * factorial(n-1);
}
factorial(5);
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('饥人谷', 2, '男');
getInfo('小谷', 3);
getInfo('男');
输出分别为:
name: 饥人谷
age: 2
sex: 男
["饥人谷",2,"男"]
name valley
name: 小谷
age: 3
sex: undefined
["小谷",3]
name valley
name: 男
age: undefined
sex: undefined
["男"]
name valley
8.写一个函数,返回参数的平方和
function sumOfSquares() {
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
sum += Math.pow(arguments[i], 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); //undefined,因为变量a声明提升并赋值为undefined,先读取变量声明
var a = 1;
console.log(b); //抛出ReferenceError:b is not defined,因为b没有声明
10.如下代码的输出是?为什么
sayName('world');
sayAge(10);
function sayName(name){
console.log('hello ', name); //hello world,因为sayName函数声明提升
}
var sayAge = function(age){
console.log(age); //抛出TypeError: sayAge is not a function,因为sayAge是函数表达式,使用前必须赋值,而声明放在了调用的后面,此时函数还不存在,所以会报错
};
11.如下代码输出什么? 写出作用域链查找过程伪代码
var x = 10;
bar() ;
function foo() {
console.log(x);
}
function bar(){
var x = 30;
foo();
}
结果为10
// 对应的作用域链
globalContext = {
AO: {
x: 10,
bar: function() {}
foo: function() {}
}
Scope: null
}
bar.[[scope]] = globalContext.AO;
foo.[[scope]] = globalContext.AO;
barContext = {
AO: {
x: 30,
}
Scope: globalContext.AO;
}
fooContext = {
AO: {},
Scope: globalContext.AO;
}
12.如下代码输出什么? 写出作用域链查找过程伪代码
var x = 10;
bar()
function bar(){
var x = 30;
function foo(){
console.log(x)
}
foo();
}
结果为30
// 对应的作用域链
globalContext = {
AO: {
x: 10,
bar: function() {}
}
Scope: null
}
bar.[[scope]] = globalContext.AO;
barContext = {
AO: {
x: 30,
foo: function() {}
}
Scope: globalContext.AO;
}
foo.[[scope]] = barContext.AO;
fooContext = {
AO: {},
Scope: barContext.AO;
}
13、如下代码输出什么? 写出作用域链查找过程伪代码
var x = 10;
bar()
function bar(){
var x = 30;
(function (){
console.log(x)
})()
}
结果为30
// 作用域链
globalContext = {
AO: {
x: 10,
bar: function() {};
}
Scope: null
}
bar.[[scope]] = globalContext.AO;
barContext = {
AO: {
x: 30,
anonymity :function() {}
}
Scope:globalContext.AO;
}
anonymity.[[scope]] = barContext.AO;
14、如下代码输出什么? 写出作用域链查找过程伪代码、
var a = 1;
function fn(){
console.log(a) // (1)
var a = 5
console.log(a) // (2)
a++
var a
fn3() // (3)
fn2() // (4)
console.log(a) // (5)
function fn2(){
console.log(a)
a = 20
}
}
function fn3(){
console.log(a)
a = 200
}
fn()
console.log(a) // (6)
在上面标注了输出时代码的序号。
// 执行到(1)时对应的作用域链
globalContext = {
AO:{
a:1,
fn: function() {},
fn3: function() {},
}
Scope: null;
}
fn.[[scope]] = globalContext.AO;
fn3.[[scope]] = globalContext.AO;
fnContext = {
AO: {
a: undefined,
fn2: function() {}
}
scope: fn.[[scope]] = globalContext.AO;
}
fn2.[[scope]] = fnContext.AO;
(1)此时输出 undefined
// 执行到(2)时对应的作用链
globalContext = {
AO:{
a:1,
fn: function() {},
fn3: function() {},
}
Scope: null;
}
fn.[[scope]] = globalContext.AO;
fn3.[[scope]] = globalContext.AO;
fnContext = {
AO: {
a: 5,
fn2: function() {}
}
scope: fn.[[scope]] = globalContext.AO;
}
fn2.[[scope]] = fnContext.AO;
(2)此时结果:undefined 5
// 执行到(3)时对应的作用域链
globalContext = {
AO:{
a:1,
fn: function() {},
fn3: function() {},
}
Scope: null;
}
fn.[[scope]] = globalContext.AO;
fn3.[[scope]] = globalContext.AO;
fnContext = {
AO: {
a: 6,
fn2: function() {}
}
scope: fn.[[scope]] = globalContext.AO;
}
fn2.[[scope]] = fnContext.AO;
fn3Content = {
AO:{}
scope: fn3.[[scope]] = globalContext.AO;
}
(3)此时结果:undefined 5 1
// 执行到(4)对应......
globalContext = {
AO:{
a:200,
fn: function() {},
fn3: function() {},
}
Scope: null;
}
fn.[[scope]] = globalContext.AO;
fn3.[[scope]] = globalContext.AO;
fnContext = {
AO: {
a: 6,
fn2: function() {}
}
scope: fn.[[scope]] = globalContext.AO;
}
fn2.[[scope]] = fnContext.AO;
fn3Content = {
AO:{}
scope: fn3.[[scope]] = globalContext.AO;
}
fn2Content = {
AO: {}
scope: fn2.[[scope]] = fnContext.AO;
}
(4)此时结果为 undefined 5 1 6
// (5)对应的作用链
globalContext = {
AO:{
a:200,
fn: function() {},
fn3: function() {},
}
Scope: null;
}
fn.[[scope]] = globalContext.AO;
fn3.[[scope]] = globalContext.AO;
fnContext = {
AO: {
a: 20,
fn2: function() {}
}
scope: fn.[[scope]] = globalContext.AO;
}
fn2.[[scope]] = fnContext.AO;
fn3Content = {
AO:{}
scope: fn3.[[scope]] = globalContext.AO;
}
fn2Content = {
AO: {}
scope: fn2.[[scope]] = fnContext.AO;
}
(5)此时结果为undefined 5 1 6 20
// (6)
globalContext = {
AO:{
a:200,
fn: function() {},
fn3: function() {},
}
Scope: null;
}
fn.[[scope]] = globalContext.AO;
fn3.[[scope]] = globalContext.AO;
fnContext = {
AO: {
a: 20,
fn2: function() {}
}
scope: fn.[[scope]] = globalContext.AO;
}
fn2.[[scope]] = fnContext.AO;
fn3Content = {
AO:{}
scope: fn3.[[scope]] = globalContext.AO;
}
fn2Content = {
AO: {}
scope: fn2.[[scope]] = fnContext.AO;
}
(6)输出结果 : undefined 5 1 6 20 200