A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

前言
之前面试前端的时候遇到一道JS题虽然不难,但是很刁钻,所有也未能全部理解,今天刚好有时间深入学习了一下,顺便把在原文的基础上学习的心得分享一下,大神请Ctrl + F4

题目
function Foo() {
    getName = function () { alert (1); };
    return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}

//请写出以下输出结果:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

答案
function Foo() {
    getName = function () { alert (1); };
    return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}

//答案:
Foo.getName();//2
getName();//4
Foo().getName();//1
getName();//1
new Foo.getName();//2
new Foo().getName();//3
new new Foo().getName();//3

解题
此题涉及到的JS知识点有变量定义提升、this指针指向、运算符优先级、原型、继承、
全局变量污染、对象属性及原型属性优先级

首先解释一下题目
function Foo() {
    getName = function () { alert (1); };
    return this;
}  // 定义一个Foo的具名函数
Foo.getName = function () { alert (2);};
// 为具名函数Foo创建了一个叫getName的静态属性(该属性储存了一个匿名函数)

Foo.prototype.getName = function () { alert (3);};
// 在具名函数Foo的原型链上添加了一个叫getName属性(该属性储存了一个匿名函数)

var getName = function () { alert (4);};
// 通过函数变量表达式创建了一个getName的函数(挂载在window下)

function getName() { alert (5);}
// 声明一个叫getName函数

相关知识点
知识点一之变量定义提升和函数定义提升
重点:在预处理时,函数提升先于变量提升
同名变量提升:对于同名的变量声明,Javascript采用的是忽略原则,后声明的会被忽略。

  console.log(a)  //undefined
  var a = 1
  console.log(a)  //1
  var a = function () {
    console.log('a')
  }
  console.log(a) //f () {console.log('a')}

  console.log(b) //undefined
  var b = function () {
    console.log('b')
  }
  console.log(b) //f () {console.log('a'
  var b = 1
  console.log(b) //1

同名函数提升:同名的函数声明,Javascript采用的是覆盖原则,先声明的会被覆盖,因为函数在声明时会指定函数的内容,所以同一作用域下一系列同名函数声明的最终结果是调用时函数的内容和最后一次函数声明相同。
a()//3
function a() {
console.log(1)
}
a()//3
function a() {
console.log(2)
}
a()//3
同名变量和函数提升:对于同名的函数声明和变量声明,采用的是忽略原则,由于在提升时函数声明会提升到变量声明之前,变量声明一定会被忽略,所以结果是函数声明有效。

  //情况一
  console.log(a)  // f a() {console.log('a')}
  var a = 1
  console.log(a)  // 1
  function a() {
    console.log('a')
  }
  console.log(a) // 1
  //情况二
  console.log(b) // f b() {console.log('b')}
  var b = 1
  console.log(b)// 1
  function b() {
    console.log('b')
  }
  console.log(b)// 1

知识点二之(). new之间的优先级关系
()>.>new
规则一 括号最大
规则二 当a前有new,后有.时,先.后new
规则三 当a()前有new,后有.时,先new后.
规则四 new带参数的优先级高于函数调用即new F()而非new (F())

new a.b()  //(new a.b)()
new a().b() //(new a()).b()
new new a().b() //new ((new a()).b)()
new new a().b.c()//new (((new a()).b).c)()

知识点三之构造函数的返回值
在传统语言中,构造函数不应该有返回值,实际执行的返回值就是此构造函数的实例化对象。

而在js中构造函数可以有返回值也可以没有。

规则一:没有返回值则按照其他语言一样返回实例化对象。
规则二:若有返回值则检查其返回值是否为引用类型。如果是非引用类型,如基本类型(string,number,boolean,null,undefined)则与无返回值相同,实际返回其实例化对象。
规则三:若返回值是引用类型,则实际返回值为这个引用类型。

// 情况一
function F(){}
console.log(new F())// F{}
// 情况二
function F(){return true;}
console.log(new F())// F{}
// 情况三
function F(){return {a:1};}
console.log(new F())// Object{a:1}

第一题
Foo.getName();//2
指的是访问Foo函数上存储的静态属性,静态属性只有直接使用函数名加.静态属性名可以调用到

第二题
getName();//4
直接调用名在getName的函数,首先找当前上文作用域内的叫getName的函数,虽然题目有

var getName = function () { alert (4);};
function getName() { alert (5);}

但根据知识点一,定义是函数声明先有效,但是之后执行了getName = function () { alert (4);};,相当于变量重新赋值,所有是4

第三题
Foo().getName();//1

先执行Foo函数
由于是把Foo当成一个函数,而非一个对象,所有这时this指的是当前运行的环境,即window对象
函数中第一句getName = function () { alert (1); };由于是赋值一个匿名函数给getName,但此时的getName在当前作用域下并没有定义,所以解释器会逐层往上层作用域找getName变量,在window对象中找到了getName ,值为 function () { alert (4);};所以把他覆盖了,变成 function () { alert (1); };(如果在window对象还没找到的话就会创建一个名为getName 的变量)
function Foo() {
        getName = function () { alert (1); };
        return this;
}

然后调用Foo函数的返回值对象的getName属性函数。即window.getName() 即 function () { alert (1); };
第四题
getName();//1
直接调用名在getName的函数,即window.getName,由于window下的geiName已被修改,所以跟第三问一样,执行function () { alert (1); };

第五题
new Foo.getName();//2
由知识点二可得此题执行顺序为new (Foo.getName)()
Foo.getName为function () { alert (2);};后将其作为构造函数执行,所以为2

第六题
new Foo().getName();//3
由知识点二可得此题执行顺序为(new Foo()).getName()

new Foo()此句为创建Foo对象,返回的this在构造函数中本来就代表当前实例化对象,由知识点三得,最终Foo函数返回实例化对象。
之后调用实例化对象的getName函数,因为在Foo构造函数中没有为实例化对象添加任何属性,之后到当前对象的原型对象(prototype)中寻找getName,故执行的为Foo.prototype.getName = function () { alert (3);};
注意: 构造函数内getName = function () { alert (1); };为赋值一个匿名函数给getName(看第三题),若想执行 alert (1); 构造函数内应为实例化对象添加属性,即this.getName = function () { alert (1); };

第七题
new new Foo().getName();//3
由知识点二可得此题执行顺序为new((new Foo()).getName)()
先初始化Foo的实例化对象,然后将其原型上的getName函数作为构造函数再次new。即Foo.prototype.getName = function () { alert (3);};作为构造函数,故为3


0 个回复

您需要登录后才可以回帖 登录 | 加入黑马