一些需要注意的js输出问题整理

1、js运行之前,会把带有var和function关键字的事先声明,但不会赋值

1
2
3
4
5
6
7
8
9
alert(a) //function a(){alert(10)}
a();//执行alert(10)
var a=3;
function a(){
alert(10)
}
alert(a) //3
a=6;
a(); //a已经不是一个函数了报错
1
2
3
4
5
6
7
8
9
alert(a) //undefined
a(); // 报错 a 不是一个函数,函数表达式函数必须在表达式之后调用
var a=3;
var a=function(){
alert(10)
}
alert(a)
a=6;
a();

考点:第一变量和函数声明提前,第二函数声明优先于变量声明!

2、下面代码输出什么?

1
2
3
4
5
6
7
8
9
10
var a=0;
var c=0;
function aa(a){
alert(a)
var a=3
c = 10;
}
aa(5) // 5
alert(a) //这里的a未受到函数体内a变量覆盖
alert(c) // 10
1
2
3
4
5
6
7
8
var a=0;
function aa(a){
alert(a)
a=3
}
aa(5)
alert(a)
//5,0 在函数体内,执行alert(a)和a=3,修改的的并不是全局变量a,而是参数a

考点: 函数内部如果用var声明了相同名称的外部变量,函数将不再向上寻找;这里在函数体内,参数a的优先级高于变量a

3、严格模式下,以下程序的输出是什么:

1
2
3
4
5
(function(){
var a = b = 3;
})();
console.log("a defined? " + (typeof a !== 'undefined'));
console.log("b defined? " + (typeof b !== 'undefined'));

答案: a defined? false,b defined? true

理解这道题的核心在于如何理解var a = b = 3这句话,实际上这句话等于

1
2
3
var a; 
b = 3;
a = 3;

这样子,实际上,b是声明在了全局变量中(编译器在预编译帮你声明了,然而在严格模式下是不行的) a是局部变量,所以在函数之外是没有定义的。

4、数组的filter,以下输出结果是什么:

1
2
3
4
5
6
7
8
var arr = [1,2,3];
arr[10] = 9;
arr.filter((item)=> {
return item === undefined
})

//答案
[]

解析: 是的,答案的确是[],不是[undefined x 7]。当数组中都是undefined时,数组就是空,或者说[empty x 7] === []。

5、写一个sum方法,可以实现以下两种调用方式

1
2
console.log(sum(2,3)) //5
console.log(sum(2)(3)) //5

答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//方法1
var sum = function(x,y) {
if(y === undefined) {
return function(y) {
return x + y;
}
}else {
return x + y;
}
}

//方法2
var sum = function(x){
if( arguments.length === 1) {
return function (y) {
return x + y;
}
} else {
console.log('here');
return arguments[0] + arguments[1];
}
}

6、+ - 运算符之惑:

1
2
3
4
5
6
console.log(1 +  "2" + "2"); //"122"
console.log(1 + +"2" + "2"); //"32"
console.log(1 + -"1" + "2"); // "02"
console.log(+"1" + "1" + "2");//"112"
console.log( "A" - "B" + "2"); "NAN2"
console.log( "A" - "B" + 2); "NAN"

核心是以下几点:

  • “- +”会隐式转换为Number类型
  • 当”+”作为运算符出现在String类型前时,会认为需要字符串拼接,因此会隐式转换为String
  • Number包含一个特殊的类型NaN,当对非数字进行Number转换时,会变为这个。

7、堆栈溢出之谜

下面的代码将会造成栈溢出,请问如何优化,不改变原有逻辑:

1
2
3
4
5
6
7
8
9
10
var list = readHugeList();

var nextListItem = function() {
var item = list.pop();

if (item) {
// process the list item...
nextListItem();
}
};

答案:

1
2
3
4
5
6
7
8
var nextListItem = function() {
var item = list.pop();

if (item) {
// process the list item...
setTimeout(nextListItem,0)
}
};

首先必须搞清楚,堆栈溢出的原因:

原因是每次执行代码时,都会分配一定尺寸的栈空间(Windows系统中为1M),每次方法调用时都会在栈里储存一定信息(如参数、局部变量、返回值等等),这些信息再少也会占用一定空间,成千上万个此类空间累积起来,自然就超过线程的栈空间了。那么如何解决此类问题?

这里介绍两个思路解决此问题: 1、异步,2、闭包

显然,这里就是使用的第一种方法,闭包。为什么使用setTimeout就可以解决问题?我们看下与没用之前的差别。如果没有使用setTimeout,那么函数将在大数据前不断的回调,直到最后走到重点,最初的函数才运行结束,释放内存。 但是如果使用了setTimeout,我们知道它是异步的,即使设置了时间为0,它也允许先执行下面的内容,可以释放堆栈,从而避免堆栈溢出的问题。 换言之,加了setTimeout,nextListItem函数被压入事件队列,函数可以退出,因此每次会清空调用堆栈。

8、你真的懂对象(Object)的key吗?

1
2
3
4
5
6
7
8
var a={},
b={key:'b'},
c={key:'c'};

a[b]=123;
a[c]=456;

console.log(a[b]); //456

原因是什么呢? 这里了解ES6新的数据类型 map 的应该就会意识到了,没错,对象的 key 值是只允许 String 类型的,这也是为什么引入了 map 数据类型了。 好了,那如果把一个对象作为 key 值,就会调用 toString 方法了。

Object.prototype.toString(obj) 会得到什么呢?没错`[object Object]。 那所以

1
2
a[b] ==> a["[object Object"] = 123;
a[c] ==> a["[object Object"] = 456;

9、回文判断

请做一个回文判断的函数,判断是否是回文

这里主要考虑了一个健壮性的问题,多了一个正则来检测:

1
2
3
4
function check(str) {
str = str.replace(/\W/g,'').toLowerCase();
return str === str.split('').reverse().join('')
}

10、下面程序的输出结果是?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var length = 10;
function fn() {
console.log(this.length);
}

var obj = {
length: 5,
method: function(fn) {
fn();
arguments[0]();
}
};

obj.method(fn, 1); // 10 2

这个我做错在第二个输出上,其实对this了解后就知道,第一个输出10应该是很显然的:虽然在程序执行时,使用了obj.method方法,让this指向了obj,但是真正的函数执行在函数体内部,也即当fn()执行的时候,this是指向window的,所以第一次执行结果是10

那么这里第二次执行arguments[0]为什么结果是2?

分析下在method(fn,1)执行时,经历了什么: 首先两个参数fn和1会被放入arguments中,在arguments中第一个参数就是我们传入的函数;接下来fn执行,此时this没有绑定因此指向window,输出10。 然而到了arguments0这一句,相当于把arguments[0]中的第一个参数拿来执行, 效果如下:

1
2
arguments[0]()  //执行,等同于下面的
arguments.0() //当然这句话是不合法的,但是这样我们可以更清楚知道,this是指向arguments实例本身

arguments.length就是它本身的长度(arguments是一个类数组,具有length属性),因此输出2

11、try..catch程序的输出结果

1
2
3
4
5
6
7
8
9
10
(function () {
try {
throw new Error();
} catch (x) {
var x = 1, y = 2;
console.log(x);
}
console.log(x);
console.log(y);
})();

输出结果:

1
2
3
1
undefined
2

我们都知道var是在预编译阶段会有一个变量提升,这种类型很容易解决,但是当遇到在catch(x)中与已有变量重名的情况,一定要区分两者之间的关系。

用变量提升的方法,把程序重写并分析如下:

1
2
3
4
5
6
7
8
9
10
11
12
(function () {
var x,y; // 外部变量提升
try {
throw new Error();
} catch (x/* 内部的x */) {
x = 1; //内部的x,和上面声明的x不是一回事!!
y = 2; //内部没有声明,作用域链向上找,外面的y
console.log(x); //当然是1
}
console.log(x); //只声明,未赋值,undefined
console.log(y); //就是2了
})();

这样子就很清晰,之后注意预编译的过程,把变量和函数定义进行提升后,进行分析,会清楚很多

12、下面程序的输出

1
2
3
4
5
6
var x = 21;
var girl = function () {
console.log(x);
var x = 20;
};
girl (); // undefined

函数内部变量提升。 相当于:

1
2
3
4
5
6
7
var x = 21;
var girl = function() {
var x;
console.log(x); // undefined
x = 20;
}
}

13、 运算符考点: 下面程序输出是什么?

1
2
3
4
5
console.log(1 < 2 < 3);
console.log(3 > 2 > 1);

//true
//flase

核心在于 js 怎么去解析 < 和 > 运算符。 在JS中,这种运算符是从左向右运算的,所以 3>2>1 就被转换成了 true>1,而 true 的值是 1,接着比较 1>1 就返回 false 了。

14、parseInt (val, radix) :两个参数,val 值,radix 基数(就是多少进制转换)

1
2
3
4
5
6
["1", "2", "3"].map(parseInt)
// 答案:[1, NaN, NaN]
// 其实等于
['1', '2', '3'].map((item, index) => {
return parseInt(item, index)
})
  1. parseInt(‘1’, 0); // radix为0时,使用默认的10进制。
  2. parseInt(‘2’, 1); // radix值在2-36,无法解析,返回NaN
  3. parseInt(‘3’, 2); // 基数为2,2进制数表示的数中,最大值小于3,无法解析,返回NaN

15、IEEE 754标准中的浮点数并不能精确地表达小数

1
2
3
4
5
6
var two   = 0.2
var one = 0.1
var eight = 0.8
var six = 0.6
[two - one == one, eight - six == two]
//答案:[true, false]
1
2
3
4
5
6
// 巩固:
var two = 0.2;
var one = 0.1;
var eight = 0.8;
var six = 0.6;
(eight - six).toFixed(4) == two //true

16、下面代码的输出是什么?

1
2
3
4
5
6
7
8
9
10
const shape = {
radius: 10,
diameter() {
return this.radius * 2;
},
perimeter: () => 2 * Math.PI * this.radius
};

shape.diameter();
shape.perimeter();

答案: 20 and NaN

请注意,diameter是普通函数,而perimeter是箭头函数。

对于箭头函数,this关键字指向是它所在上下文(定义时的位置)的环境,与普通函数不同! 这意味着当我们调用perimeter时,它不是指向shape对象,而是指其定义时的环境(window)。没有值radius属性,返回undefined。

17、哪个选项是不正确的?

1
2
3
4
5
6
7
8
const bird = {
size: "small"
};

const mouse = {
name: "Mickey",
small: true
};
  • A: mouse.bird.size
  • B: mouse[bird.size]
  • C: mouse[bird[“size”]]
  • D: All of them are valid

答案: A

在JavaScript中,所有对象键都是字符串(除了Symbol)。尽管有时我们可能不会给定字符串类型,但它们总是被转换为字符串。

JavaScript解释语句。当我们使用方括号表示法时,它会看到第一个左括号[,然后继续,直到找到右括号]。只有在那个时候,它才会对这个语句求值。

mouse [bird.size]:首先它会对bird.size求值,得到small。 mouse [“small”]返回true。

但是,使用点表示法,这不会发生。 mouse没有名为bird的键,这意味着mouse.bird是undefined。 然后,我们使用点符号来询问size:mouse.bird.size。 由于mouse.bird是undefined,我们实际上是在询问undefined.size。 这是无效的,并将抛出Cannot read property “size” of undefined。

18、下面代码的输出是什么?

1
2
3
4
5
6
7
let a = 3;
let b = new Number(3);
let c = 3;

console.log(a == b);
console.log(a === b);
console.log(b === c);

答案:true false false

new Number()是一个内置的函数构造函数。 虽然它看起来像一个数字,但它并不是一个真正的数字:它有一堆额外的功能,是一个对象。

当我们使用==运算符时,它只检查它是否具有相同的值。 他们都有3的值,所以它返回true。

译者注:== 会引发隐式类型转换,右侧的对象类型会自动拆箱为 Number 类型。

然而,当我们使用===操作符时,类型和值都需要相等,new Number() 不是一个数字,是一个对象类型。两者都返回 false。

19、下面代码的输出是什么?

1
2
3
4
5
6
7
8
9
10
11
12
class Chameleon {
static colorChange(newColor) {
this.newColor = newColor;
}

constructor({ newColor = "green" } = {}) {
this.newColor = newColor;
}
}

const freddie = new Chameleon({ newColor: "purple" });
freddie.colorChange("orange");

答案: TypeError

colorChange 方法是静态的。 静态方法仅在创建它们的构造函数中存在,并且不能传递给任何子级。 由于freddie是一个子级对象,函数不会传递,所以在 freddie 实例上不存在 freddie 方法:抛出 TypeError。

20、当我们这样做时会发生什么?

1
2
3
4
5
function bark() {
console.log("Woof!");
}

bark.animal = "dog";
  • A: Nothing, this is totally fine!
  • B: SyntaxError. You cannot add properties to a function this way.
  • C: undefined
  • D: ReferenceError

答案: A

这在JavaScript中是可能的,因为函数也是对象!(原始类型之外的所有东西都是对象)

函数是一种特殊类型的对象。您自己编写的代码并不是实际的函数。 该函数是具有属性的对象,此属性是可调用的。

21、下面代码的输出是什么?

1
2
3
4
5
6
7
8
9
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}

const member = new Person("Lydia", "Hallie");
Person.getFullName = () => this.firstName + this.lastName;

console.log(member.getFullName());
  • A: TypeError
  • B: SyntaxError
  • C: Lydia Hallie
  • D: undefined undefined

答案: A

您不能像使用常规对象那样向构造函数添加属性。 如果要一次向所有对象添加功能,则必须使用原型。 所以在这种情况下应该这样写:

1
2
3
Person.prototype.getFullName = function () {
return `${this.firstName} ${this.lastName}`;
}

这样会使 member.getFullName() 是可用的,为什么样做是对的? 假设我们将此方法添加到构造函数本身。 也许不是每个 Person 实例都需要这种方法。这会浪费大量内存空间,因为它们仍然具有该属性,这占用了每个实例的内存空间。 相反,如果我们只将它添加到原型中,我们只需将它放在内存中的一个位置,但它们都可以访问它!

22、所有对象都有原型吗?

不是的,础对象指原型链终点的对象。基础对象的原型是null。

23、下面代码的输出是什么?

1
2
3
4
5
6
7
8
9
10
function getPersonInfo(one, two, three) {
console.log(one);
console.log(two);
console.log(three);
}

const person = "Lydia";
const age = 21;

getPersonInfo`${person} is ${age} years old`;
  • A: Lydia 21 [“”, “is”, “years old”]
  • B: [“”, “is”, “years old”] Lydia 21
  • C: Lydia [“”, “is”, “years old”] 21

答案: B

如果使用标记的模板字符串,则第一个参数的值始终是字符串值的数组。 其余参数获取传递到模板字符串中的表达式的值!

24、下面代码的输出是什么?

1
const sum = eval("10*10+5");
  • A: 105
  • B: “105”
  • C: TypeError
  • D: “10*10+5”

答案: A

eval会为字符串传递的代码求值。 如果它是一个表达式,就像在这种情况下一样,它会计算表达式。 表达式为10 * 10 + 5计算得到105。

25、下面代码的输出是什么?

1
2
3
4
5
6
7
const obj = { 1: "a", 2: "b", 3: "c" };
const set = new Set([1, 2, 3, 4, 5]);

obj.hasOwnProperty("1");
obj.hasOwnProperty(1);
set.has("1");
set.has(1);
  • A: false true false true
  • B: false true true true
  • C: true true false true
  • D: true true true true

答案: C

所有对象键(不包括Symbols)都会被存储为字符串,即使你没有给定字符串类型的键。 这就是为什么obj.hasOwnProperty(’1’)也返回true。

上面的说法不适用于Set。 在我们的Set中没有“1”:set.has(’1’)返回false。 它有数字类型1,set.has(1)返回true。

26、下面这些值哪些是假值?

1
2
3
4
5
6
0;
new Number(0);
("");
(" ");
new Boolean(false);
undefined;
  • A: 0, ‘’, undefined
  • B: 0, new Number(0), ‘’, new Boolean(false), undefined
  • C: 0, ‘’, new Boolean(false), undefined
  • D: 所有都是假值

答案: A

JavaScript中只有6个假值:

  • undefined
  • null
  • NaN
  • 0
  • ‘’ (empty string)
  • false
  • 函数构造函数,如 new Number 和 new Boolean 都是真值。