前端面试——JS基础

1 数据类型

原始类型

  • String
  • Number
  • Boolean
  • null
  • undefined
  • Symbol

引用类型

  • Object
  • Array
  • Date
  • RegExp
  • Symbol

问:说说JavaScript中的基本类型有哪些?以及各个数据类型是如何存储的?

堆栈池内存

  • 栈:存放变量
  • 堆:存放复杂对象
  • 池:存放常量

栈和堆是两种基本的数据结构。栈在内存中自动分配内存空间,堆在内存中动态分配内存空间,不一定会自动释放,在项目中将对象类型手动置为null, 是为了减少无用内存消耗。

2 类型转换

数字类型转换

Number:

  • 字符串 => 数字:非有效字符串转化为NaN;
  • 布尔 => 数字:1或0
  • null => 数字:0
  • undefined => 数字:NaN
  • ‘’ =>数字:0
  • 对象 => 数字: 先把对象转化为字符串,再转化为数字
  • [] => 数字:0 遵循对象转换的规则
Number(10); // 10
Number('10'); // 10
Number(null); // 0
Number(''); // 0
Number(true); // 1
Number(false); // 0
Number([]); // 0
Number([1,2]); // NaN
Number('10a'); // NaN
Number(undefined); // NaN

parseInt()/parseFloat()

对于字符串来说,从左到右依次查找有效的数字字符,直到遇到非有效数字字符,停止查找(不管后面是否还有数字)

let str = '12.5px'
parseInt(str) // 12
parseFloat(str) // 12.5
parseFloat(true) // NaN

字符串类型转换

String

对于原始类型来说,转字符串类型默认调用toString() 方法,在外层包一层引号

  • 数字=> 字符串:包裹一层引号。
  • NaN=>字符串: ‘NaN’
  • true=>字符串: ‘true’
  • null =>字符串:‘null’ (浏览器会报错(禁止你使用)—— 通常可以进行转换)
  • undefined=> 字符串:‘undefined’ (浏览器会报错(禁止你使用)—— 通常可以进行转换)
  • Object=>字符串:’[object,Object]’

普通对象转化为字符串为’[object,Object]’,因为Object.prototype.toString方法不是转化为字符串的,而是用来检测数据类型的。

String(123); // "123" 
String(true); // "true" 
String(null); // "null"(报错) 
String(undefined);// "undefined"(报错) 
String([1,2,3]) // "1,2,3" 
String({
    
    }); // "[object Object]"

布尔类型转换

Boolean

  • ‘’、 undefined、NaN、null、false、0 => false
  • 其余转化为true
Boolean('') // false 
Boolean(undefined) // false 
Boolean(null) // false 
Boolean(NaN) // false 
Boolean(false) // false 
Boolean(0) // false 
Boolean({
    
    }) // true 
Boolean([]) // true

3 隐式转换

什么是隐式转换?

在js中,当运算符在运算时,如果两边数据不统一,CPU就无法计算,这时编译器会自动将运算符两边的数据做一个数据类型转换,这种由编译器自动转换的方式就称为隐式转换

隐式转换的规则

转成string类型

  • +(字符串连接符)

转成number类型

  • ++/–(自增自减运算符)
  • ±*/%(算术运算符)
  • > < >= <= == !== === (关系运算符)

转成boolean类型

  • ! (逻辑非运算符)

面试题大坑

坑一:字符串连接符与算数运算符隐式转换规则混淆

// 字符串连接符(只要+号两边有一边是字符串)
1+"true" // 1true
// 算数运算浮(会把其他数据类型调用Number()方法转换成数字)
1+true // 2
1+undefined //NaN
1+null // 1

坑二:关系运算符:会把其他数据类型转换成number之后再比较关系

"2" > 10 // false
"2" >"10" // true,两边都是字符串时按照unicode编码 (字符串。charCodeAt())转换成数字
"abc" > "b" // false, 先比较'a'和'b', 'a'不大于'b',直接返回结果
"abc" > "aad" // true, 先比较'a'和'a',两者相等,接着比较'b'和'a',得出结果
NaN == NaN // false, NaN与任何数据比较都是NaN
undefined==null // true,如果数据类型是undefined和null ,得出固定结果true

坑三:复杂数据类型在隐式转换时会先转换成String, 再转换成Number运算

var a=??
if(a == 1 && a == 2 && a==3) {
    
    
    console.log(1)
} // 如何完善a, 使其正确打印1

// 原理:
// 重写对象的valueOf() 方法
    var a = {
    
    
      i:0,
      valueOf: function() {
    
    
        return ++a.i
      }
    }
    if(a==1 && a==2 && a==3) {
    
     // 每一次运算时都会调用一次a的valueOf方法
      console.log("1");
    }

坑四:逻辑非隐式转换与关系运算符隐式转换混淆

空数组的toString()会得到空字符串,而空对象的toString() 方法会得到字符串’[object, Object]’, Number(’[object, Object]’) == NaN, NaN与任何数据比较都不等

  • []==0, true
  • ![]==0, true
  • []==![], true
  • []==[], false
  • {} = !{}, false
  • {} ={}, false
[]==0 // true, [].valueOf().toString() = '', Number('') ==0 成立
![]==0 // true, 逻辑非优先级高于关系运算符, ![]==false(空数组转布尔得到true),false==0 成立

[]==![] // true, ![]== 0, []==0
[] == [] // false, 引用类型数据存在堆中,栈中存的是地址,所以结果为false

{
    
    } ==!{
    
    } // false, !{} == false, Number({}.valueOf().toString()) == NaN
{
    
    } == {
    
    } // false, Number({}.toString()) == NaN

4 数据类型检测

  • typeof
  • instanceof
  • constructor
  • Object.prototype.toString.call()

typeof

typeof是一元运算符,同样返回一个字符串类型。一般用来判断一个变量是否为空或者是什么类型。

typeof undefined // 'undefined'
typeof '10' // 'String'
typeof 10 // 'Number'
typeof false // 'Boolean'
typeof Symbol() // 'Sysbol'
typeof Function // 'function'
typeof null // 'Object'
typeof [] // 'Object'
typeof {
    
    } // 'Object'

instanceof

instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的prototype属性。可以使用instanceof来进行判断某个对象是不是另一个对象的实例

instanceof 的原理是通过判断该对象的原型链中是否可以找到该构造类型的prototype类型

function Foo() {
    
    }
var f1 = new Foo();
console.log(f1 instanceof Foo); // true

constructor

对于引用类型,除此之外,还可以使用xx.constructor.name 构造函数来判断。

// constructor 构造来判断 
console.log(fn.constructor.name) // Function 
console.log(date.constructor.name)// Date 
console.log(arr.constructor.name) // Array 
console.log(reg.constructor.name) // RegExp

Object.prototype.toString.call()

一种最好的基本类型检测方式,它可以区分null、sring、boolean、number、undefined、array、function、object、date、 math数据类型

// 判断基本类型 
Object.prototype.toString.call(null); // "[object Null]" 
Object.prototype.toString.call(undefined); // "[object Undefined]" 
Object.prototype.toString.call(“abc”);// "[object String]" 
Object.prototype.toString.call(123);// "[object Number]" 
Object.prototype.toString.call(true);// "[object Boolean]" 
// 判断引用类型 
function fn() {
    
     
    console.log(“xiaolu”); 
}
var date = new Date(); 
var arr = [1,2,3]; 
var reg = /[hbc]at/gi; 
Object.prototype.toString.call(fn); // "[object Function]" 
Object.prototype.toString.call(date); // "[object Date]" 
Object.prototype.toString.call(arr); // "[object Array]"
Object.prototype.toString.call(reg); // "[object RegExp]"

5 如何判断数组

**使用instanceof方法: ** 用于判断一个变量是否某个对象的实例

var arr = []
console.log(arr instanceof Array) // true

**使用constructor方法: **

console.log([].constructor == Array) // true

使用Object.prototype.toString.call()方法:

var arr = []
console.log(Object.prototype.toString.call(arr) === '[object Array]') // true

ES5的Array.isArray :

console.log(Array.isArray([])) // true

6 == 和 ===的区别

=== :严格意义上的相等,会比较两个操作符的类型和值。

  • 如果 X 和 Y 的类型不同,返回 false ;

  • 如果 X 和 Y 的类型相同,比较值的大小。

== :非严格意义上的相等,先判断两个操作符的类型是否相等,如果类型不同,则先进行类型转换,然后再判断值是否相等。

  • 如果 X 和 Y 的类型相同,返回 X == Y 的比较结果;

  • 如果 X 和 Y 的类型不同,根据下方表格进一步判断;

    null == undefifined -> true

    String == Number -> 字符串转数字,再比较

    Boolean == Number -> 布尔转数字,再比较

    Object == String,Number,Symbol -> Object 转化为原始类型

7 dom0级事件dom2级事件

dom0级事件

原理:

  • 给当前元素的某一私有属性(例如onclick)赋值的过程,之前属性默认值是null,如果我们赋值了一个函数,就相当于绑定了一个方法)
  • 当我们赋值成功(赋值一个函数),此时浏览器会把DOM元素和赋值的函数建立关联,以及建立DOM元素的行为监听,当某一行为被用户触发,浏览器会把赋值的函数执行;

分为两种:

一是在标签内写事件:

<button onclick="fn()">按钮</button>

二是获取元素绑定事件:

<button id="btn">按钮</button>
var btn = $('#btn').get(0)
btn.onclick = function() {}

注:

第二种方法会覆盖第一种

dom2级事件

原理:

  • DOM2事件绑定是在eventTarget这个内置类的原型上定义的,调用的时候,首先要通过原型链找到这个方法,然后执行完成事件绑定
  • 浏览器会给当前元素的某个事件行为开辟一个事件池(事件队列)
  • 当元素的某一行为被触发,浏览器回到对应事件池中,把当前放在事件池的所有方法按序依次执行

方法: addEventListener()和removeEventListener()。

  • addEventListener(): 可以为元素添加多个事件处理程序,触发时会按照添加顺序依次调用。
  • removeEventListener():不能移除匿名添加的函数。

参数:

  • 第一个参数是事件名(如click)
  • 第二个参数是事件处理程序函数
  • 第三个参数如果是true则表示在捕获阶段调用,为false表示在冒泡阶段调用。

事件流:

  • 事件捕获阶段
  • 处于目标阶段
  • 事件冒泡阶段

详细描述

document.getElementById("btn").attachEvent("onclick", function() {
    
     }); // 只有ie支持
//等价于
document.getElementById("btn").addEventListener("click", function(){
    
    alert(1)}, false);

注:

dom0级与dom2级是共存的 不会覆盖

8 常用数组,字符串方法

常用数组方法

  • push():从尾部添加一个或多个元素
  • pop():删除数组的最后一个元素
  • shift():删除数组的第一个元素
  • unshift():向数组的开头添加一个或多个元素
  • splice(start,0,item):像数组中间添加元素
  • concat(数组||字符串):把两个或多个数组连接起来
  • join():将数组转化为字符串
  • toString():数组转化为字符串
  • reverse():数组反转
  • sort():数组排序
  • indexOf() :返回指定元素在数组中出现的位置,没有则返回-1
  • slice():截取几个连续的元素组成新数组

常用字符串方法

  • chartAt():返回指定位置的字符
  • indexOf():返回字符串中一个子串第一处出现的索引
  • concat():将多个字符组合起来
  • match():在字符串内检索指定的值,或找到一个或多个正则表达式的匹配
  • replace():在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串
  • search():检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串
  • split():字符串转化为数组
  • slice(start,end):提取字符串的某个部分,并以新的字符串返回被提取的部分
  • substr(start,length):在字符串中抽取从 start 下标开始的指定数目的字符
  • substring(start,stop):提取字符串中介于两个指定下标之间的字符

9 数组去重

问:如何给一个数组去重

通过实现一个函数,将下列数组中重复的元素进行删除

[1,2,1,1,1,2,3,3,3,2]
结果:
[1,2,3]

最Low的两种方案

  • 创建一个新数组,然后遍历老数组,每次检查当前元素是否在新数组中,如果在,则不加入。

    let ary = [1,2,1,1,1,2,3,3,3,2] 
    let newAry = [] 
    for(let i = 0;i < ary.length; i++){
          
           
        let item = ary[i]; 
        if(!newAry.includes(item)){
          
           
            newAry.push(item) 
        } 
    }
    console.log(newAry)
    
  • 每拿出一项数据,就与当前数组其他数据做对比,相同的数据进行删除,依次遍历所有数据

    let ary = [1,2,1,1,1,2,3,3,3,2] 
    for(let i = 0;i < ary.length; i++){
          
           
        var item = ary[i]; 
        for(let j = i + 1; j < ary.length; j++){
          
           
            var compare = ary[j]; 
            if(compare === item){
          
           
                ary.splice(j, 1); 
                // 数组塌陷问题:J 后边每一项索引都提前了一位,下次要比较应该  还是 j 这个索引 
                j--; 
            } 
        } 
    }
    

最优秀的两种方案

方案一:基于对象去重

对象去重,最主要的是我们优化了上述谈到的数组塌陷问题。如果当前是重复的数据,就将数组的最后一个数据移动到当前数据进行替换,这样重复的数据删除了,而且数组塌陷问题也得到解决。

let obj = {
    
    } 
for(let i = 0;i < ary.length; i++){
    
     
    let item = ary[i] 
    if(obj[item]){
    
     
        // 将最后一项填补当前项 
        ary[i] = ary[ary.length - 1] 
        // 数组长度减一 
        ary.length--; 
        // 索引减- 
        i--; 
        continue; 
    }
    obj[item] = item
}
console.log(ary)

方案二: 基于Set去重

Set是ES6的新语法,可以去掉重复的元素

ary = new Set(ary); console.log([...ary])

10 数组展平

const arr = [1, 2,  3, [4,5,[5, 6], 7, 8], 9, [10,11,[12]]]

用现成api

flat(depth)

  • depth: 指定要提取嵌套数组的结构深度,默认值为1
  • depth取 Infinity, 展开任意深度嵌套的数组
const arr1 = arr.flat(Infinity)

利用join() 展平

const arr2 = arr.join().split(',').map(Number)

利用toString()直接展平

const arr3 = arr.toString().split(',').map(Number)

原生循环递归实现

function myFlat(arr) {
    
    
    const result = []
    arr.forEach((item)=> {
    
    
        if(Array.isArray(item)) {
    
    
            result.push(...myFlat(item))
        } else {
    
    
            result.push(item)
        }
    })
    return result
}
cosnt arr4 = myFlat(arr)

猜你喜欢

转载自blog.csdn.net/qq_46178261/article/details/112472788
今日推荐