饥人谷技术博客

JS函数

2019-03-02  本文已影响66人  吴一晏

函数的本质

函数是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值。

五种声明

  1. 具名函数
    function 函数名(参数1,参数2){ 函数体 } function:关键字 。函数名本来是有七种数据类型,但是在这里是函数,可以看成是特例。
    函数名.toString()>>>"function 函数名(参数1,参数2){ 函数体 }"
  2. 匿名函数+var
    var x x = function(){}
  3. 具名函数+var
    var x = function y(input1,input2){}
    console.log(y)>>>会报错y is not defined
    如果是具名函数会打出函数本身,这就是加了var和不加var的区别之一。
    使用这种方法声明函数时,函数名y只有在函数体内部生效,在外不生效,这就是为什么console.log会报错的原因。
    4.Function构造函数
    f = new Function('x','y','return x+y')Function构造函数接受了三个参数,只有最后一个参数是函数体,其余都是f函数的参数。你可以传送多个参数给构造函数,只有最后一个参数会被当成函数体。如果只有一个参数,那么这个参数就是函数体。
var foo = new Function(
  'return "hello world";'
);

// 等同于
function foo() {
  return 'hello world';
}
  1. 箭头函数(等同于匿名函数)
    f = (x,y) => {return x+y}
    当函数体只有一句话,不返回对象时,return和大括号能同时省略f = (x,y) => x+y
    当参数也只有一个的时候,括号也能省略y = n =>n*n
    多行的写法:f = (x,y) =>{let n=x*3;let m=y*2;return m+n}

函数的重复声明

如果同一个函数被多次声明,后面的声明就会覆盖前面的声明。

function f() {
  console.log(1);
}
f() // 2

function f() {
  console.log(2);
}
f() // 2

上面代码中,后一次的函数声明覆盖了前面一次。而且,由于函数名的提升,前一次声明在任何时候都是无效的,这一点要特别注意。

函数的name属性

  1. 具名函数
function  f(){}
f.name//"f"
  1. 匿名函数+var
var f = function (){}
f.name//"f"
  1. 具名函数+var
var f = function f1(){}
f.name//"f1"
f1.name//报错 f1 is not defined
  1. Function构造函数
f = new Function('x','y','return x+y')
f.name//"anonymous" 匿名的英文单词

函数的调用,return和递归

调用函数有两种:f()f.call()

f = function(x,y){ return x+y}
f(1,2)//3
//等同于
f.call(undefined,1,2)//3

f.call()方法语法是f.call(this,arguments)(在后面会讲到)

函数体内部的return语句,表示返回。JavaScript 引擎遇到return语句,就直接返回return后面的那个表达式的值,后面即使还有语句,也不会得到执行。也就是说,return语句所带的那个表达式,就是函数的返回值。return语句不是必需的,如果没有的话,该函数就不返回任何值,或者说返回undefined

函数可以调用自身,这就是递归(recursion)。下面代码就是用递归计算1+2+....+n的值

function sum(n){
       if (n===1){
       return 1
}else{
        return n+sum.call(undefined,n-1)
}
}

上面代码中,在sum中又调用了sum。

this和arguments

与其他语言相比,函数的 this 关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别。

  1. 全局模式下
    无论是否在严格模式下,在全局执行环境中(在任何函数体外部)this 都指向全局对象。
// 在浏览器中, window 对象同时也是全局对象:
console.log(this === window); // true

a = 37;
console.log(window.a); // 37

this.b = "MDN";
console.log(window.b)  // "MDN"
console.log(b)         // "MDN"
  1. 在函数内部
    在函数内部,this的值取决于函数被调用的方式。
    简单模式下, this的值不是由该调用设置的,所以 this 的值默认指向全局对象。
function f(){
      console.log(this)//window
}

严格模式下,this将保持他进入执行环境时的值,如果this 没有被执行环境(execution context)定义,那它将保持为 undefined

function f2(){
  "use strict"; // 这里是严格模式
  console.log(this) //undefined
}

函数调用里讲到 f.call(this,arguments) 严格模式下,this就是函数调用时候的第一个参数,给什么就是什么,如果只有一个参数,这个参数就是this

function f(){
      "use strict"
      console.log(this)
      console.log(this === window)
}
f.call(undefined)// undefined, false
f.call(1,2)//1,false
f.call(a)// a,false 只有一个参数

f.call(this,arguments)arguments是除了第一位的后面参数,他返回的是一个伪数组arguments.__proto__ === Object.prototype

function f1(){
      console.log(arguments)
}
f1(1,2,3)//Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
//注意和f1.call(1,2,3)的区别,1是this,2,3才是arguments
f1.call(1,2,3)//Arguments(2) [2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]

call stack 调用栈

Call stack,意思是调用堆栈,调用堆栈是一个方法列表,按调用顺序保存所有在运行期被调用的方法。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
遵循先入后出的原因。

作用域

作用域分两种:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。

  1. 对于顶层函数来说,函数外部声明的变量就是全局变量,它可以在函数内部读取。
var v = 1;
function f() {
  console.log(v);
}
f()//1

上面代码中,函数f内部可以读取全局变量v

  1. 在函数内部定义的变量,外部无法读取,称为“局部变量”
function f(){
  var v = 1;
}
v // ReferenceError: v is not defined
  1. 函数内部定义的变量,会在该作用域内覆盖同名全局变量。
var v = 1;
function f(){
  var v = 2;
  console.log(v);
}
f() // 2 函数内作用域里覆盖全局变量
v // 1

4 .对于var命令来说,只有在局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。

if (true) {
  var x = 5;
}
console.log(x);  // 5

上面代码中,x变量是在条件判断区块里声明的,但是依然可以在区块外读取。

函数内部的变量提升

与全局作用域一样,函数作用域内部也会产生“变量提升”现象。var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。

function f(){
      console.log(a)
      var a = 1
}
//等同于
function f(){
      var a
      console.log(a)
       a = 1
}
f()//undefined 因为先声明,还没赋值就打出a,所以a就是undefined
  1. 练习题1
var a = 1
function f1(){
    alert(a) // 是多少
    var a = 2
}
f1.call()

考虑变量提升,等同于

var a = 1
function f1(){
    var a
    alert(a) // 是多少
    a = 2
}
f1.call()//undefined
  1. 练习题2
var a = 1
function f1(){
    var a = 2
    f2.call()
}
function f2(){
    console.log(a) // 是多少
}
f1.call()//1

f1.call()调用f1函数,得到f2.call().再调用f2函数得到 console.log(a),f2函数是顶层函数,他作用范围内没有a的赋值,因此向上找父作用范围:全局范围,因此f1.call()等于1

  1. 练习题3
var liTags = document.querySelectorAll('li')
for(var i = 0; i<liTags.length; i++){
    liTags[i].onclick = function(){
        console.log(i) // 点击第3个 li 时,打印 2 还是打印 6?
    }
}

console.log(i)会在他之上的代码全部运行结束后再执行,这里i是动态取值的,所以在最后运行完整个for循环,得到的值是6

  1. 做题技巧
    遇到这类题时,先不要着急做。1:考虑变量提升,2:作用域只能确定变量在哪个范围,但是不能确定变量的值。

闭包

闭包是啥:如果一个函数使用了他范围外的变量,那么这个变量+这个函数就叫做闭包。

上一篇下一篇

猜你喜欢

热点阅读