JS系列 -- Function 浅析

2018-01-08  本文已影响80人  bowen_wu

概述

函数是一个可以执行代码的对象。每个函数都是 Function 类型的实例,并且都与其他引用类型一样具有属性和方法。

Function

每个 Function 的实例的 __proto__ 都指向了 Function.prototype(原型),函数原型(Function.prototype)的 __proto__ 指向了 Object.prototype(对象的原型)

定义函数

  1. 函数声明

    function name( param1[ , param2[, ...] ] ){
        return
    }
    

    如果 return 省略,那么浏览器将自动添加 return undefined
    function 是关键字,它只能用来声明函数,而 var 则可以用来声明7 种数据类型(number、string、boolean、null、undefined、symbol、 object)中的任意一种。

  2. 函数表达式

    var x = function( param1[ , param2[, ...] ] ){
        return 
    }
    

    匿名函数需要赋给变量,之后才能调用。这个匿名函数称为函数表达式

  3. 混合式

    var fn = function name( param1[ , param2[, ...] ] ){
        return 
    }
    

    混合式中,函数名 name 只在函数体内部有效,指代函数表达式本身,在函数体外部无效。

  4. Function 构造函数
    最后一个参数被始终看成函数体,而前面的参数则枚举了新函数的参数。

    var fn = new Function( 'param1'[ , 'param2'[,' ...'] ] , functionBoby ){
        return 
    }
    

    例子:

    let n = 1;
    let fn = new Function( 'x' , 'y' , 'return x + ' + n + ' + y' );
    fn( 1,2 )  //4,此时的 n 是1
    
  5. 箭头函数(ES6)

    (param1[ , param2[, ...] ])=> { return  }
    

形参和实参

说明

函数声明和函数表达式

声明前置

先声明变量,后声明 function,如果 function 名字和 var 声明名字一样
function 优先。如果赋值就按照赋值的类型。

声明前置

API

自身 API

自身属性 API fn.name

Function.prototype API

Function.prototype API

new

  1. 创建一个新对象 obj ,对象 __proto__ 指向构造函数的 prototype
    对象. __proto__ === 构造函数.prototype
    
  2. 调用 call()/apply()/bind()

Call Stack(调用栈)

栈是一种数据结构,特点是先进后出,js 代码执行时都遵循 Call Stack 的规则。如果压栈太多会导致错误(Stack Overflow[ 栈溢出 ])

作用域 和 作用域链

scope(范围、视野、眼界)

如果在函数作用域中没有写 var 直接写 a = 3
1. 优先赋值
2. 沿着作用域链寻找 a 的声明
3. 若作用域链中没有 a 的声明,则声明全局变量并赋值

变量提升

将作用域链中首先要做变量提升

例子1:
var a = 1;
function fn(){
    console.log( a );
    var a = 2;
}
fn.call();  //  a === undefined
例子2:
var a = 1;
function fn1(){
    console.log( a );  //  a === undefined
    var a = 2;
    fn2.call();
}
function fn2(){
    console.log( a );  //  a === 1
}
fn1.call();
例子3:
var obj = { name: ' obj ' }
function fn1(){
    function fn2(){
        console.log( this );   // this === window 
    }
    fn2.call();
}
fn1.call( obj );
例子4:
var liTags = document.querySelectAll( ' li ' );
for( let i = 0, len = liTags.length; i < len; i++ ){
    liTags[ i ].onclick = function(){
        console.log( i )  // 打印出的 i 都是最后的数字
    }
}

this 值

this 的调用主要有两种方式,一种是函数(function),另一种是作为对象的方法(methods)

判断 this 值就看函数怎么被调用,之后转化为 call 形式

例子1:作为对象的方法调用
function fn(){
    console.log(this)
}
let obj = {
    a: 'a',
    fn: fn
}
obj.fn()  // this === obj
例子2:作为对象属性的深层次嵌套
function fn(){
    console.log(this)
}
let obj = {
    a: 'a',
    wrapper: {
        b: 'b',
        fn: fn
    }
}
obj.wrapper.fn()  // this === { b: 'b', fn: fn } === obj.wrapper
例子3:
let obj = {
    a: 'a',
    fn () {
        console.log(fn)
    }
}
function fncb(cb){
    cb()
}
fncb(obj.fn)  // this === 全局变量

闭包

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

声明变量

立即调用函数

window下有很多全局属性


window

当定义变量的时候,有可能不小心覆盖了 window 的全局属性,为了避免覆盖 window 上的属性,可以:

  1. 不用全局属性
  2. 用局部变量
  3. 使用立即调用函数,从而使用局部变量
    () => { }.call()
    () => { }()
    
  4. 但是如果像上述写法,浏览器会报错,所以要这么写:
    (() => { }.call() )
    (() => { } ).call()
    -() => { }.call()
    +() => { }.call() 
    !() => { }.call()
    ~() => { }.call()
    { let  }  // let 作用域在块级作用域中
    

套路

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

  2. 递归:函数调用自身

  3. JavaScript将函数看做一种值,函数只是一个可以执行的值

  4. 函数表达式函数声明同时有的时候

    function
  5. 不能在条件语句中声明函数(变量提升,是的 if 无效)

  6. 函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。

  7. 参数传值传递,内存中体现原始值的拷贝
    参数传址传递(复杂类型的值),内存中体现原始值的地址,唯一一种不改变参数的是替换掉整个参数,这时不会影响到原始值(添加属性或方法会影响到原始值)

  8. 如果有同名的参数,则取最后出现的那个值

  9. eval命令的作用是,将字符串当作语句执行。eval没有自己的作用域,都在当前作用域内执行,因此可能会修改当前作用域的变量的值,造成安全问题。为了防止这种风险,JavaScript 规定,如果使用严格模式eval内部声明的变量,不会影响到外部作用域

上一篇下一篇

猜你喜欢

热点阅读