JS函数

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

  • 函数声明可以看作是函数的初始化,我们将给函数传参并建立函数体的表达式,当我门建立完成后,就可以运行行函数的表达式了,做法如下:function foo(){}, foo();
  • 函数表达式其实就是创建一个匿名的函数声明并赋值给一个变量,如var foo = function () {}。
  • 使用函数声明的时候,其定义的foo函数名会受到javascript的变量名提升机制的影响,而通过函数表达式创建的函数,当然其最明显直接的特征就是省略了函数名.
  • 另外还有几种函数的声明写法,比如自执行函数,主要用于创建一个新的作用域,在此作用域内声明的变量不会和其它作用域内的变量冲突或混淆,大多是以匿名函数方式存在,且立即自动执行,如:(function(){var x = xx; return x}),还有就是一些js的函数设计模式,比如构造,工厂,混合,等....

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

  • 变量提升:当一个变量被定义时,在代码执行前会先将变量进行初始化(提升到当前作用域顶部)再执行语句。
console.log(a)  //undefined
var a=1
console.log(a)  //1
  • 函数提升:当函数以函数声明的方式声明时,代码执行前会首先生成该函数(提升到当前作用域顶部),然后再执行语句
fn('hello')  //"hello"
function fn(str){
  console.log(str)
}

3、arguments 是什么

  • 函数声明或函数表达式中,如function fn(v1,v2){...}中,v1、v2是函数的形参,而在实际调用时传入的参数会存入arguments中,如fn(3,4,5),那么arguments的长度就是3,分别对应arguments[0]:3,arguments[1]:4,arguments[2]:5
  • arguments是类数组对象,每个函数中都存在argument对象,argument并不是一个真正的数组,所以不具备除length属性之外的属性,这个对象维护这所有传入该函数的参数列表。
    通过以下语句可将arguments转化为数组对象
var args=Array.prototype.slice.call(arguments)

4、函数的"重载"怎样实现

  • 重载是很多面向对象语言实现多态的手段之一,在静态语言中确定一个函数的手段是靠方法签名——函数名+参数列表,也就是说相同名字的函数参数个数不同或者顺序不同都被认为是不同的函数,称为函数重载。
  • 在JavaScript中没有函数重载的概念,函数通过名字确定唯一性,参数不同也被认为是相同的函数,后面的覆盖前面的,但可以在函数体针对不同的参数调用执行相应的逻辑。
function printPeopleInfo (name, age, sex){
    if(name){
      console.log(name);
    }
    if(age){
      console.log(age);
    }
    if(sex){
      console.log(sex);
    }
  }
  printPeopleInfo('Byron', 26);
  printPeopleInfo('Byron', 26, 'male');

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

我们都知道,一般定义一个函数有函数声明和函数表达式两种方法:
function fnName () {…};   //函数声明
var fnName = function () {…};   //函数表达式
两者的区别是:

  • Javascript引擎在解析javascript代码时会‘函数声明提升'(Function declaration Hoisting)当前执行环境(作用域)上的函数声明,而函数表达式必须等到Javascirtp引擎执行到它所在行时,才会从上而下一行一行地解析函数表达式。
  • 函数表达式后面可以加括号立即调用该函数,函数声明不可以,只能以fnName()形式调用 。

所以,要在函数体后面加括号就能立即调用,则这个函数必须是函数表达式,不能是函数声明。

在function前面加()、!、+、-、=等运算符,都将函数声明转换成函数表达式,消除了javascript引擎识别函数表达式和函数声明的歧义,告诉javascript引擎这是一个函数表达式,不是函数声明,可以在后面加括号,并立即执行函数的代码。

(function(){
  console.log(123)
})()         //输出123

(function(){
  console.log(123)
}())         //输出123

!function(){
  console.log(123)
}()         //输出123

+function(){
  console.log(123)
}()         //输出123

-function(){
  console.log(123)
}()         //输出123

var a=function(){
  console.log(123)
}()         //输出123

加括号是最安全的做法,因为!、+、-等运算符还会和函数的返回值进行运算,有时造成不必要的麻烦。

那么这样做有什么作用:
在全局或局部作用域中声明了一些变量,可能会被其他人不小心用同名的变量给覆盖掉,根据javascript函数作用域链的特性,可以使用这种技术可以模仿一个私有作用域,用匿名函数作为一个“容器”,“容器”内部可以访问外部的变量,而外部环境不能访问“容器”内部的变量,所以( function(){…} )()内部定义的变量不会和外部的变量发生冲突,俗称“匿名包裹器”或“命名空间”。

6、求n!,用递归来实现

利用n!等于n(n-1)!,(n-1)!等于(n-1)((n-1)-1)!,直至括号内的值为1,另外0!等于1。

function fac(n){
  if( n===1 || n===0 ){return 1}
  return (n*fac(n-1))
}
console.log(fac(1))     //输出1
console.log(fac(2))     //输出2
console.log(fac(3))     //输出6
console.log(fac(4))     //输出24
console.log(fac(5))     //输出120

也可以用三元运算符 ' ? ' 写作:

function fac(n){
  return n===0 || n===1 ? 1 : n*fac(n-1)
}
console.log(fac(5))     //输出120

另外也可以利用循环来处理

function fac(n){
  var i =1
  if( n===1 || n===0 ){console.log(1)}
  else{
    for( var j=n;j>1;j--){
      i*=j
    }
    console.log(i)
  } 
}
fac(5)     //输出120

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('男');

注意:

  • console.log(…)输出的并不是括号内的返回值,而是输出括号内所有表达式的值。
  • 给函数传入参数时是按顺序传入,不会自动识别,没有传入参数则为undefined。
    所以getInfo('饥人谷', 2, '男');相当于:
function getInfo(){
            arguments[0]='饥人谷'
            arguments[1]=2
            arguments[2]='男'
            console.log('name:','饥人谷')     
            console.log('age:', 2)
            console.log('sex:', '男')
            console.log(['饥人谷',2,'男'])
            arguments[0] = 'valley'
            console.log('name', 'valley')
}
getInfo()

/*输出:
name: 饥人谷
age: 2
sex: 男
["饥人谷", 2, "男"]
name valley
*/

getInfo('小谷', 3);相当于

function getInfo(){
            arguments[0]='饥人谷'
            arguments[1]=3
            arguments[2]=undefined
            console.log('name:','饥人谷')     
            console.log('age:', 3)
            console.log('sex:', undefined)
            console.log(['饥人谷',3])
            arguments[0] = 'valley'
            console.log('name', 'valley')
}
getInfo()

/*输出:
name: 饥人谷
age: 3
sex: undefined
["饥人谷", 3]
name valley
*/

getInfo('男');相当于

function getInfo(){
            arguments[0]='男'
            arguments[1]=undefined
            arguments[2]=undefined
            console.log('name:','男')     
            console.log('age:', undefined)
            console.log('sex:', undefined)
            console.log(['男'])
            arguments[0] = 'valley'
            console.log('name', 'valley')
}
getInfo()

/*输出:
name: 男
age: undefined
sex: undefined
["男"]
name valley
*/

8、 写一个函数,返回参数的平方和?

function sumOfSquares(){
   }
   var result = sumOfSquares(2,3,4)
   var result2 = sumOfSquares(1,3)
   console.log(result)  //29
   console.log(result2)  //10

解答思路:遍历每一个传入的参数,求它们的平方和;通过不同遍历的方法,有不同的写法。

  • for循环方法

循环每执行一次,都要检查一次 array.length 的值,读属性要比读局部变量慢,尤其是当 array 里存放的都是 DOM 元素(像 array = document.getElementByClassName(“class”);),因为每次读 array.length 都要扫描一遍页面上 class=”class” 的元素,速度更是慢得抓狂。

function sumOfSquares(){
    var sum=0
    for(var i=0;i<arguments.length;i++){
        sum+=arguments[i]*arguments[i]
    }
    return sum
}
var result = sumOfSquares(2,3,4)
var result2 = sumOfSquares(1,3)
console.log(result)  //29
console.log(result2)  //10
  • for-in循环方法

for-in 需要分析出 array 的每个属性,这个操作的性能开销很大

function sumOfSquares(){
    var sum=0
    for(i in arguments){
        sum+=arguments[i]*arguments[i]
    }
    return sum
}
  var result = sumOfSquares(2,3,4)
  var result2 = sumOfSquares(1,3)
  console.log(result)  //29
  console.log(result2)  //10
  • 先把数组的长度先查出来,存进一个局部变量,那么循环的速度将会大大提高
function sumOfSquares(){
    var sum=0
    var length=arguments.length
    for(var i=0;i<length;i++){
        sum+=arguments[i]*arguments[i]
    }
    return sum
}
  var result = sumOfSquares(2,3,4)
  var result2 = sumOfSquares(1,3)
  console.log(result)  //29
  console.log(result2)  //10
  • 不过我们还可以让它更快。如果循环终止条件不需要进行比较运算,那么循环的速度还可以更快
  • 把数组下标改成向 0 递减,循环终止条件只需要判断 i 是否为 0 就行了。因为循环增量和循环终止条件结合在一起,所以可以写成更简单的 while 循环
function sumOfSquares(){
    var sum=0
    var i=arguments.length
    while(i--){
        sum+=arguments[i]*arguments[i]
    }
    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);

由于变量提升的原则,上述代码相当于

var a
console.log(a)    //输出undefined
a=1
console.log(b)    //报错:Uncaught ReferenceError: b is not defined
  • 原因:先声明了变量a,a并没有复制,所以此时输出a得到undefined;b没有声明就直接调用,所以会报错。

10、如下代码的输出?为什么

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

输出:"hello" "world" Uncaught TypeError: sayAge is not a function

  • 原因:由于函数声明会自动提升,而函数表达式不会;sayName('world');正常执行;sayAge(10);会调用函数sayAge但此时只声明了sayAge是变量,并未将函数声明赋值给它,所以它还不是函数,所以报错。

11、 如下代码输出什么? 为什么

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

输出:10

  • 原因:函数的作用域与其定义时所在的作用域有关,与其调用时所在的作用域无关;在bar()中可以调用全局作用域中的foo(),而foo()不能访问bar()的局部变量x(x=30),只能访问全局作用域中的全局变量x(x=10),所以输出10。

12、如下代码输出什么? 为什么

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

输出:30

  • 原因:变量的查找是就近原则,去寻找var定义的变量,当就近没有找到的时候就去查找外层。函数域优先于全局域,故局部变量x会覆盖掉全局变量x,所以输出30。

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
    (no-name): function
  }
  Scope: bar.[[scope]] //globalContext.AO
}
(no-name).[[scope]] = barContext.AO

(no-name)Context = {
  AO: {}
  Scope: (no-name).[[scope]] //barContext.AO
}

当调用 (no-name)() 时,先从 (no-name) 执行上下文中的 AO里找,找不到再从 bar 的 [[scope]]里找,得到x=30
找到后即调用

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

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
  }
}

function fn3(){
  console.log(a)     //输出1
  a = 200
}

fn()
console.log(a)     //输出200

伪代码如下:

globalContext = {
  AO: {
    a: 1
    fn: function
    fn3: function
  }
  Scope: null
}
fn.[[scope]] = globalContext.AO
fn3.[[scope]] = globalContext.AO
//执行fn()
fnContext = {
  AO: {
    a: undefined
    fn2: function
  }
  Scope:  fn.[[scope]] //globalContext.AO
}
//执行console.log(a)     //输出undefined
//a = 5
fnContext = {
  AO: {
    a: 5
    fn2: function
  }
  Scope:  fn.[[scope]] //globalContext.AO
}
fn2.[[scope]] = fnContext.AO
//执行console.log(a)     //输出5
//  a++
fnContext = {
  AO: {
    a: 6
    fn2: function
  }
  Scope:  fn.[[scope]] //globalContext.AO
}
fn2.[[scope]] = fnContext.AO
//执行fn3()
fn3Context = {
  AO: {}
  Scope:  fn3.[[scope]] //globalContext.AO
}
//执行console.log(a)     //输出1
//a=200全局中a变为200
//执行fn2()
fn2Context = {
  AO: {}
  Scope:  fn2.[[scope]] //fnContext.AO
}
//执行console.log(a)     //输出6
//a=20fn中a变为20
//执行console.log(a)     //输出20
//执行console.log(a)     //输出200

所以最终输出

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

推荐阅读更多精彩内容

  • 函数声明和函数表达式有什么区别 (*)解析器会率先读取函数声明,并使其在执行任何代码之前可以访问;函数表达式则必须...
    coolheadedY阅读 385评论 0 1
  • 1. 函数声明和函数表达式有什么区别 (*) 函数在JS中有三种方式来定义:函数声明(function decla...
    进击的阿群阅读 440评论 0 1
  • 概念 1、函数声明和函数表达式有什么区别? ECMAScript规定了三种声明函数方式: 构造函数首先函数也是对象...
    周花花啊阅读 465评论 1 1
  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,132评论 0 13
  • 1.函数声明和函数表达式有什么区别 ? 2.什么是变量的声明前置?什么是函数的声明前置 js引擎的工作方式:先解析...
    饥人谷区子铭阅读 437评论 0 1