函数与作用域

2017-08-01  本文已影响0人  进击的前端_风笑影

1,函数声明和函数表达式有什么区别

1、背景介绍

定义函数的方法主要有三种:

1:函数声明(Function Declaration)

2:函数表达式Function Expression)

3:new Function构造函数

其中,经常使用的是函数声明和函数表达式的函数定义方法,这两种方法有着很微妙的区别和联系,

而且这两种方法的使用也容易混淆,所以我们来讲一下两者有哪些不同之处

2:知识剖析

函数声明的典型格式:

function functionName(arg1, arg2, ...){}

函数表达式

函数表达式的典型格式:

var variable=function(arg1, arg2, ...){}

3、常见问题

两者具体有哪些区别呢

一、Javascript引擎在解析javascript代码时会‘函数声明提升'(Function declaration Hoisting)

当前执行环境(作用域)上的函数声明,而函数表达式必须等到Javascirtp引擎执行到它所在行时,

才会从上而下一行一行地解析函数表达式

,二、函数表达式后面可以加括号立即调用该函数,函数声明不可以,只能以fn()形式调用 。

以下是两者差别的两个例子。

fn1();//不会报错,因为"提升了"函数声明,函数调用可在函数声明之前

function fn1(){

console.log("这里是函数声明");

}

fn2();//会报错,变量fn2还未保存对函数的引用,函数调用必须在函数表达式之后

var fn2=function(){

console.log("这里是函数表达式");

}

关于立即执行函数的讨论

//结果会被输出

varfn=function(){

console.log("函数表达式赋值给一个变量");

}();

//结果不会被输出,JavaScript引擎只解析函数声明,忽略后面的括号,函数声明不会被调用

functionfn(){

console.log("函数声明");

}();

//语法错误,匿名函数属于函数表达式,未执行赋值操作,不能被调用

function(){

console.log("函数表达式");

}();

使用两种方式创建函数时都发生了什么

函数声明解析过程如下:

  1. 创建一个new Function对象,FormalParameterList指定参数,FunctionBody指定函数体。

将当前正在运行环境中作用域链作为它的作用域。

  1. 为当前变量对象创建一个名为Identifier的属性,值为Result(1)。

具名函数表达式的解析过程如下:

  1. 创建一个new Object对象

  2. 将Result(1)添加到作用域链的顶端

  3. 创建一个new Function对象,FormalParameterList指定参数,FunctionBody指定函数体。将当前正在运行的执行环境中作用域链作为它的作用域。

  4. 为Result(1)创建一个名为Identifier 的属性,其值为为Result(3),只读,不可删除

  5. 从作用域链中移除Result(1)

  6. 返回Result(3)

2,什么是变量的声明前置?什么是函数的声明前置

变量的声明前置JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,给他初始值undefined,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升。

变量提升

上面的效果JS引擎会自动帮我们去这样解析:

默认解析方式

函数的声明前置函数的声明前置包括两种情况,分别为函数声明和函数表达式:函数表达式

函数表达式声明前置

上面的代码浏览器会自动帮我们转成下面的方式,效果都是一样的:

JS引擎默认这么做

从上面可以看出函数表达式的声明前置只是将变量的声明前置而已,并不是将整个表达式前置。

函数声明但是对于函数声明前置的则是整个函数声明,即使函数写在最后也可以在前面语句调用: 函数声明的前置

最后总结一下:
变量声明会提前到作用域的顶部,而赋值部分会留在原来的地方,按次序执行;
函数表达式其实和变量是一样的,只是将一个函数赋值给变量,同样也只是这个变量会提前,函数赋值给变量部分依然留在原地;
但是对于函数声明就不一样,整个声明都会被前置,但是总是跟随在变量声明的后面,所以即使我们把函数写在最后也可以被前面的语句调用。

3,arguments 是什么?

arguments是一个类数组对象,里面存储的是函数在执行时所传递进来的参数。
arguments只在函数内部有效,可以在函数内部通过使用arguments对象来获取函数的所有参数;这个对象为传递给函数的每一个参数建立一个索引,从0开始;外部传进来的参数还可以通过这个方法被重新赋值;
arguments对象类似于数组,但它并不是真正的数组,除了length,它没有数组所特有的属性和方法;

arguments主要用途:arguments对于参数数量是一个可变量的函数来说比较有用。 当我们传递给这个函数的参数数量比它显式声明的参数数量更多的时候,你就可以使用 arguments对象获取到该函数的所有传入参数。 arguments用法

4,函数的"重载"怎样实现?

重载是很多面向对象语言实现多态的手段之一,在静态语言中相同名字的函数参数个数不同或者顺序不同都被认为是不同的函数,称为函数重载。
在JS中没有函数重载的概念,函数通过名字确定唯一性,参数不同也被认为是相同的函数,后面的覆盖前面的。

JS中没有重载

那么难道JS中我们就不能通过重载实现一个函数参数不同功能不同吗?这里就可以用到上面的arguments类数组对象来实现了;

用arguments实现重载

5立即执行函数表达式是什么?有什么作用

立即执行函数表达式(Immediately-Invoked Function Expression)是一种语法,可以在你的函数在定义后立即被执行,这种模式本质上就是函数表达式(命名或者匿名的),在创建后立即执行,其代码格式如下:
//在最前最后加括号
(function atOnce(){
console.log('abc');
}());
//在function外面加括号
(function atOnce(){
console.log('abc');
})();
立即执行函数表达式的作用:

求n!,用递归来实现

Paste_Image.png

·```
function factorial(n){
if(n==0) return 1;
if(n<0) return 'error';
var f = 1;
for(var i=1;i<=n;i++){
f*=i;
}
return f;
}

####以下代码输出什么?

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('hunger', 28, '男');
getInfo('hunger', 28);
getInfo('男');

输出:
name: hunger
age: 28
sex: 男
["hunger", 28, "男"]
name valley

name: hunger
age: 28
sex: undefined
["hunger", 28]
name valley

name: 男
age: undefined
sex: undefined
["男"]
name valley
####写一个函数,返回参数的平方和?

![Paste_Image.png](https://img.haomeiwen.com/i6760918/1fb3003249f83f61.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
####如下代码的输出?为什么

console.log(a);
var a = 1;
console.log(b);

输出:
undefined
未定义的错误
由JavaScript的变量提升,变量a被提升了,所以声明的时候放置在代码的前端,所以输出a的时候是undefined。由于b在作用域内无法找到其声明,所以报出未定义的错误。
 ####如下代码的输出?为什么

sayName('world');
sayAge(10);
function sayName(name){
console.log('hello ', name);
}
var sayAge = function(age){
console.log(age);
};

输出:
"hello world"
Error: sayAge is not a function
在JavaScript中,函数声明可以被提升,所以sayName(name)可以放置在函数声明前也可以被调用。而对于函数表达式就不存在函数提升,只有变量提升,所以sayAge就被提升到了代码前端。当使用sayAge(10)时,由于此时解析器只知道sayAge是一个变量,而不知道它是一个函数(即使在后面实现了这个函数),所以报出"sayAge is not a function"的错误。
####如下代码输出什么? 写出作用域链查找过程伪代码?

var x = 10
bar()
function foo() {
console.log(x)
}
function bar(){
var x = 30
foo()
}

10

globalEC = {
AO: {
x: 10,
foo: function,
bar: function
},
Scope: null
}
foo.[[scope]] = globalEC. AO
bar.[[scope]] = globalEC. AO

barEC = {
AO: {
x: 30
}
Scope: bar.[[scope]] = globalEC. AO
}

fooEC = {
AO: {
}
Scope: bar.[[scope]] = globalEC. AO
}

 ####如下代码输出什么? 写出作用域链查找过程伪代码

var x = 10;
bar()
function bar(){
var x = 30;
function foo(){
console.log(x)
}
foo();
}

30

globalEC = {
AO: {
x: 10,
bar: function
},
Scope: null
}
bar.[[scope]] = globalEC. AO

barEC = {
AO: {
x: 30
foo: function,
}
Scope: bar.[[scope]] = globalEC. AO
}
foo.[[scope]] = barEC. AO

fooEC = {
AO: {
}
Scope: foo.[[scope]] = barEC. AO
}

##以下代码输出什么? 写出作用域链的查找过程伪代码

var x = 10;
bar()
function bar(){
var x = 30;
(function (){
console.log(x)
})()
}

30

globalEC = {
AO: {
x: 10,
bar: function
},
Scope: null
}
bar.[[scope]] = globalEC. AO

barEC = {
AO: {
x: 30
function(){}
}
Scope: bar.[[scope]] = globalEC. AO
}
function.[[scope]] = barEC. AO

functionEC = {
AO: {
}
Scope: function.[[scope]] = barEC. AO
}

####以下代码输出什么? 写出作用域链查找过程伪代码

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)

undefined
5
1
6
20
200

globalEC = {
AO: {
a: 1,
fn: function
fn3: function
},
Scope: null
}
fn.[[scope]] = globalEC. AO
fn3.[[scope]] = globalEC. AO

执行 fn()

fnEC = {
AO: {
a: 5
fn2: function,
}
Scope: bar.[[scope]] = globalEC. AO
}
fn2.[[scope]] =fnEC. AO

执行 fn3()

fn3EC = {
AO: {
}
Scope: fn3.[[scope]] = globalEC. AO
}

fn3()执行完毕跳回fn()
执行fn2()

fn2EC = {
AO: {
}
Scope: fn2.[[scope]] =fnEC. AO

fn2()执行完毕跳回fn()
fn()执行完毕跳回globalEC

上一篇 下一篇

猜你喜欢

热点阅读