函数与作用域

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("这里是函数表达式");

}

关于立即执行函数的讨论

  • //情况1

//结果会被输出

varfn=function(){

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

}();

  • //情况2

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

functionfn(){

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

}();

  • //情况3

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

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](http://upload-images.jianshu.io/upload_images/6760918-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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容

  • 1. 函数声明和函数表达式有什么区别 使用function关键字声明一个函数时,声明不必放到调用的前面。//函数声...
    _李祺阅读 238评论 0 0
  • 函数声明和函数表达式有什么区别? 函数声明和函数表达式是EMACScript规定的两种不同的声明函数的方法。1.函...
    LeeoZz阅读 326评论 0 1
  • 1.函数声明和函数表达式有什么区别。 函数声明使用function来进行声明,函数声明提升,所以不论执行语句的位置...
    Rising_suns阅读 275评论 0 0
  • 任务 函数声明和函数表达式有什么区别答:函数声明:function functionName(){}  函数表达式...
    mhy_web阅读 390评论 0 0
  • JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里。 函数声明和函数表达式有什么区...
    毕子歌阅读 340评论 0 0