前端面试常见面试题总结
JS的数据类型有哪些 值是如何存储
JavaScript一共有8种数据类型,其中有7种基本数据类型:undefined、Null、Boolean、Number、String、Symbol(es6新增,表示独一无二的值)和BigInt(es10新增)
1种引用数据类型——Object(Object本质上是由一组无序的名值对组成的)。里面包含 function、Array、Date等。JavaScript不支持任何创建自定义类型的机制,而所有值最终都将是上述 8 种数据类型之一。
原始数据类型:直接存储在栈(stack)中,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。
引用数据类型:同时存储在栈(stack)和堆(heap)中,占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
JS中数据类型的判断
- typeof
typeof 对于原始基本数据类型来说,除了 null 都可以显示正确的类型
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof 'str'); // string
console.log(typeof []); // object []数组的数据类型在 typeof 中被解释为 object
console.log(typeof function(){}); // function
console.log(typeof {}); // object
console.log(typeof undefined); // undefined
console.log(typeof null); // object null 的数据类型被 typeof 解释为 object
所以说 typeof 并不能准确判断变量到底是什么类型
- instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。
console.log(2 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log('str' instanceof String); // false
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
// console.log(undefined instanceof Undefined);
// console.log(null instanceof Null);
可以看出直接的字面量值判断数据类型,instanceof可以精准判断引用数据类型(Array,Function,Object),而基本数据类型不能被instanceof精准判断。instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的prototype属性。其意思就是去判断对象是否是某一数据类型(实例)在这里字面量值中的 2、true 、'str’不是实例,所以判断值为false
- constructor 构造函数
console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
这里有一个坑,如果去创建一个对象,更改它的原型,constructor就会变得不可靠了
function Fn(){};
Fn.prototype=new Array();
var f=new Fn();
console.log(f.constructor===Fn); // false
console.log(f.constructor===Array); // true
- Object.prototype.toString.call() 使用 Object 对象的原型方法 toString ,使用 call 进行狸猫换太子,借用Object的 toString 方法
var a = Object.prototype.toString;
console.log(a.call(2)); // Number
console.log(a.call(true)); // Boolean
console.log(a.call('str')); // String
console.log(a.call([])); // Array
console.log(a.call(function(){})); // Function
console.log(a.call({})); // Object
console.log(a.call(undefined)); // Undefined
console.log(a.call(null)); // Null
null 和 undefined 的区别
undefined 和 Null 都是基本数据类型
- undefined代表的含义是未定义 一般变量声明了但还没有定义的时候会返回 undefined
- null 代表的含义是空对象 null 主要用于赋值给一些可能会返回对象的变量,作为初始化。
var a ;
var b = null;
console.log(a) // undefined
console.log(b) // null
其实 null 不是对象,虽然 typeof null 会输出 object,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。
众所周知 undefined 在 js 中不是一个保留字,这意味着我们可以使用 undefined 来作为一个变量名,这样的做法是非常危险的,它会影响我们对 undefined 值的判断。undefined是JS语言中的7大基本类型之一,表示未定义,它的值只有一个,就是undefined。任何变量在赋值前都是undefined 可以使用void 0代替undefined
if(a === b){
// 在类型的值进行比较时==时候会输出
console.log('111')
}else{
console.log('222')
}
if(void 0 === undefined){
console.log('111') // 会输出
}else{
console.log('222')
}
toString()与valueOf()的区别
-
toString()
作用:toString()方法用于将当前对象以字符串的形式进行返回 -
valueOf()
作用:valueOf()方法返回指定对象的原始值,JS会利用 valueOf() 方法用来把对象转换成原始类型的值(数值、字符串和布尔值) 不会进行修改调用对象或数组
var array = ['aa','bb','cc'];
console.log(array.toString()); // aa,bb,cc
console.log(array.valueOf()); // [ 'aa', 'bb', 'cc' ]
可以参考看一下: toString()与valueOf()的区别.
javascript 创建对象的几种方式
我们一般使用字面量的形式直接创建对象,但是这种创建方式对于创建大量相似对象的时候,会产生大量的重复代码。但 js
和一般的面向对象的语言不同,在 ES6 之前它没有类的概念。但是我们可以使用函数来进行模拟,从而产生出可复用的对象
(1)第一种是工厂模式,工厂模式的主要工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用的目的。但是它有一个很大的问题就是创建出来的对象无法和某个类型联系起来,它只是简单的封装了复用代码,而没有建立起对象和类型间的关系。
(2)第二种是构造函数模式。js 中每一个函数都可以作为构造函数,只要一个函数是通过 new 来调用的,那么我们就可以把它称为构造函数。执行构造函数首先会创建一个对象,然后将对象的原型指向构造函数的 prototype 属性,然后将执行上下文中的 this 指向这个对象,最后再执行整个函数,如果返回值不是对象,则返回新建的对象。因为 this 的值指向了新建的对象,因此我们可以使用 this 给对象赋值。构造函数模式相对于工厂模式的优点是,所创建的对象和构造函数建立起了联系,因此我们可以通过原型来识别对象的类型。但是构造函数存在一个缺点就是,造成了不必要的函数对象的创建,因为在 js 中函数也是一个对象,因此如果对象属性中如果包含函数的话,那么每次我们都会新建一个函数对象,浪费了不必要的内存空间,因为函数是所有的实例都可以通用的。
(3)第三种模式是原型模式,因为每一个函数都有一个 prototype 属性,这个属性是一个对象,它包含了通过构造函数创建的所有实例都能共享的属性和方法。因此我们可以使用原型对象来添加公用属性和方法,从而实现代码的复用。这种方式相对于构造函数模式来说,解决了函数对象的复用问题。但是这种模式也存在一些问题,一个是没有办法通过传入参数来初始化值,另一个是如果存在一个引用类型如 Array 这样的值,那么所有的实例将共享一个对象,一个实例对引用类型值的改变会影响所有的实例。
(4)第四种模式是组合使用构造函数模式和原型模式,这是创建自定义类型的最常见方式。因为构造函数模式和原型模式分开使用都存在一些问题,因此我们可以组合使用这两种模式,通过构造函数来初始化对象的属性,通过原型对象来实现函数方法的复用。这种方法很好的解决了两种模式单独使用时的缺点,但是有一点不足的就是,因为使用了两种不同的模式,所以对于代码的封装性不够好。
(5)第五种模式是动态原型模式,这一种模式将原型方法赋值的创建过程移动到了构造函数的内部,通过对属性是否存在的判断,可以实现仅在第一次调用函数时对原型对象赋值一次的效果。这一种方式很好地对上面的混合模式进行了封装。
(6)第六种模式是寄生构造函数模式,这一种模式和工厂模式的实现基本相同,我对这个模式的理解是,它主要是基于一个已有的类型,在实例化时对实例化的对象进行扩展。这样既不用修改原来的构造函数,也达到了扩展对象的目的。它的一个缺点和工厂模式一样,无法实现对象的识别。
谈谈你对this、call、apply和bind的理解
- 在浏览器里,在全局范围内this 指向window对象;
- 在函数中,this永远指向最后调用他的那个对象;
- 构造函数中,this指向new出来的那个新的对象;
- call、apply、bind中的this被强制绑定在指定的那个对象上;
- 箭头函数中this比较特殊,箭头函数this为父级作用域的this,不是调用时的this 要知道前四种方式,都是通过调用确定 也就是动态的 而箭头函数的this指向是静态的 (也可以理解为固化)在声明的时候就确定下来啦!
什么是闭包 为什么要用它?
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。
闭包的特性:
- 函数内再嵌套函数
- 其内部函数可以引用访问外部的参数和变量
- 参数和变量不会被垃圾回收机制回收
闭包不能滥用,否则会导致内存泄露,影响网页的性能。闭包使用完了后,要立即释放资源,将引用变量指向null。
闭包有两个常用的用途
function a(){
var n = 0;
function add(){
n++;
console.log(n);
}
return add;
}
var a1 = a(); //注意,函数名只是一个标识(指向函数的指针),而()才是执行函数;
a1(); //1
a1(); //2 第二次调用n变量还在内存中
-
我们都知道 ,JS的每个函数都是独立的,可以获取外界信息,但是外界却无法直接看到里面的内容 。通过使用闭包,我们可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量 ,也不会受到影响,这就是所谓的“封装性”而之所以使用return 返回函数是因为在函数a外无法直接调用add函数所以return出去。
-
函数的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收