JS相关

225 阅读10分钟

1 typeof 知多少

typeof 判断数据类型会输出如下这些:

number string boolean object undefined function

需要注意的:

console.log(typeof(null)) // object
console.log(typeof(undefined)) // undefined

开门见山,下面输出会是什么?

console.log(typeof 1 - '1'); //NaN
console.log(typeof(1 - '1')); // number
console.log(typeof('1' -'1')) //number

继续 gogogo

console.log(a)

image.png

console.log(typeof(a)) //undefined 

这里 a 没声明,判断类型当然是 undefined呐!

继续加深

console.log(typeof(typeof(a))) //string
console.log(typeof(typeof(123))) //string

原因是 typeof 判断数据类型时,会返回字符串形式的类型,例如上上题的typeof(typeof(a)),里面会返回一个 'undefined'这样的结果,而 typeof(typeof(123)) 里面会返回一个'number'。因此我们再次 typeof 时,就会返回 string 类型。(恍然大悟,给自己鼓个掌~)

2 显示、隐式类型转换

显示转换

let a = '123'
console.log(typeof(Number(a)) + '-' + Number(a)) //number-123

我们把 a 改成 字符串 'true',又会输出什么呢?

let a = 'true'
console.log(typeof(Number(a)) + '-' + Number(a)) //number-NaN

此时会输出 NaN,如果把 a 改成 true,就会输出数字 1(即改成下述代码)。

let a = true
console.log(typeof(Number(a)) + '-' + Number(a)) //number-1

继续,来点特殊一点的东西:

let a = null
console.log(typeof(Number(a)) + '-' + Number(a)) // 
let a = undefined
console.log(typeof(Number(a)) + '-' + Number(a))

对于 null,结果会输出数字 0 对于 undefined,结果会输出 NaN

var a = 'a'
console.log(typeof(Number(a)) + '-' + Number(a)) // number-NaN
var a = '1a'
console.log(typeof(Number(a)) + '-' + Number(a))// number-NaN
var a = 3.14
console.log(typeof(Number(a)) + '-' + Number(a))// number-3.14
console.log(typeof(NaN)) // number

接下来,我们一起来探讨一下 parseInt

还是先来个简单的热热身,我们知道 parseInt会将数字转换成整形,例如:

let a = '123'
console.log(typeof(parseInt(a)) + '-' + parseInt(a)) //number-123
let a = true
console.log(typeof(parseInt(a)) + '-' + parseInt(a))

答案是 number-NaN,因为 parseInt不会对非数字进行取整操作。

对于 truefalseundefinednullNaN 都会输出 NaN。如果是小数,不会四舍五入,而是直接舍弃小数点后的位数。

parseInt()更多用法:

parseInt() 函数可解析一个字符串,并返回一个整

参数描述
string必需。要被解析的字符串。
radix可选。表示要解析的数字的基数。该值介于 2 ~ 36 之间。如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果它以 “0x” 或 “0X” 开头,将以 16 为基数。如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。
parseInt("10");			//返回 10
parseInt("19",10);		//返回 19 (10+9)
parseInt("11",2);		//返回 3 (2+1)
parseInt("17",8);		//返回 15 (8+7)
parseInt("1f",16);		//返回 31 (16+15)
parseInt("010");		//未定:返回 10 或 8

最后,再来两个输出,结束我们的 parseInt 的介绍

console.log(parseInt('abc123')) // NaN
console.log(parseInt('123abc456')) // 123

parseFloat()用法(保留小数位数):

console.log(parseFloat('3.1415926').toFixed(2)) // 3.14
console.log(parseFloat('3.1415926').toFixed(4)) // 3.1416

上述我们进行了字符串到整数的转换,下面我们介绍整数到字符串的转换:

由于还算比较简单,这里我就直接上代码了。

let str = 123 + ''
let str1 = 123
str1 = str1.toString()
console.log(typeof(str)) //string
console.log(typeof(str1)) // string

比较特殊的是,下面会报错,因为 nullundefined 会报错。

let str = null
let str1 = undefined
str = str.toString()
str1 = str1.toString()
console.log(typeof(str)) // error
console.log(typeof(str1)) // error

另外, toString()也能传一个基数,可以进行进制转换。

let num = 10
console.log(num.toString(2)) // 1010

接下来,我们探讨一下 Boolean,直接看下面几个输出就好了,记住下面几个 false就行,其它就为 true 了。

console.log(Boolean(1)) // true
console.log(Boolean(null)) // false
console.log(Boolean(undefined)) // false
console.log(Boolean(NaN)) // false
console.log(Boolean("")) // false
console.log(Boolean(0)) // false

隐式转换

开门见山,还是先看一道题,注意隐式转换标题!

let a = '123'
a++
console.log(a)

答案是 124,因为看到 a++ 操作,会有一个 Number(a) 的操作,然后数字自加,得到 124

let a = 'a' + 1
console.log(a) 

答案是 a1,这应该好理解,此时将 1 进行 String(1) 的操作,然后进行字符串拼接。

let a = '3' > 2
console.log(a) // true
let a = 1 == '1'
console.log(a) // true

下面来一道经典题,会输出什么?

let a = 1 === '1'
console.log(a)

答案是 false,因为 ===是全等于,不会进行隐式转换,而上面 == 是会进行隐式转换的。

继续

let a = NaN == NaN
console.log(a)

答案是 false,因为和谁都不相等,连自己也不相等。

继续,会输出什么?难倒你为止!

let a = 4 > 3 > 2 
let b = 2 > 1 == 1  
console.log(a,b)

答案是 falsetrue ,因为 4 > 3 会返回 true ,然后再隐式转换为 1,下面类似。

let a = undefined > 0 
let b = undefined == 0
let c = undefined < 0
let d = undefined == undefined
let e = undefined == null
let f = null > 0
let g = null == 0
let h = null < 0

console.log(a,b,c,d,e,f,g,h)

答案是 false false false true true false false false,因为 nullundefined 只和它们比才相等。

补充:

显示转换那里,我们介绍了字符串转整数的几种方式,还例举了很多题目,现在再补充一个:

let a = '123'
console.log(typeof(+ a) + '-' + +a) // number-123

有了前面转换知识了,那就看看下面会输出什么吧

let a = NaN
let b = '123'
let c = 'a'
let d = null
let e = undefined
console.log(isNaN(a))
console.log(isNaN(b))
console.log(isNaN(c))
console.log(isNaN(d))
console.log(isNaN(e))
true
false
true
false
true
记得前面的结论嘛:对于 null,结果会输出数字 0
对于 undefined,结果会输出 NaN

最后的题

console.log(typeof(1 -'1'))
console.log(typeof('1' - '1'))

答案都是 number

3 函数基础

函数种类、字面量 开门见山,下面代码会输出什么

var test = function test1() {
  var a = 1;
  console.log(a);
}
test()
test1()

答案是 1 和 报错,报错信息如下:

image.png 这是为什么呢?实际上,我们申明了一个变量 test,把这个函数 test1赋给它的时候,就已经把这个变量赋予了函数的功能,调用 test()方法当然能够正常运行,输出 1。而对于表达式赋值,会自动忽略后面的函数名称,也就是说写与不写并不影响 test()方法的执行。不信,看看下面代码会输出什么?

var test = function() {
  var a = 1;
  console.log(a);
}
test() // 1

那这样,是不是说 test1完全没作用了,那写着干嘛,还多几个字符。当然不是!,test1在函数体内部是可见的,而在外部却不可见,通过这样,我们就可以实现递归操作

var test = function() {
  var a = 1;
  console.log(a);
}

而对于上述代码,后面函数体没有名字,我们称之为 匿名函数,是不是有点印象了,原来就是这东西,哟西~

而通过这种方式赋值的表达式,我们称之为 匿名函数表达式,也称为 函数字面量,这些专有名词一出来,瞬间觉得有点逼格了有没有!

字面量这种东西,简单来说就是数据,例如下述,有数字字面量,字符串字面量,数组字面量等等。简单理解就是,对于赋值的过程,右边的数据就是字面量。

var a = 10
var b = '111'
var c = [1,2,3]

形参实参映射

补充:如何获取形参和实参对应的长度?

function test (a,b) {
  console.log(test.length)  // 形参的长度 2
  console.log(arguments.length) // 实参的长度 3
}
test(1,2,3)

我想小伙伴们应该清楚实参和形参是什么玩意,但是我们可以更改实参的值吗?例如下述代码,会输出什么呢?

function test(a, b) {
    a = 3;
    console.log(arguments[0]);
}
test(1, 2);

答案是 3,我们可以修改实参的值。

刚刚那题只是简单热个身,继续下一题吧,我们可以改变 b 的值吗?(提示:注意我并没有传对应实参哦~)

function test(a, b) {
  b = 3;
  console.log(arguments[1]);
}
test(1);

答案是 undefined,因此对于上一题表述,要修改一下:对于实参传递过来确定的值,我们是可以进行修改的,而如果实参并没有传递值过来,我们是不能进行修改的。这就是形参和实参的映射关系。

简单解释一下形参和实参的映射关系,其实实参和形参不能说是一类的,看上述代码,我们可以通过 arguments 来获取我们的实参,可以看做是一个数组里面的某一项值,而数组是存放堆内存的,而对应我们形参其实是存放在栈内存的,它们之间会有一个映射关系,并且是一对一对应的,上述我们实参没有对b进行赋值,尽管修改了形参,但改变不了我们的 arguments[1] 就是这个道理。(没有建立一对一映射关系)。

让人费解的 GO 和 AO

作用域引入 再来一道引申题,为后续内容做铺垫。下面 a b c 分别会输出什么?

a = 1;
function test1 () {
  var b = 2;
  console.log(a)  // 
  function test2() {
    var c = 3
    console.log(b); // 
  }
  test2();
  console.log(c); // 
}
test1();

答案是 1 2 报错,这就牵扯到 scope 问题了,简单理解就是函数内部能访问外面的变量,而函数外面却不能访问内部的变量,也就是闭包问题。(这个后文会提到)

函数默认参数

如果实参没有赋值,那么形参怎样设置默认参数呢?说到默认参数,我想你应该会想到如下代码:

function test(a = 1, b = 1) {
  console.log(a)
  console.log(b)
}
test() // 1 1

好的,上述问题算是开胃小菜,我们继续,我如果给 a 设定默认值,而 b 通过实参传递过来呢?可以实现吗?之前没有传参的话,不是默认打印 undefined吗,那我现在给 a 传递一个 undefined,是不是就会定为默认值。

function test(a = 1, b) {
  console.log(a)
  console.log(b)
}
test(undefined, 2)

答案是可以的,上述代码输出结果为 1 2。简单解释一下,在之前我们将了形参实参是有一个映射关系,对于堆内存 arguments 里面,如果给了 undefined,那么就会去栈内存形参里面找,如果不为 undefined,则会设置形参的默认值。(其实这是 es6 的语法了)

那么,可以用es5的方式实现一下吗?(当然可以,见代码)

function test(a, b) {
  a = arguments[0] || 1
  b = arguments[1] || 1
  console.log(a)
  console.log(b)
}
test(undefined, 2)

预编译

预编译总结一下就是如下几点:

检查通篇的语法错误 解释一行,执行一行 下面这两段代码,熟悉的同学一下就明白了,面试常考的经典题!

test()
function test(){
  console.log(1)
}

1

console.log(a);
var a = 1;

undefined

这就扯到了函数声明提升和变量提升相关的问题了。这里总结整理一下:

函数声明会进行整体的提升,而变量只有声明进行了提升,赋值不会提升

关于变量那块,举例下面代码,其实是有两个步骤:第一,进行变量声明 var a; 第二,进行赋值操作,a = 1;

好了,这里我就认为你已经理解了提升相关的知识了,我们来看一道题吧:

console.log(a)
function a(a) {
  var a = 10;
  var a = function () {

  }
}
var a = 1;

答案是[Function: a] (即函数 a),这里可能一下也想不明白,我们先来讲一下知识,再来解决这个问题吧。

讲解暗示全局变量 imply golbal variable

下面代码会输出 1,比较简单,就直接说答案了。实际上这里就暗示全局变量了,因为全局有一个 window对象 ,下面代码也可以这样表示 window.a = 1,所有权归 window。

a = 1;
console.log(a);

这又让我想到了下述代码,b 能打印出来吗?还是会报错?

function test() {
  var a = b = 1;
}
test();
console.log(b)

答案是 1,能打印出来 b,这种写法就是经典的变量泄露问题。而 a没办法打印,因为它是test函数的局部变量,相当于闭包内的变量,外层没办法访问闭包内的变量。

继续,这次加大一点难度。下面代码又分别输出什么呢?

function test(a){
  console.log(a);
  var a = 1;
  console.log(a);
  function a() {}
  console.log(a);
  var b = function(){}
  console.log(b)
  function d(){}
}
test(2);

放答案之前,先总结一下知识点,函数在执行之前,会生成一个 AO(activation object,也称为活动对象或者函数上下文)这个AO会按照如下形式创建:

第一步:寻找函数里的形参和变量声明 第二步:将实参的参数值赋值给形参 第三步:寻找函数声明,然后赋值函数体 第四步:执行 下述代码是预编译(即函数执行之前AO对象的结果):