ECMAScript - Function Ⅰ

- 在类里面,static声明的方法只提供给构造函数使用,不能传递给任何子例。如果在实例里使用会出现`TypeError`

# 思考: 当实参没有传值给对应的形参,内部给形参赋值,是没有用的。因为形参和实参没
# 有一 一对应的话,那么arguments是无法跟实参产生联系的,也就是说undefined的时候
# 形参和arguments是不产生联系的
function test(a, b) {
    b = 3;
    console.log(arguments[1]); // undefined,这里的argument[0]对应a,argument[1]对应b
}
test(1);

# 注意,当执行函数的时候,解析引擎自动添加上call
function test () {}
test.call();
summary: apply或者call是更改this的指向

# callee 和caller,arguments是一个指针,指向函数test,所以是引用类型
function test (a, b, c) {
    console.log(arguments.callee.length); // 形参的长度
    console.log(test.length);  // 形参的长度
    console.log(arguments.length); //实参的长度
}
callee在递归里面,若是匿名函数,可通过callee找到对应的递归函数
new的原理
关于 `new`,有一件很酷的事情——当你使用`new`关键字调用一个函数时,以下两行代码将隐式地(在底层)为你完成,所创建的对象被称为`this`- `const this = Object.create(Object.prototype);`
  - `return this;`

function newTest (name, age) {
      let me = {}; // 相当于this
      me.name = name;
      me.age = age;
      return me;  // new的时候隐式添加return this;
}
let test1 = newTest('jack', 44);
let test2 = newTest('jack1', 45);

# target属性
ES6引入了new.target属性,用于确认当前作用在哪个构造函数上。若不通过new命令使用构造函数则返回undefined,否者返回对应的构造函数。
class Parent {
    constructor () {
        if(new.target.name === 'Parent') {   // 划重点,这里可以用来禁止当前类被实例化
            throw new Error('本类禁止实例化')
        }
    }
}

在函数内声明变量需要注意,这里面的a是局部变量,b是全局变量
function test () {
    var a = b = 10; 
}
console.log(b); // 10
使用表达式去定义函数的时候,函数名对外部是不可访问的,内部可访问
let test1 = function test2() {
    console.log(test2.name); // test2
}
console.log(test2.name); // ReferenceError
IIFE
# IIFE Imediately invoked function expression
优点有:自动执行、执行完毕后即释放GO里面的 function
写法一:(fn)()
写法二:(fn())
其中fn是function () {}

# 要注意以下两种写法区别
function fn () {                       function fn() {
    `报错`                                    `不报错`
}()                                    }(6)
左边把第二个括号当作函数的执行,
右边把第二个括号当作数字表达式

# ;(Fn())()
之所以要在前面加上分号,一方面是压缩代码的时候会报错,另一方面因为如果并列两个 IIFE 会造成括号混乱而报错。
表达式与语句的区别
表达式的定义:能产生值的操作的代码片段成为表达式,
语句:语句不包含值,但它改变程序的运行状态。
函数声明变成表达式时的方法!+-||&&
其中0 || fn、1 && fn
函数调用都发生了什么
宏观:函数返回时必须跳回到调用它的地方,因此计算机必须记住调用函数的发生处上下文。计算机存储上下文的地方叫做调用栈。每次调用函数时,当前上下文都会存储在此栈的顶部。当函数返回时,它会从栈中删除顶部上下文,并使用原来的上下文继续执行。在典型的`JavaScript`实现中,函数递归大约比单纯循环慢三倍。也就是说,通过简单循环来运行,通常比多次调用函数开销低。
微观:函数会产生一个独立的AO对象,存放函数执行时需要的属性和方法,其中一定会存在全局GO,排序关系是AO一定会比GO序号靠前,这就是为什么全局不能直接访问局部的一因素。当函数被调用完毕后,会销毁自身的AO对象,然后作用域链初始至函数定义。
构造函数调用和普通函数调用以及方法调用在实参处理、调用上下文和返回值方面都有不同。
`let obj = new Function("x","y","x+y;")`
a. 如果构造函数调用在圆括号内包含一组实参列表,先计算这些实参表达式,然后传入函数内,这和函数调用和方法调用是一致的。但如果构造函数没有形参,`Javascript`构造函数调用的语法是允许省略实参列表和圆括号的。

b. 调用构造函数创建一个新的空对象,这个对象继承自构造函数的prototype属性。构造函数试图初始化这个新创建的对象,并将这个对象用作其调用上下文,构造函数可以使用this关键字来引用这个新创建的对象。注意,尽管构造函数看起来像一个方法调用,它依然会使用这个新对象作为调用上下文。也就是说,在表达式new o.m()中,调用上下文并不是o.

c. 构造函数通常不使用return关键字,他们通常初始化新对象,当构造函数的函数体执行完毕时,它会显式返回。在这种情况下,构造函数调用表示是计算结果就是这个新对象的值。然而如果构造函数显式地使用return语句返回一个对象,那么调用表达式的值就是这个对象。如果构造函数使用return语句但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对象作为调用结果。这里主要说明当作为构造函数时的返回值存在两种情况,一种是返回引用类型的值会覆盖新创建的实例,一种是返回的基本类型的值会被新创建的对象覆盖。一个是被覆盖,一个是覆盖。

d.最后一点,也是关于`Function()`构造函数非常重要的一点,就是它所创建的函数并不是词法作用域,相反,函数体代码的编译总是会在顶层函数执行。

let scope = 'globals';
function constructFunction () {
    let scope = 'local';
    
    // 无法捕获局部作用域
    return new Function('return scope'); 
} 

// 这一行代买返回global,因为同故宫Function()构造函数
// 所返回的函数使用的不是局部作用域
constructFunction()(); // =>"global"

既然说到了构造函数,顺便提一下函数的调用方式:
    * 函数调用
    * 作为对象属性的方法调用
    * 作为构造函数调用
    * 使用call、apply见解调用
闭包
# 当内部函数被返回到外部并保存时,一定会产生闭包,闭包会产生原来的作用域不释放。
# 过度的闭包可能会导致内存泄漏
为了输出 0 - 9 ,使用IIFE来保存 i 的值

函数被定义的时候的作用域是上级的AOGO,除了return函数出去外,还可以通过window.xxx = fn
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值