储备知识:函数定义方式
- 函数声明 function fun(){}
- 函数表达式 var fun = function(){}
- 构造函数 new Function()
一、函数是对象,函数名是指针
var sum = function (num1, num2) {
return num1 + num2
}
console.log(sum(10, 10)) // 20
var anotherSum = sum
console.log(anotherSum(10, 10)) // 20
sum = null
console.log(anotherSum(10, 10)) // 20
先定义名为sum()的函数,有声明了一个变量anotherSum,和sum指向同一个函数,即便sum被赋值为null,也不会改变anotherSum的指向
二、函数没有重载
function sum (num1, num2) {
return num1 + num2
}
function sum (num1, num2) {
return num1 + num2 * 2
}
console.log(sum(10, 10)) // 30
等同于
var sum = function (num1, num2) {
return num1 + num2
}
sum = function (num1, num2) {
return num1 + num2 * 2
}
console.log(sum(10, 10)) // 30
声明了两个同名函数,后面的函数会覆盖前面的
三、函数的执行顺序
console.log(sum(10, 10)) // 20
function sum (num1, num2) {
return num1 + num2
}
console.log(sum(10, 10)) // 报错,先执行 var sum;sum不会保存对函数的引用
var sum = function (num1, num2) {
return num1 + num2
}
函数声明先于函数表达式执行
四、构造函数返回值
function Fun(){}
new Fun()
function Fun(){
return 1
}
new Fun()
function Fun(){
return {a: 1}
}
new Fun()
构造函数返回值总结
- 没有返回值则按照其他语言一样返回实例化对象。
- 返回值为基本类型,返回实例化对象
- 返回值是引用类型,则实际返回值为这个引用类型
补充内容(运算符优先级)
练习
function Foo() {
getName = function () { console.log(1) }
return this
}
Foo.getName = function () { console.log(2)}
Foo.prototype.getName = function () { console.log(3)}
var getName = function () { console.log(4)}
function getName() { console.log(5)}
//请写出以下输出结果:
Foo.getName()
getName()
Foo().getName()
getName()
new Foo.getName()
new Foo().getName()
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
实际执行顺序
**
function Foo() {
getName = function () { console.log(1) }
return this
}
var getName // 变量声明提升
function getName() { console.log(5)} // 函数声明提升,覆盖var的声明
Foo.getName = function () { console.log(2)}
Foo.prototype.getName = function () { console.log(3)}
getName = function () { console.log(4)}
最终结果:2,4,1,1,2,3,3