我的前端面试准备合集
本人是在校大学生,准备面试了,记录一些基础知识吧~
- 目录
- 认识js
- 数据类型
- 类型转换
- 运算符
一、认识Javascript
javascript是一种轻量级的脚本语言,所以它不具备开发操作系统的能li,只是用来操作控制一些大型的应用程序的一种嵌入式的脚本。
javascript语法很简单,大致分为基本语法(比如操作符、控制结构、语句)和标准库,就是一系列具有各种功能的对象比如Array、Date、Math等),再者javascript提供的api很少,I/O等相关API都是靠宿主环境,去调用宿主环境的API的。
目前,已经嵌入 JavaScript 的宿主环境有多种,最常见的环境就是浏览器,另外还有服务器环境,也就是 Node 项目。
从语法上看js是一门对象模型的语言,但是却又不是一门真正的面向对象的语言,因为除此之外,他还支持编程范式,比如函数式编程。
JavaScript 的发明目的,就是作为浏览器的内置脚本语言,为网页开发者提供操控浏览器的能力。它是目前唯一一种通用的浏览器脚本语言,所有浏览器都支持。它可以让网页呈现各种特殊效果,为用户提供良好的互动体验。
二、数据类型
1、数据类型的认识
Js内置了7种数据类型,七种内置类型又分为两大类型:基本类型和对象(Object)。
除了symbol 不常用之外,包括五种基本类型null、undefined、string、number、boolean和一种引用类型object。ES6引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型。它也是基本数据类型。
javascript在设计当初借鉴了java的一些东西,我们可以对比他门的数据类型,可以发现许多相似之处,只是js将多个类型归结为一个了。
其中 JS 的 number 类型是浮点类型的,在使用中会遇到某些Bug,比如0.1 + 0.2 !== 0.3,但是这一块的内容会在进阶部分讲到。string 类型是不可变的,无论你在 string 类型上调用何种方法,都不会对值有改变。
为什么JS可以一个number解决所有的数值类型,或者说一个变量就可以承载任意类型的数据。答案在你开始学习JavaScript的那几段话中,“JavaScript是一种动态类型、弱类型...的语言”。动态类型意味着在声明一个变量之后,可以存储不同类型的变量。也就是说在JS中声明一个变量a,他的初始值是13,var a= 13, 在这语句之后,你仍然可以再写一个这样的语句a = '13.333' 来改变a的值且不会报错,甚至可以写 a = 'name is sam' 。
- 按值访问和按引用访问
基本数据类型的值是按值访问的
基本类型的值是不可变的
基本类型的比较是它们的值的比较
基本类型的变量是存放在栈内存(Stack)里的
引用类型的值是按引用访问的
引用类型的值是可变的
引用类型的比较是引用的比较
引用类型的值是保存在堆内存(Heap)中的对象(Object) 与其他编程语言不同,JavaScript 不能直接操作对象的内存空间(堆内存)
图解:
- 针对按值访问和按引用访问的相等判断~~
对于引用类型的变量,==和===只会判断引用的地址是否相同,而不会判断对象具体里属性以及值是否相同。因此,如果两个变量指向相同的对象,则返回true。
var arrRef = ["Hi!"];
var arrRef2 = arrRef;
console.log(arrRef === arrRef2); // true
如果是不同的对象,及时包含相同的属性和值,也会返回false。
var arr1 = ["Hi!"];
var arr2 = ["Hi!"];
console.log(arr1 === arr2); // false
如果想判断两个不同的对象是否真的相同,一个简单的方法就是将它们转换为字符串然后判断。
var arr1str = JSON.stringify(arr1);
var arr2str = JSON.stringify(arr2);
console.log(arr1str === arr2str); // true
另一个方法就是递归地判断每一个属性的值,直到基本类型位置,然后判断是否相同。
- 函数参数传递中的值传递和引用传递
当将基本数据类型传入函数,函数会进行拷贝副本然后赋值(重新开辟栈空间)。
var hundred = 100;
var two = 2;
function multiply(x, y) {
return x * y;
}
var twoHundred = multiply(hundred, two);
hundred的值拷贝给变量x,two的值拷贝给变量y。
当传入的是引用类型,函数会拷贝其地址一份赋给参数变量,指向的其实是同一块堆地址,函数中的修改会直接改变外部的引用。
function changeAgeImpure(person) {
person.age = 25;
return person;
}
var alex = {
name: "Alex",
age: 30
};
var changedAlex = changeAgeImpure(alex);
console.log(alex); // { name: 'Alex', age: 25 }
console.log(changedAlex); // { name: 'Alex', age: 25 }
对该变量的操作将会影响到原本的对象。这样的编程手法将产生附带影响,是的代码的逻辑复杂和可读性变低。
因此,很多数组函数,比如Array.map和Array.filter是以纯函数的形式实现。虽然它们的参数是一个数组变量,但是通过深度拷贝并赋值给一个新的变量,然后在新的数组上操作,来防止原始数组被更改。
让我们来看如何实现一个纯函数:
function changeAgePure(person) {
var newPersonObj = JSON.parse(JSON.stringify(person));
newPersonObj.age = 25;
return newPersonObj;
}
var alex = {
name: "Alex",
age: 30
};
var alexChanged = changeAgePure(alex);
console.log(alex); // { name: 'Alex', age: 30 }
console.log(alexChanged); // { name: 'Alex', age: 25 }
面试题目:值传递和引用传递经常在面试中被问到,来尝试回答一下如下代码如何输出:
function changeAgeAndReference(person) {
// person是地址副本 person, personObj1 ---> 001
person.age = 25; // 更改了外面的personObj1指向的变量对象的值
// 然后让person 变量指向了另外一个地方
person = {
name: "John",
age: 50
};
// 此时 person --> 002
// personObj1 ---> 001
return person; //
}
var personObj1 = {
name: "Alex",
age: 30
};
var personObj2 = changeAgeAndReference(personObj1);
// personObj2, person ---> 002
console.log(personObj1); // -> 001 {name: "Alex", age: 25}
console.log(personObj2); // -> 002 {name: "John", age: 50}
面试题目:对象类型和原始类型的不同之处?函数参数是对象会发生什么问题?
对象类型和原始类型不同的是,原始类型存储的是值,对象类型存储的是地址(指针)。当你创建了一个对象类型的时候,计算机会在内存中帮我们开辟一个空间来存放值,但是我们需要找到这个空间,这个空间会拥有一个地址(指针)。
var a = 1;
// 为a分配一个栈内存
var b = 2;
// 为b分配一块栈内存,存储的内容与a相同
a = "meils";
// 为a再重新分配一个栈内存,之前的a的栈内存销毁。
接下来我们来看函数参数是对象的情况:
案例1
function test(person) {
// 函数传参是传递对象指针的副本
// 这里修改对象的属性,外面也同步
person.age = 26
}
var p1 = {
name: 'yck',
age: 25
}
test(p1)
console.log(p1)
// {name: "yck", age: 26}
案例2
function test(person) {
person.age = 26
person = {
name: "zjj"
}
}
var p1 = {
name: 'yck',
age: 25
}
test(p1)
console.log(p1)
// {name: "yck", age: 26}
案例3
function test(person) {
person.age = 26
person = {
name: 'yyy',
age: 30
}
return person
}
var p1 = {
name: 'yck',
age: 25
}
var p2 = test(p1)
console.log(p1) // {name: "yck", age: 26}
console.log(p2) // {name: "yyy", age: 30}
接下来让我为你解析一番:
首先,函数传参是传递对象指针的副本
到函数内部修改参数的属性这步,我相信大家都知道,当前 p1 的值也被修改了
但是当我们重新为 person 分配了一个对象时就出现了分歧,请看下图
下面我们分别认识它们吧:
number类型我们需要知道哪些?
1、在js内部,所有的数值都是用64位浮点形式表示的,整数也是。
2、有些时候我们必须要使用整数来计算,这时候js会将64位小数转换位32位整数,然后再计算。
3、如果涉及到小数的比较运算,因为浮点型是不准确的,所以我们要小心。比如:
0.1 + 0.2 === 0.3
// false
0.3 / 0.1
// 2.9999999999999996
(0.3 - 0.2) === (0.2 - 0.1)
// false
4、第1位:符号位,0表示正数,1表示负数,11位指数部分,52位小数部分(即有效数字)。因此所能表示的整数范围是2^52
5、64位浮点数的指数部分的值最大为2047,分出一半表示负数,则 JavaScript 能够表示的数值范围为2^1024到2^1023(开区间),超出这个范围的数无法表示。
6、JavaScript 提供Number对象的MAX_VALUE和MIN_VALUE属性,返回可以表示的具体的最大值和最小值。
7、js特殊数值有 -0 +0 NaN 正负Infinity。
8、溢出能够表示的范围会返回Infinity(正数)或者0(负数)
9、数值相关的全局方法 parseInt() parseFloat() isNaN() isFinite()
10、-0 === +0 // true
0 === -0 // true
0 === +0 // true
11、NAN 是非数值,主要出现在数字解析出错的场合,比如:
5 - 'x' // NaN
0 / 0 // NaN
typeof NaN // 'number'
NaN === NaN // false
Boolean(NaN) // false
NaN + 32 // NaN
NaN - 32 // NaN
NaN * 32 // NaN
NaN / 32 // NaN
1 / -0 // -Infinity
-1 / -0 // Infinity
string
1、JavaScript使用Unicode字符集。
null && undefined
1、 都表示没有,在真值判断中都是false,null == undefined
2、 二者的区别,null是一个表示‘空’的对象,转为数值时为0,undefined 是一个表示此处无定义的原始值,转为数值时为NaN。
3、 由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null的类型标签也成为了 0,typeof null就错误的返回了"object"
boolean
1、 除了以下值: undefined null false 0 NaN "" 或者' '其他值都视为true。
2、 空数组和空对象都是true。
总之,6大基本类型一个引用类型,一共7大类型!!
对于基本类型来说,如果使用字面量的方式,那么这个变量只是个字面量,只有在必要的时候才会转换为对应的类型。
原始类型存储的都是值,是没有函数可以调用的,比如 undefined.toString()
'1'.toString() 是可以使用的。其实在这种情况下,'1'已经不是原始类型了,而是被强制转换成了 String 类型也就是对象类型,所以可以调用 toString 函数。
let a = 111 // 这只是字面量,不是 number 类型
a.toString() // 使用时候才会转换为对象类型
2、数据类型的判别
typeof
适合判断基本数据类型和function的判断
有一点不足的是,typeof null == "object"
typeof "sss" ===> "string"
typeof 123 ===> "Number"
typeof [1,2,3] ===> "object"
typeof new Date() ===> "object"
typeof function(){alert('111');} ===> "function"
typeof undefined ===> "undefined"
typeof NaN ===> "number"
typeof null ===> "object"
typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'
instanceof
判断已知的对象类型或者是自定义的对象
[1,3] instanceof Array === true
new Array('1, 3,4') instanceof Array === true
new Object() instanceof Object === true
new String("string") instanceof String === true
function(){this.name="22";} instanceof Function === true
// 定义一个构造函数
function Person(){
}
// 定义一个构造函数
function Student(){
}
// 每一个构造函数都有一个prototype对象属性, 这个对象属性将会作为通过new Person()创建的对象的一个原型。
// 也就是当我们在new 一个对象的时候,这个对象的原型就指向了这个构造函数的prototype。
Student.prototype = new Person(); // student继承至person
var bson = new Student();
bson instanceof Student
// false
bson instanceof Person
// true
const Person = function() {}
const p1 = new Person()
p1 instanceof Person // true
var str = 'hello world'
str instanceof String // false
var str1 = new String('hello world')
str1 instanceof String // true
Object.prototype.toString.apply()
判断基本数据类型和内置对象
Object.prototype.toString.apply([]) === "[object Array]"
Object.prototype.toString.apply(function(){}) === "[object Function]"
Object.prototype.toString.apply(new Function); // "[object Function]"
Object.prototype.toString.apply(new Object); // "[object Object]"
Object.prototype.toString.apply(new Date); // "[object Date]"
Object.prototype.toString.apply(new Array); // "[object Array]"
Object.prototype.toString.apply(new RegExp); // "[object RegExp]"
Object.prototype.toString.apply(new ArrayBuffer); // "[object ArrayBuffer]"
Object.prototype.toString.apply(Math); // "[object Math]"
Object.prototype.toString.apply(JSON); // "[object JSON]"
var promise = new Promise(function(resolve, reject) {
resolve();
});
Object.prototype.toString.apply(promise); // "[object Promise]"
Object.prototype.toString.apply(124)
// "[object Number]"
Object.prototype.toString.apply("222")
// "[object String]"
Object.prototype.toString.apply(true)
// "[object Boolean]"
Object.prototype.toString.apply(null)
// "[object Null]"
Object.prototype.toString.apply(null) === "[object Null]" // 在IE6/7/8下存在有兼容性问题
如果我们想获得一个变量的正确类型,可以通过 Object.prototype.toString.call(xx)。这样我们就可以获得类似 [object Type] 的字符串。
2、类型转换
类型转换在平时会经常使用到,无非就是转换为布尔值、数值、字符串三类。
1、将非数值转换为数值类型的函数【非数值 => 数值】
Number()可以用于将任何类型转换为对应的数值。ParseInt()、ParseFloat()则是把字符串转为数值型。
var num = ["123" , "124.4" , "234asd" , "asf456"] ;
for (i = 0; i < num.length; i++) {
console.log(parseInt(num[i]));
}
// 123 , 124 , 234 , NaN
ParseFloat 远离同上。另外ParseInt还有另一项用途:parseInt(string,radix):以radix为基底,将string转换成多少进制的整数。
2、将其它类型的数据转换为字符串类型的函数【其他类型 => 字符串】
有两个函数可以把其他数据类型转换为字符串。toString() 和 string() 。
string(): 可以将任意类型转为字符型。 demo.toString(): 将demo转换成字符串类型。demo不能等于null undefined.(也可以传入进制)
3、将其他类型转换成布尔值类型【其他类型 => boolean类型】
1、 除了以下值: undefined null false 0 NaN "" 或者' '其他值都视为true。
2、 空数组和空对象都是true。
4、对象转基本类型
对象在转换类型的时候,会调用内置的 [[ToPrimitive]] 函数,对于该函数来说,算法逻辑一般来说如下:
如果已经是原始类型了,那就不需要转换了 调用 x.valueOf(),如果转换为基础类型,就返回转换的值 调用 x.toString(),如果转换为基础类型,就返回转换的值 如果都没有返回原始类型,就会报错 当然你也可以重写 Symbol.toPrimitive ,该方法在转原始类型时调用优先级最高。
let a = {
valueOf() {
// 优先级第二
return 0
},
toString() {
// 优先级第三
return '1'
},
[Symbol.toPrimitive]() {
// 优先级最高
return 2
}
}
// a是一个对象,要想参与运算就必须先转为数值
1 + a // => 3
let a = {
toString() {
return '1'
}
}
a + 1;
// "11"
let a = {
valueOf() {
// 优先级第二
return 0
},
toString() {
// 优先级第三
return '1'
}
}
1 + a // => 1
5、四则运算
1、只有当加法运算时,其中一方是字符串类型,就会把另一个也转为字符串类型。
2、对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字
1 + '1' // '11'
2 * '2' // 4
[1, 2] + [2, 1] // '1,22,1'
// [1, 2].toString() -> '1,2'
// [2, 1].toString() -> '2,1'
// '1,2' + '2,1' = '1,22,1'
'a' + + 'b' // -> "aNaN"
// 因为 + 'b' -> NaN
// 你也许在一些代码中看到过 + '1' -> 1
true + true // 2
4 + [1,2,3] // "41,2,3"
// 数组通过 toString 转为字符串 1,2,3,得到结果 41,2,3
'a' + + 'a'; // "aNaN"
// 因为 + 'b' 等于 NaN,所以结果为 "aNaN"
4 * '3' // 12
4 * [] // 0
4 * [1, 2] // NaN
Number([1]) // 1
Number([1,2]) // NaN
Number([]) // 0
{} - 1 // -1 {} 被当成了代码块
3、运算符
==
等于运算符我们也是经常使用的,我们应该更好的了解它的判别原理,防止较为隐藏的bug出现。
(1) type(x) == type(y)
如果类型相同,那么以下情况返回true
undefined == undefined
10 == 10
+0 == -0
Null == Null
"aaa" == "aaa"
true == true
false == false
唯独下面这个返回false
NaN == NaN
(2) null == undefined
(3) "2" == 2
(4) 引用类型需转为基本类型在比较
- 栗子:
[''] == false
(1) 需要将布尔类型转为数字类型,而false转为数字的结果是0,所以表达式变为:[''] == 0
[''] == 0
(2) 需要将对象类型转为基本类型:首先调用[].valueOf(),由于数组的valueOf()方法返回自身,所以结果不是原始类型,继续调用[].toString()。对于数组来说,toString()方法的算法,是将每个元素都转为字符串类型,然后用逗号,依次连接起来,所以最终结果是空字符串'',它是一个原始类型的值。
'' == 0
(3) 需要将字符串类型转为数字类型,前面说了空字符串变成数字是0
0 == 0 // true
===
不仅数值相同,数据类型还必须相同。
总结
undefined == null,结果是true。且它俩与所有其他值比较的结果都是false。
String == Boolean,需要两个操作数同时转为Number。
String/Boolean == Number,需要String/Boolean转为Number。
Object == Primitive,需要Object转为Primitive(具体通过valueOf()和toString()方法)。
[] == ![] // true
// 0 == !true
// 0 == 0
未完待续~~