储备知识:函数定义方式

  • 函数声明 function fun(){}
  • 函数表达式 var fun = function(){}
  • 构造函数 new Function()

一、函数是对象,函数名是指针

  1. var sum = function (num1, num2) {
  2. return num1 + num2
  3. }
  4. console.log(sum(10, 10)) // 20
  5. var anotherSum = sum
  6. console.log(anotherSum(10, 10)) // 20
  7. sum = null
  8. console.log(anotherSum(10, 10)) // 20

先定义名为sum()的函数,有声明了一个变量anotherSum,和sum指向同一个函数,即便sum被赋值为null,也不会改变anotherSum的指向

二、函数没有重载

  1. function sum (num1, num2) {
  2. return num1 + num2
  3. }
  4. function sum (num1, num2) {
  5. return num1 + num2 * 2
  6. }
  7. console.log(sum(10, 10)) // 30

等同于

  1. var sum = function (num1, num2) {
  2. return num1 + num2
  3. }
  4. sum = function (num1, num2) {
  5. return num1 + num2 * 2
  6. }
  7. console.log(sum(10, 10)) // 30

声明了两个同名函数,后面的函数会覆盖前面的

三、函数的执行顺序

  1. console.log(sum(10, 10)) // 20
  2. function sum (num1, num2) {
  3. return num1 + num2
  4. }
  1. console.log(sum(10, 10)) // 报错,先执行 var sum;sum不会保存对函数的引用
  2. var sum = function (num1, num2) {
  3. return num1 + num2
  4. }

函数声明先于函数表达式执行

四、构造函数返回值

  1. function Fun(){}
  2. new Fun()
  3. function Fun(){
  4. return 1
  5. }
  6. new Fun()
  7. function Fun(){
  8. return {a: 1}
  9. }
  10. new Fun()

构造函数返回值总结

  • 没有返回值则按照其他语言一样返回实例化对象。
  • 返回值为基本类型,返回实例化对象
  • 返回值是引用类型,则实际返回值为这个引用类型

补充内容(运算符优先级

练习

  1. function Foo() {
  2. getName = function () { console.log(1) }
  3. return this
  4. }
  5. Foo.getName = function () { console.log(2)}
  6. Foo.prototype.getName = function () { console.log(3)}
  7. var getName = function () { console.log(4)}
  8. function getName() { console.log(5)}
  9. //请写出以下输出结果:
  10. Foo.getName()
  11. getName()
  12. Foo().getName()
  13. getName()
  14. new Foo.getName()
  15. new Foo().getName()
  16. new new Foo().getName()


审题:**首先定义了一个叫Foo的函数;为Foo创建了一个叫getName的静态属性存储了一个匿名函数;为Foo的原型对象新创建了一个叫getName的匿名函数;通过函数变量表达式创建了一个getName的函数;声明一个叫getName的函数。

解析:

  • Foo.getName 访问Foo函数上存储的静态属性,其存储了一个匿名函数,执行 Foo.getName() 调用该匿名函数,打印出 2
  • 直接调用 getName 函数。即访问当前上文作用域内的叫getName的函数,即为第 7、8 行的代码。此处有坑,考察两个知识点:一是对函数的执行顺序的理解,二是函数没有重载。因为声明变量或声明函数都会被提升到当前函数的顶部,所以第 8 行代码优先于第 7 行执行,又因为函数没有重载,则打印结果为 4
  • Foo().getName(); 先执行了Foo函数,然后调用Foo函数的返回值对象的getName属性函数。Foo函数的第一句 getName = function () { console.log(1) } 是一句函数赋值语句(注意不是函数表达式,因为它没有var变量声明),会按照由内而外的顺序寻找getName变量,因为Foo函数作用域内没有getName变量,外层作用域内有getName变量,找到此变量并重新赋值为 function () { console.log(1) },返回值为this,指向window对象。则打印结果为 1
  • 直接调用getName函数,相当于 window.getName(),指向Foo函数时修改了该变量,则打印结果为 1
  • new Foo.getName(),考察运算符优先级。相当于new (Foo.getName)(),即将Foo.getName函数作为了构造函数来执行,打印值为 2
  • new Foo().getName(),相当于(new Foo()).getName(),先执行Foo函数,Foo作为构造函数有返回值,根据构造函数返回值可知,返回值为this(当前实例化对象),即返回实例化对象Foo。Foo构造函数中没有为实例化对象添加任何属性,遂到当前对象的原型对象(prototype)中寻找getName,则最终打印值为 3
  • new new Foo().getName(),相当于执行 new ((new Foo()).getName)()。先初始化Foo的实例化对象,然后将其原型上的getName函数作为构造函数再次new,结果为 3

实际执行顺序
**

  1. function Foo() {
  2. getName = function () { console.log(1) }
  3. return this
  4. }
  5. var getName // 变量声明提升
  6. function getName() { console.log(5)} // 函数声明提升,覆盖var的声明
  7. Foo.getName = function () { console.log(2)}
  8. Foo.prototype.getName = function () { console.log(3)}
  9. getName = function () { console.log(4)}

最终结果:2,4,1,1,2,3,3