JS常见面试题

1. JS的数据类型

1 JS的基本数据类型有哪些?

​ JavaScript一共有8种数据类型:其中7种基本数据类型,一种引用类型

8种数据类型:

undefined、null、Boolean、Number、Array、String、Symbol(es6)BigInt(es10)

  • Symbol代表创建后独一无二且不可变的数据类型
  • BigInt是一种数字类型的数据,它可以表示任意精度格式的整数,使用BigInt可以安全的存储和操作大整数,即使这个数已经超出了Number能够表示的安全整数范围
一种引用数据类型:Object

2. 引用类型和基本类型的区别

  • 原始数据类直接存储在栈(stack)中,占据空间大小固定。

  • 引用数据类型同时存储在堆和栈中,占据空间大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索在栈中的地址,根据地址在堆中获得实体。

3. null和undefined的区别

null表示空对象

  1. 表示空对象,作为对象原型链的终点
  2. 主要赋值给可能返回对象的变量,作为初始化

undefined表示缺少值

  1. undefined在JS中不是一个保留字,所以可以用undefined作为变量名,但是这种做法是不可取的,我们可以通过void 0来返回一个安全的undefined值
  2. 变量声明未赋值,为undefined
  3. 调用函数是,函数的参数未提供,为undefined
  4. 对象没有赋值的属性,为undefined
  5. 函数没有返回值时,默认返回undefined

2. instanceof操作符

1. 使用场景

instanceof 运算符用于判断构造函数的prototype属性是否出现在某个实例对象的原型链上

// A instanceof B  A是B
// 狗是动物
function Dog(){}
const d = new Dog()
d instanceof Dog // true Object.getPrototypeOf(d) === Dog.prototype
d instanceof Object // true d.__proto__.__proto__ = Object.prototype

2 具体实现:手写instanceof

// 1.
function instanceOf(instance, fn) {
  let proto = instance.__proto__;
  let type = fn.prototype;
  while(true) {
    if(proto === null) {
      return false;
    }
    if(proto === type) {
      return true;
    }
    proto = proto.__proto__;
  }
}
// 2.
function myInstanceOf(left, right) {
    if !(left && right) return false
    // Object.getPrototypeOf() 返回指定对象的原型
    let pto = Object.getPrototypeOf(left)
    left prototype = right.prototype
    // 判断一个对象是否在另一个对象的原型链上
    if !(pto && prototype) return false
    return prototype.isPrototypeOf(pto)
}

3. instanceof和多全局对象?

在浏览器中,脚本有可能需要在多个窗口之间进行交互。多窗口意味着多个全局环境,不同的全局环境有不同的全局对象,从而拥有不同的内置类型构造函数,这会导致一些问题:

[] instanceof window.frames[0].Array // 返回false
Array.prototype !== window.frames[0].Array.prototype

3. isNaN和``Number.isNaN`函数的区别

  • 函数isNaN接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的值,都会返回true,因此非数字值传入也会返回true,影响NaN的判断
  • Nmuber.isNaN会首先判断传入参数是否为数字,如果是数字再判断,不会进行数据类型的转换。这种方法判断更准确

4. new的实现原理,如何实现一个new

1. new的定义

new 运算符创建一个用户自定义的对象类型的实例或具有构造函数的内置对象的实例。

2. new关键字做了4件事

  1. 创建一个新的空对象
  2. 设置原型,将对象的原型设置为函数的prototype对象
  3. 让函数的this指向这个对象,执行构造函数的代码
  4. 函数的返回值类型,如果是基础值,返回创建的对象,如果是引用类型,就返回这个引用类型的对象

3. 手动实现new

// Object.create()方法创建一个新对象,新对象的__proto__指向括号内部的对象
function create() {
    // 获取构造函数
    const Con = Array.prototype.shift.call(arguments)
    // 创建一个指向构造函数原型的对象
    const obj = Object.create(Con.prototype)
    // 绑定this实现继承,给obj添加属性构造函数中的属性
    const res = Con.apply(obj, arguments)
    // 优先返回构造函数返回的对象
    return res instanceof Object? res : obj
}

function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.sayHi = function() {
    console.log('Hi')
}
const temp = create(Person, 'lingchen', 18)
console.log(temp)
temp.sayHi()

new.target是什么东西,如何使用

new.target属性允许你监测函数或者构造函数是否通过new操作符被调用的。在通过new运算符被初始化的函数或者构造函数中,new.target返回一个指向构造方法或函数的引用,在普通的函数调用中,返回undefined

function User(name, age) {
    console.log(new.target)
}
const u = new User('whisper', 18) // function User(){...}

User('whisper', 18) // undefined

5. call apply bind三者的区别

call 和 apply的区别就是call方法接收的是参数列表,而apply接收的是一个参数数组

  • call的性能要由于apply,因为call方法传递的参数正是我们需要的格式。

  • call() 该方法使用一个指定的this值,和单独给出的一个或多个参数来调用一个函数

    call()方法接收的是一个参数列表

    // 使用call方法调用父构造函数
    function Product(name, price) {
    	this.name = name;
    	this.price = price;
    }
    function Food(name, price) {
    	Product.call(this, name, price);
    	this.category = 'food';
    }
    console.log(new Food('milk', 5).name);
    
  • apply() 方法调用一个具有给定this值得函数,以及一个数组(或类数组对象)形式提供的参数

    let array = ['a', 'b'];
    let elements = [0, 1, 2, 3];
    array.push.apply(array, elements)
    console.log(arr);
    
  • bind() 方法创建一个新的函数,在bind()被调用时,这个新函数的this被指定为bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

  • 返回值 返回一个原函数的拷贝,被预置入绑定函数的参数列表中的参数

    function test(a, b) {
    	return a + b;
    }
    let fn = test.bind(null, [1,2]);
    let res = fn();
    

6. 原型和原型链的理解

1. 规则

  1. 所有的对象都是通过new函数创建的

  2. 所有的函数都是通过``new Function 创建的`

  3. Function是一个函数,是直接放到内存里,不是new出来的,所以Function的隐式原型指向自己的原型,Function.prototype也是一个对象,所以Function.prototype.__proto__指向Object.prototype

    Function.__proto__ === Function.prototype
    Function.prototype.__proto__ === Object.prototype
    
  4. 所有的函数也是对象,可以具有属性

  5. 所有对象都是引用类型

2. 函数原型

  1. 所有函数都具有一个``prototype`属性,称为函数原型

  2. 默认情况下,prototype就是一个普通的Object对象

  3. 默认情况下,prototype中有一个属性``constructor`,它是一个对象,指向函数本身

    Object.prototype.constructor === Object // true
    

3. 隐式原型

  1. 所有的对象都有一个属性__proto__,称为隐式原型

  2. 实例对象的__proto__指向创建该实例构造函数的prototype

    function Person(name){
    	this.name = name
    }
    const p = new Person("法外狂徒")
    p.__proto__ === Person.prototype // true
    
  3. 问:已知实例对象,如何得到创建该对象的构造函数名称?

    const name = obj.__prototype__.constructor.name
    

4. 原型链概念

  1. 每一个对象都有自己的原型__proto__,而原型也是对象,也会有自己的原型,依次类推,形成原型链。

  2. Object的原型的隐式原型指向null,即原型链的最顶端

    Object.prototype.__proto__ === null
    
  3. 对象访问某个属性:就近原则

5. 原型链追溯

// 1. 对于创建的对象
function Person() {}
const p = new Person()
p.__proto__ => Person.prototype
Person.__proto__ => Object.prototype
Object.prototype.__proto__ => null
// 2. 对于创建的函数
function User(){}
User.__proto__ => Function.prototype
Function.prototype.__proto__ => Object.prototype
Object.prototype.__proto__ => null
// Q1
function User(){}
User.prototype.sayHello = function() {}
const u1 = new User()
const u2 = new User()

console.log(u1.sayHello === u2.sayHello) // true
console.log(User.prototype.constructor) // User
console.log(Function.__proto__ === Function.prototype)  // true
console.log(User.__proto__ === Function.prototype) // true
console.log(User.__proto__ === Function.__proto__) // true
console.log(u1.__proto__ === u2.__proto__) // true
console.log(u1.__proto__ === User.__proto__) // false
console.log(Function.__proto__ === Object.__proto__) // true
console.log(Function.prototype.__proto__ === Object.prototype.__proto__) // false
console.log(Function.prototype.__proto__ === Object.prototype) // true

// Q2
function F() {}
Object.prototype.a = 'abc'
Function.prototype.b = 'bbb'

var f = new F();
console.log(f.a)	// 'abc'
console.log(f.b)	// undefined
console.log(F.a)	// 'abc'
console.log(F.b)	// 'bbb'

7. 什么是闭包?闭包的作用是什么?有什么缺点

1. 什么是闭包

内部函数被保存到外部,此时内部函数在外部执行的时候,可以访问内部的变量,形成闭包。

防止变量污染,可以用来封装实现属性或者方法的私有化。

2. 闭包的使用场景

  • 私有化变量
  • 缓存

3. 闭包的缺点

  • 闭包会导致原有作用域链不释放,造成内存泄漏

8. 说说你对JS的执行上下文和作用域链的理解

  • 作用域和执行上下文的区别

    javascript的执行分为解释和执行两个阶段:

    解释阶段:

    1、词法分析

    2、语法分析

    3、作用域规则确定

    执行阶段:

    1、创建执行上下文

    2、执行函数代码

    3、垃圾回收

    javascript在解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定。而执行上下文是函数执行之前创建的。执行上下文就是this的指向是执行时确定的。

    执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变。

9. 作用域与作用域链

  • [[scope]]:每个js函数都是一个对象,对象中有些我们可以访问,有些不可以,这下属性仅供JS引擎存取,[[scope]]就是其中一个。

    [[scope]]指的就是作用域,其中存储了执行上下文的集合。

  • 作用域链:[[scope]]中存储了执行上下文对象的集合,这个集合呈链式链接,称之为作用域链

  • 执行上下文:当函数执行之前,会创建一个执行上下文的对象。执行上下文定义了函数执行的环境,函数每次执行对应的执行上下文都是独一无二的,多次调用一个函数会创建多个执行上下文,当函数执行完毕,它产生的执行上下文立即被销毁。

  • 查找变量:会从作用域链的顶端依次向下查找

  • 作用域有三种:

    1. 全局作用域
    2. 函数作用域
    3. 块级作用域
    var c = 3
    function a(){
        var a = 1
        function b() {
            var b = 2
       		console.log(a, b, c) 
        }
        b()
    }
    // 当函数b执行时,b的作用域链:[[scope]]
    // 0 -------> b函数产生的AO对象,包含变量b的定义
    // 1 -------> a函数产生的AO对象,包含变量a的定义
    // 2 -------> 全局AO对象,包含变量c的定义
    // 打印abc变量时,通过作用域链,分别拿到abc变量
    

10. 预编译

1. 预编译四部曲:

  1. 创建AO对象
  2. 找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
  3. 将实参值和形参统一
  4. 在函数体里面找函数声明,值赋予函数体

2. 应用

 console.log(zhaowa)
   function zhaowa() {
   	this.course = 'this'
   }
   zhaowa = 'course'
   // 打印的是函数体
       
   function foo() {
   	console.log('zhaowa', zhaowa)
       function zhaowa() {
           this.course = 'this'
       }
       zhaowa = 'course'
   }
   foo() // 打印的函数体

11. 判断一个变量是什么类型

- isNAN()全局方法,用于判断一个变量是否能被Number成功转化(涉及类型转化),本身不是NaN但不能被转化成功的变量,其返回结果也是true。
- ES6在Number对象上提供了isNaN()方法,用于判断变量本事是否是NaN,不涉及类型转换。
利用typeof只能判断正常的string、number、undefined、symbol类型的变量,例如:
let a = 123; 		let b = 'abc'; 
let c = undefined; 	let c = Symbol('haha');
而typeof null是object, typeof NaN是number

/**
 * 用于判断当前变量属于什么类型
 * @param {Object} val
 */
function variableType(val){
    // 首先判断是否为NaN
    let type = '';
    if( Number.isNaN(val) ) {
    	type = 'NaN';
    }else {
    	type = Object.prototype.toString.call(val);
	}
    	return type;
}

12. 事件捕获、事件冒泡

事件流描述的是从页面接受事件的顺序。
 事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。一般情况下捕获阶段不会涉及事件目标,但IE9、Safari、Chrome、Firefox和Opera9.5及更高版本都会在捕获阶段触发事件对象上的事件。
 事件监听和移除事件监听分别是:addEventListener()、removeEventListener(),所有DOM节点都包含这两个方法,接受三个参数分别是:处理事件名,事件处理函数和布尔值。如果为true,则在捕获阶段调用处理函数,如果为false,则在冒泡阶段调用处理函数,大多数情况下都是将事件处理程序放在冒泡阶段。

13. "use strict"的作用是什么?有什么优缺点

use strict是ES5特性,它使我们的代码在函数或整个脚本中处于严格模式。严格模式邦之我们在代码的早期避免bug,增加限制。

严格模式下的限制:

  1. 变量必须先声明后使用
  2. 函数的参数不能有同名属性,否则报错
  3. 不能使用with语句
  4. 不能只读属性赋值,否则报错
  5. 不能使用前缀0标识八进制树,否则报错
  6. 不能删除不可删除的属性,某则报错
  7. 禁止this执行全局对象

严格模式的目的:

  1. 消除JavaScript语法的一些不合理不严谨之处,减少怪异行为
  2. 消除代码运行的一些不安全之处,保证代码运行的安全
  3. 提高编译器效率和运行速度
  4. 为未来新版的JavaScript做好铺垫

14. == 操作符的强制类型转换规则

15. 类型转换

  • javascript 允许数组与字符串之间相互转换。其中Array定义了3个方法,可以把数组转换成字符串:

    1. toString()

      toString()在把数组转换成字符串时,首先将数组的每个元素都转换为字符串,然后进行逗号分隔。

      对于多维数组js会以迭代的方式调用toString()

    2. toLocalString()

      将数组转换成本地约定的字符串,与toString()基本一致。

      主要区别是toLocalString()能使用用户所在地区特定的分隔符,连接生成字符串。

    3. join() 将数组链接起来构建一个字符串

  • null转为数值时为0, undefined转为数值为NaN

    var b = 1;
    function outer() {
        var b = 2;
      function inner(){
        	b++;
      	console.log(b);
    		var b = 3;
    	}
    	inner();
    }
    outer();
    
    null + 1 // 1
    undefined + 1 // NaN
    
  • 类型转换面试题

    var bar = true;
    console.log(bar + 0);	// 1
    console.log(bar + "xyz") // truexyz
    console.log(bar + true);	// 2
    console.log(bar + false); // 1
    console.log('1' > bar); // false
    console.log(1+'2' +false); // 12false
    console.log('2' + ['kak', 1]); // 2kak,1
    var obj = {a:1, b:2}
    console.log('2' + obj); //2[object Object]
    var obj2 = {
        toString:function() {
            return 'a';
        }
    }
    
    console.log('2' + obj2); // 2a
    

16. IIFE(立即执行函数)

  • 在定义时就会执行的函数,称为立即执行函数(IIFE)

  • 这个匿名函数拥有独立的作用域,可以避免外界访问,不会污染全局作用域

  • 应用:

    1. 代码在页面加载过程中,有些工作只需要执行一次,所以没必要创建一个具体的函数,代码需要一些临时变量,初始化之后就不需要了,这时候就可以采用立即执行函数。
  • (function(){
        var a = 1
        console.log(a)
    }())
    // 只有表达式才能被执行符号执行
    // 1.
    var test = function(){
        console.log(123)
    }()
    // 2.此时为函数声明,会报语法错误
    function test(){
        console.log(124)
    }()
    // 3. 会变成表达式,可以执行
    +function test(){
        console.log(124)
    }()
    

17. delete操作符

delete操作符用于删除对象的某个属性;

如果没有指向这个属性的引用,那它最终会被释放。

返回值:

​ 对于所有情况都是true,除非属性是一个自身不可配置的属性,在这种情况下,非严格模式返回false。

  • 如果你试图删除的属性不存在,那delete不起任何作用,但是会返回true

  • delete操作只会在自身的属性上起作用(不能操作原型上的属性)

  • 任何使用var声明的属性不能从全局作用域或函数的作用域中删除

    • delete不能删除任何在全局作用域中或函数作用域中的函数

    • 在对象中的函数是可以被删除的

  • 任何使用let后const声明的属性不能从它被声明的作用域中删除。

  • 不可设置的属性不能被移除。像Math,Array,Object内置对象的属性,以及使用Object.defineProperty()方法设置为不可设置的属性不能被删除

总结:

  1. 使用var、let、const声明的属性都不能从它被声明的作用域中删除。
  2. 未声明直接定义的变量可以删除(自动到window作用域下)
  3. 对象中的属性(包括函数)可以被删除,前提是属性可设置
var a = 1;
let b = 2;
const c = 3;
d = 4;
console.log(delete a); //false
console.log(delete b); //false
console.log(delete c); //false
console.log(delete d); // true

18. 类数组的定义?常见的类数组?类数组转为数组的方法有哪些?

1. 类数组的定义

类型化数组,具有length属性

常见的类数组有:元素检索API返回的都是类数组,function中的arguments

2. 如何将类数组转化为数组:

  1. Array.from
  2. Array.prototype.slice.call(类数组)
  3. 循环遍历放入数组中
  4. Array.prototype.map
  5. Array.prototype.concat.apply([], 类数组)

19. AJAX是什么,原理是什么,如何手动实现

  1. 创建一个XMLHttpRequest对象,也就是创建一个异步调用对象
  2. 创建一个新的HTTP请求,指定该请求的方法、URL、以及验证信息
  3. 设置响应HTTP请求状态变化的函数
  4. 发送HTTP请求
  5. 获取异步调用返回的数据
  6. 使用JS和DOM实现局部刷新
// 1. 创建XMLHttpRequst对象
// 2. 调用open方法,传入method、url、isAsync
// 3. 监听readState和status是否分别为4和200, 获取responseText数据,执行回调
// 4. 调用send方法,如果有参数传入参数
const AJAX = {
    get: function(url, cb) {
        const xhr = new XMLHttpRequest()
        xhr.open('GET', url, true)// 第三个参数是否为异步
        xhr.onreadystatechange(function(){
            if(xhr.readyState !== 4 && xhr.status === 200) {
                cb(xhr.responseText)
            }
        })
        xhr.send()
    },
    post: function(url, data, cb) {
        const xhr = new XMLHttpRequest()
        xhr.open('POST', url, true)// 第三个参数是否为异步
        xhr.setRequestHeader('Content-Type', "application/x-www-form-urlencoded")
        xhr.onreadystatechange(function(){
            if(xhr.readyState !== 4 && xhr.status === 200) {
                cb(xhr.responseText)
            }
        })
        xhr.send(data)
    }
}

使用promise封装ajax

function getData(url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        xhr.open('GET', url, true)
        // xhr.setRequestHeader("Content-Type", "applicattion/x-www-form-urlencoded")
        xhr.onreadystatechange = function() {
            if(xhr.readyState !== 4) return
            if(xhr.status === 200) {
                resolve(xhr.responseText)
            } else {
                reject(new Error(xhr.status))
            }
        }
        xhr.send()
    })
}

20. 什么是防抖和节流?有什么区别?怎么实现?具体适用场景?

函数防抖:停止操作后一段时间调用函数 debounce 比如input

函数节流: 同一时间段只调用一次改函数 throttle,比如resize,scroll

// 防抖
function debounce(fn, delay) {
    let timer = null;
    return function() {
    	let context = this;
        let args = arguments;
        if(timer) {
        	clearTimeout(timer);
        }
        timer = setTimeout(() => {
          	fn.apply(context, args);
        }, delay);
    }
}

// 节流
//1. 时间戳的写法 第一次立即执行
function throttle(fn, interval) {
    let last = 0;
    return function() {
        let now = Date.now();
        if(now - last >= interval) {
            last = now;
            fn.apply(this, arguments);
        }
    }
}

// 第一次延迟执行的写法 定时器
function throttle (fn, interval) {

    let timer = null;
    return function() {
        let context = this;
        let args = arguments;

        if(!timer) {
            timer = setTimeout(() => {
                fn.apply(context, args)
                timer = null;
            }, interval);
        }
    }
}

21. sort排序问题

使用sort对数组[3, 15, 8, 29, 102, 22]进行排序,输出结果

let arr = [3, 15, 8, 29, 102, 22]
arr.sort()
console.log(arr) // [102, 15, 22, 29, 3, 8]

sort方法默认排序的顺序是将元素转换为字符串,然后比较UTF-16代码单元值序列

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值