本篇文章从一道题开始,后面会逐步的在这道题的基础上进行拓展,先思考一下执行完下面的代码后输出的是什么
getAge()
var getAge = function(){
console.log(18);
}
function getAge(){
console.log(20);
}
getAge()
这道题考到了两个知识点,一点是在JavaScript中的变量提升,另一点是函数的定义有函数声明与函数表达式两种类型。
什么是变量提升
函数及变量的声明都将被提升到作用域的最顶部。
变量可以在使用后声明,也就是变量可以先使用再声明。
函数声明
fun()
function fun(){
console.log('函数声明');
}
fun()
上面这是一个函数声明的方式,按照变量提升机制,这里的fun()会被提升到最顶部,因此这里的结果就显而易见了。
/*答案*/
fun() //函数声明
function fun(){
console.log('函数声明');
}
fun() //函数声明
通过这个例子我们可以得到,函数声明是在JS解析时进行函数提升,因此在同一个作用域内,无论函数在哪里定义声明的,该函数都可以调用。
函数表达式
fun()
var fun = function(){
console.log('函数表达式');
}
表达式声明是将函数赋值给一个变量,这个变量当然也会被提升到最前面,但是这里存在一个坑。变量提升只是把变量提升到了最前面,但是赋值是在JS运行时进行的。我们来看一下输出结果,显示fun不是一个函数。
fun() //Uncaught TypeError: fun is not a function
var fun = function(){
console.log('函数表达式');
}
把fun()放到后面看一下呢
var fun = function(){
console.log('表达式声明');
}
fun() //表达式声明
已经可以正常执行fun 函数,因为此时已经被赋值了。
了解完上面的内容后,回头看看开始的问题,答案如下
getAge() // 20
var getAge = function(){
console.log(18);
}
function getAge(){
console.log(20);
}
getAge() //18
在JavaScript中函数也可以是对象,它拥有自己的构造函数,也有原型链。同样以一道思考题开始。
function Person(){
getAge = function(){
console.log(17);
}
this.getAge = function(){
console.log(18);
}
}
Person.getAge = function(){
console.log(19);
}
Person.prototype.getAge = function(){
console.log(20);
}
Person.getAge()
new Person().getAge()
这道题主要考察对象的私有属性/方法、公有属性/方法、静态属性/方法。
公有属性/方法必须实例化才可以使用,也就是new操作。
公有方法不可以调用私有方法可静态方法。
私有属性和私有方法不可以在外部被访问到。
静态属性和静态方法无需实例化就可以访问。
Person.getAge() 这里没有对Person进行实例化,因此是调用的Person的静态方法。
new Person().getAge() 这一步先对Person进行了实例化,然后调用了实例的getAge()方法。其实本质是这样的顺序(new Person())getAge()。但是现在有个问题,Person 在构造函数与原型链中都有getAge()方法。这种情况下会默认使用构造函数中的公有方法,而不是原型中的。
function Person(){
function getAge(){ //私有方法
console.log(17);
}
this.getAge = function(){ //公有方法
console.log(18);
}
}
Person.getAge = function(){ //静态方法
console.log(19);
}
Person.prototype.getAge = function(){ //公有方法
console.log(20);
}
Person.getAge() //19
new Person().getAge() //18
这道题相对之前的就要难一些了,这里主要是对this和作用域的考察。
function Person(){
this.getAge = function(){
console.log(18);
}
getAge = function(){
console.log(19);
}
return this
}
var getAge = function(){
console.log(20);
}
getAge()
Person().getAge()
new Person().getAge()
getAge()
第一问:getAge() ,这个比较简单,没有什么可以讲解的,就是单纯的执行getAge函数,答案是:20
第二问:Person().getAge()先执行了Person()函数,然后调用Person函数返回的对象的getAge属性函数。其中的getAge = function(){console.log(19);}是一赋值语句,在当前作用域中没有声明getAge,因此它会向上层作用域寻找变量getAge。在外面var getAge = function(){console.log(20);}变量被提升,因此这里相当于修改了全局变量getAge 。函数返回的this在这里是指的window,因为这里是执行Person()函数,而并没有实例化。所以Person().getAge()相当于调用window下的getAge 方法,最终输出结果为:19。
第三问:与第二问不同的是,这里对Person进行了实例化,所以这里返回的this指返回的实例,然后调用该实例的公有方法,也就是this.getAge,答案就是:18。
第四问:这问看起来与第一问差不多,通过第二问的分析,此时的全局变量getAge在第二问时已经被修改了,所以这里的输出为19。
在传统语言中,构造函数是没有返回值的,实际的返回值就是此构造函数的是实例化的对象。
在JavaScript中的构造函数可以有返回值,也可以没有,这里可以分为3种情况。
没有返回值时,返回的是实例化对象。
function Person(name){
this.name = name
}
console.log(new Person('bbb').name); //bbb
有返回值,但是返回的内容为值类型而非引用类型,如String,Number,Boolean,Null,Undefined),这种情况与没有返回值的情况相同,返回的仍然是返回的是实例化对象。
function Person(name){
this.name = name
name = 'aaa'
return name
}
console.log(new Person('bbb').name); //bbb
有返回值,返回的值为引用类型。那么实际返回的就是该返回值。
function Person(name){
this.name = name
name = 'aaa'
return name
}
console.log(new Person('bbb').name); //bbb