JavaScript面试时候的坑洼沟洄——表达式

上篇博客JavaScript面试时候的坑洼沟洄——数据类型总结了一下JavaScript数据类型几转换的相关知识,很多朋友可能和我一样,买了书后对数据类型啊、运算符啊、语句啊都是扫两眼或直接略过的,自己为搞定原型、闭包、作用域链就可以秒杀JavaScript笔试题,结果一次次死在毫不起眼的基础知识上,看似平淡无奇实则暗流涌动,一不小心就会栽倒。好了不扯淡了,回正题

神马是表达式

表达式是由数字、运算符、数字分组符号(如括号)、自由变量和约束变量等以能求得数值的有意义排列方法所得的组合。~~约束变量在表达式中已被指定数值,而自由变量则可以在表达式之外另行指定数值。一个表达式代表一个函数,其输入为自由变量的定值,而其输出则为表达式因之后所产生出的数值。 ——维基百科

看起来很不接地气的赶脚,表达式是JavaScript中的一个短语,JavaScript会将其计算出一个结果,常量如1、"hello"、null这些都是表达式;变量名也是表达式,JavaScript计算出的结果就是赋值给变量的值,这些都是简单的表达式,几个简单的表达式可以组合为复杂的表达式,[3,4,5,6]这也是一个表达式,计算结果是数组,我们也可以通过运算符将简单表达式组合位复杂表达式,8+9这样,JavaScript表达式有几种形式

  1. 原始表达式

    常量、变量、保留字

  2. 对象、数组初始化表达式

    var obj={a:1,b:2};
    var a=[1,2,3];

  3. 函数定义表达式

    var fn=function(){}

  4. 属性访问表达式

    Math.abs

  5. 调用表达式

    alert(‘hello‘);

  6. 对象创建表达式

    new object();

函数定义

我们想使用一个函数的时候通常有几种做法

  1. 函数表达式

    函数表达式中函数名称并不是必需的,所以我们经常这么使用
    var fn=function(n) { console.log(n) };

  2. 函数声明

    更常见的做法是这样 function fn(n){ console.log(n);}

  3. 使用Function构造函数

    偶尔也会这样 var fn=new Function(‘n‘,"console.log(n);");

这几种做法都很好理解,但是如果函数表达式使用了名字呢,我们看个题目

1
2
3
var f = function g(){ console.log(g);};
f();//function g(){ console.log(g);};
typeof g();//g is not defined

不知道结果和同学们的预期是否一致,但看起来这种结果似乎互相矛盾,当我们使用函数声明的方式定义一个函数的时候,实际上声明了一个变量,在上面例子中就是f,并把函数赋值给这个变量,普通的函数表达式没有创建该变量,也就是我们所说的创建了一个匿名函数,但是如果函数表达式包含名称,也就是上面例子的g,那么函数的局部作用域将包含将包含该名称,并且把创建的函数绑定到该名称上,在上面例子中g变成了函数的局部变量,变量指向函数本身,所以我们调用f的时候会把其本身打印出来。但是g只作为函数的局部变量存在,我们在外部调用g的时候就会报错了。

命名函数表达式在创建的时候,会在当前作用域最前段添加一个新的对象
{funcname:referfunction_expression},然后,将作用域链添加到
函数表达式的[[scope]]中,接着在删除该对象。

看个题目

1
2
3
4
5
var x=1;
if(function f(){}){
    x+=typeof f;
}
console.log(x);//‘1undefined‘

是不是觉得自然就能想到答案了

立即执行函数

初学JavaScript的同学很容易被类似这样的东西唬住

1
(function(){})();

其实我们了解了表达式就能很清楚的看明白这是什么结构了

(函数定义表达式)函数调用表达式

也就是说先创建了一个匿名函数,然后不传入参数调用它,这就变成了“立即执行函数”,知道了这些看个传入参数调用的立即执行函数题目

1
2
3
(function f(f){
    return typeof f();// "number"
})(function(){return 1;});

这个题目事实上还涉及了一些其它的知识,立即执行函数不再是以空括号()来调用了,同事传入了一个function作为参数传入调用。再一个疑惑就是typeof f() 中的f究竟指谁,这个知识我们后面会介绍道,简单说一下,当函数执行有命名冲突的时候,函数依次填入 变量=》函数=》参数,所以最后被填入的参数f会覆盖函数定义f,typeof f()是对参数的调用,参数是立即执行函数传入的function参数,返回数字1,typeof 1是 "number"。

表达式返回值

表达式看明白了,我们却经常忽略其计算结果,也就是我们常说的返回值,对于原始表达式、对象数组初始化表达式、属性访问表达式很简单不会有什么问题。

函数定义表达式返回的是函数对象本身,我们在调用alert或者console.log的时候会调用其toString方法

console.log(function(){alert(‘a‘);}) //function (){alert(‘a‘);}

函数调用表达式自然是返回函数的return结果,但在JavaScript中并不是所有的函数都有return语句,对于没有return语句的function,其调用表达式返回undefined,对于只写个return的坑爹做法同样也是返回undefined

1
2
(function(){})(); //undefined
(function(){return;})();//undefined
对象创建表达式本来也应该很简单,返回new的对象就可以了

1
typeof new Date(); //"object"

但是总有特殊的,看个题目

1
2
3
4
5
6
function Test(){
    return new Date();
}
var test=new Test();
console.log(test instanceof Test);//false
console.log(test);//Sat Jan 18 2014 14:57:08 GMT+0800 (CST)

很奇怪啊,new Test()没有返回Test的实例对象,返回的却是Date对象,这是为什么呢?是不是有返回值的function使用构造函数的时候就会返回return指令的结果呢?看个例子

1
2
3
4
5
6
7
8
function Test(){
    return new Date();
}
function Test2(){
    return 2;
}
typeof new Test();
new Test2() instanceof Test2;//true,竟然是true
刚才的推测明显不正确,Test2有返回值,new test2() 返回的是Test2的实例,但是我们已经可以看出一丝端倪了

当使用function的构造函数创建对象(new XXX)的时候,如果函数return基本类型或者没有return(其实就是return undefined)的时候, new 返回的是对象的实例;如果 函数return的是一个对象,那么new 将返回这个对象而不是函数实例。

这里千万别把构造函数(使用new)和普通函数调用混淆了,普通函数调用还是该返回什么返回什么的。看个题目

1
2
‘foo‘ == new function(){ return String(‘foo‘); }; //false
‘foo‘ == new function(){ return new String(‘foo‘); };//true

怎么样,答对没有?

最后

关于表达式还有一个重点没有说——正则表达式,相关内容已经总结位单独博客,有兴趣同学可以看看

JavaScript 正则表达式上——基本语法

JavaScript正则表达式下——相关方法

本来这篇是想把表达式和运算符都说了呢,没想到表达式就啰嗦了这么多,运算符就下篇再说只能。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。