凡科前端笔试之打印题

基础程度:4颗星


知识点

主要涉及是预编译变量提升、AO
GO、new的使用,还有静态方法,实例方法之类的基础知识

牛客网2021凡科春招前端(大佬面经)

一、题目

function fn() {
	getName = function () {
		console.log('yifang')
	}
	return this;
}

fn.getName = function () {
	console.log('liudehua')
}

fn.prototype.getName = function () {
	console.log('zhangxueyou')
}

var getName = function () {
	console.log('zhouxingci')
}

function getName() {
	console.log('huangzesi')
} 

// 依次以下代码分别输出什么 
fn().getName(); 
fn.getName(); 
getName(); 
new fn.getName(); 
new new fn.getName();  

二、分析

简单入手

先不着急看答案,也别看全部的题,先看第一个,一般一开始的不会太难,稳住心态

  • fn().getName();输出“yifang”,为什么呢?因为其实语句的意思是执行fn,然后执行fn中的getName方法;它返回的是this,详细看下面拓展1,拓展2,拓展3会更好理解,主要考的是变量提升,预编译的问题;
  • fn.getName(); 输出“liudehua”,因为这是fn对象的一个静态方法(函数也是对象),这也没理由是其他答案,不理解的可以看看实例方式与静态方法的区别;
  • getName(); 蛮复杂的下一小节分析;
  • new fn.getName(); 输出“liudehua”,为什么不是“zhangxueyou”呢?new它并没有实例化fn,所以其实这条语句相当于刚刚的fn.getName();(斜体字指的是猜想的),有时间可以看看new一个过程到底发生了什么?
  • new new fn.getName(); 输出“liudehua”,这题其实会报错,可能那个提供面经的大佬记错题了

复杂语句简单分析

为啥说getName()蛮复杂的呢?因为这个涉及到了变量提升,作用域AO,GO这些,所以我就简单说说这个题中为什么会输出“yifang”

function fn() {
	getName = function () { ... }
	return this;
}

// fn.getName = function () { ... }
// fn.prototype.getName = function () {}

var getName = function () { ... }

function getName() { ... } 

// 依次以下代码分别输出什么 
fn().getName(); 
fn.getName(); 
getName(); 
...
  • 无关的代码都删掉或注释了,答案输出“yifang”是有前提的

  • 先看看GO对象(不懂GO,预编译的可以看看总结),步骤如下

    1. 寻找变量声明:有了,是getName
    2. 寻找函数声明,并赋值:没有,因为刚刚已经有个叫getName了,所以直接赋值,值为函数体
  • 来执行代码咯!

  • fn().getName(); fn函数执行,getName又被赋值,输出”yifang“;

  • fn.getName(); 这行无影响,执行fn的一个静态方法

  • getName(); 执行,输出“yifang”,就是那么简单!

  • 想要更加理解看拓展1,拓展2,拓展3


三、拓展

这里我们把输出改成:

... // 前面都不变

getName();  // "zhouxingci"
fn().getName();  // "yifang"
getName();  // "yifang"

分析:为什么同样执行getName,两次打印的答案是不一样呢,其实刚刚上面的分析就写了,由于第二句的fn()给getName赋值了,如果没有执行那个fn().getName(),结果是:

  • GO对象跟刚刚一样,此时getName的值是function getName() { console.log(‘huangzesi’) };
  • 好了,开始执行代码,遇到getName的赋值语句, getName被赋值为function () { console.log(‘zhouxingci’) },所以第一次打印的结果是"zhouxingci"

其实前面分析并没去分析到fn函数的AO,现在我们又来改改题目:

function fn() {
/*- getName = function () { console.log('yifang') } */
	var getName = function () { console.log('yifang') }
	return this;
}

// 无关省略

var getName = function () { console.log('zhouxingci') }

function getName() { console.log('huangzesi') } 

// 依次以下代码分别输出什么 
getName();
fn().getName(); 
getName(); 

我们在fn函数里面重新声明一个变量叫getName,那么我们打印会发生什么改呢?

  • 首先先来看看第一次执行getName,他还是输出"zhouxingci",因为没有执行fn(),所以改了代码其实影响不大的,可以重新看看前面的分析
  • 第二行:fn().getName();输出“zhouxingci”,为啥子输出的不是"yifang"呢?因为fn函数返回的是this,这里的执行环境this指的是window对象,其实就是GO。而这个同名的变量getName是属于fn的AO里面的,所以不会打印出“yifang”。此时等价于,fn().getName() === this.getName() —> getName() (仅仅是建立此环境上的结论)
  • 又来看看第二次执行的getName,整个变量提升(预编译)过程分析一下:
  1. GO对象跟刚刚一样,此时getName的值是function getName() { console.log(‘huangzesi’) };
  2. 分析函数fn的AO,先找形参和变量声明,发现没有形参,只声明了一个变量getName,值为undefined
  3. 来执行getName(),getName被赋值为function () { console.log(‘zhouxingci’) },所以输出“zhouxingci”
  4. 来执行fn().getName(),fn的AO中的getName被赋值function () { console.log(‘yifang’) },然后输出了”zhouxingci“,原因前面那点说了,此时this == window, window == GO,GO中getName值此时为function () { console.log(‘zhouxingci’) }。

所以其实还是输出"zhouxingci";

与拓展4一样,我们来看看在fn函数内部,手动给this.getName赋值:

function fn() {
	var getName = function () { console.log('yifang') }
	this.getName = getName;
	return this;
}
// 无关省略
var getName = function () { console.log('zhouxingci') }

function getName() { 	console.log('huangzesi') } 

// 依次以下代码分别输出什么(给出了答案) 
getName(); // "zhouxingci"
fn().getName(); // "yifang"
getName(); // "yifang"

手动给this即window(当前环境this为window)中的getName赋值为函数里面同名getName的值后,fn().getName()输出变成了”yifang“,getName()也是”yifang“,该结果与拓展1一样;


答案

fn().getName(); // yifang
fn.getName(); // liudehua
getName(); // yifang
new fn.getName(); // liudehua
new new fn.getName();  // liudehua

总结

预编译其实就是程序执行前的一个步骤;比如var a = 1;这里有两个步骤,一个是声明变量,一个是赋值。那么GO又是什么呢?GO是global Oject(全局对象),AO是活动对象,也称函数上下文,函数们在执行前一刻会生成自己的AO

  • GO对象
  1. 寻找变量声明,并赋值为undefined
  2. 寻找函数声明,并赋值为该函数体
  3. 等待执行代码

引申一下,还有个暗示全局变量,即变量未经声明赋值,此变量就为全局对象所有;

  • AO对象
  1. 寻找形参和变量声明
  2. 实参形参对应,就是给形参赋值,然后变量值为undefined
  3. 寻找函数声明,赋值为该函数的函数体
  4. 等待执行代码

引申一下,这里可以更深入理解一下函数声明跟函数表达式的区别;
引申两下,实参,形参,跟arguments的关系,还有es6扩展运算符的作用;

前面所说的GO,AO其实都跟作用域相关,《你不知道的JavaScript》里面讲到作用域(scope)是根据名称查找变量的一套规则,当函数发生嵌套的时候,其实也就是发生了作用域的嵌套,产生了作用域链(scope chain)。
怎么个根据名称查找变量呢?这个变量其实就是GO,AO对象里面的属性

  • 它是如何查找的呢?
  1. 先找自己的AO对象,里面有没有这个变量
  2. 没有的话,找上面那个函数的AO对象,
  3. 一直沿着这个作用域链找
  4. 直到找到GO,这个最顶层的作用域下的GO对象
  5. 再没有的话,那就是要报错了

引申一下,es6块级作用域

  • 作用域链如何串在一起?

先写到这里吧,笔试题还涉及了静态方法和实例方法的区别,这里就不讲了

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值