JavaScript高频面试题

文章目录

数据类型

什么是基本数据类型?什么是引用数据类型?以及各个数据类型是如何存储的?(5星)

JS内存和其他语言的差别
在其他常见的语言中,用来保存变量和对象的内存一般分为栈内存和堆内存,而在JavaScript中,所谓的栈和堆都是放在堆内存中的,而在堆内存中,JS把其分为栈结构和堆结构,这里常被误认为是堆内存和栈内存,但是我们可以把它简称为栈和堆。
在这里插入图片描述
栈和堆的特点和区别
栈(stack) :栈会自动分配内存空间,会自动释放,存放基本类型引用数据类型的变量
所有在函数/方法中定义的变量都是放在栈内存中,随着函数/方法的执行结束,这个函数/方法的内存栈也自然销毁。
优点:存取速度比堆快。 缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
堆(heap) :动态分配的内存,大小不定也不会自动释放,存放引用类型的值,指那些可能由多个值构成的对象,保存在堆内存中。
堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(参数传递)。创建对象是为了反复利用。

基本数据类型 数据直接存储在栈中,按值进行存储和访问的,每个变量都有自己的内存空间和值。当你将一个基本数据类型的值赋给另一个变量时,会创建一个新的值并将其复制到新变量中。因此,基本数据类型的变量之间是相互独立的,修改其中一个变量的值不会影响其他变量。基本数据类型包括:

  • 布尔值(Boolean):表示真或假。
  • 数字(Number):表示数字,包括整数和浮点数。
  • 字符串(String):表示文本。
  • undefined:表示未定义的值。
  • null:表示空值。
  • Symbol(ES6新增):表示一种唯一的、不可变的值。

基本数据类型被保存在栈结构中,在栈结构中会有全局执行环境(也称全局执行上下文),在全局作用域中的基本数据类型就是被保存全局执行环境中的。

var a = 10;
var b = a;
b = 20;

在这里插入图片描述

首先var 定义的变量a 加入到全局执行环境中,并且赋值为10,然后定义变量b并且把a的值赋值给变量b,此时b的值为10,最后b的值被修改为20,最终的执行结果为a输出为10,b输出为20
注:实际上是有变量提升,先定义后赋值,不过理解存储原理变量提升可以暂且忽略

引用数据类型栈中仅保存数据的引用地址,数据存储在堆中,通过引用地址访问数据。栈就是一本书的目录,堆是目录对应的内容。复杂数据类型包括:

  • 对象(Object):表示键值对的集合。
  • 数组(Array):表示有序的值的列表。
  • 函数(Function):表示可执行的代码块。

当你将一个复杂数据类型的值赋给另一个变量时,实际上是将引用地址复制给了新变量。这意味着两个变量指向同一个对象(或数组、函数)在内存中的位置。因此,改变其中一个变量所指向对象的属性或元素,另一个变量也会反映出这些改变,因为它们引用的是同一个对象。

引用类型是存放在堆内存中的对象,变量其实是保存的在栈内存中的一个指针(保存的是堆内存中的引用地址),这个指针指向堆内存。
引用类型数据在栈内存中保存的实际上是对象在堆内存中的引用地址。通过这个引用地址可以快速查找到保存中堆内存中的对象。
对比上一段代码,如果我们把10和20换成一个数组又会发生什么呢?

var arr = [1,2,3];
var arr1 = arr;
arr1[1] = 22;

在这里插入图片描述

首先定义了arr变量,数组也是一个对象,所以在栈结构的全局执行环境中我们保存的实际上是一个地址值(指针),而真正的数组是被保存在了堆结构中。保存对象首先会在堆结构中开辟一块内存空间,然后把地址值赋值给变量,栈结构中只保存对象的地址值。这里我们可以看到arr的保存的地址值赋值给了arr1,所以此时arr和arr1都指向了同一个堆结构中的对象,此时我们通过数组的索引arr[1]去修改第二个元素为22,无论我们通过arr还是arr1访问这个数组,实际上都是在访问相同的对象。所以我们两次打印输出的结果都是[1,22,3]。

数据类型 USONB 你很牛逼 U:undefined S:string symbol O:object N:number null B:boolean

JS有哪些内置对象

基本对象Object,内置对象有Array Math Number String Date JSON

类型转换

为什么typeof null是Object?(4星)

  • 因为在JavaScript中,如果二进制前三位都是0的话,系统会判断为是Object类型,而null的二进制全是0,自然也就判断为Object

判断数据类型有几种方法 ?(5星)

  • typeof

    • 缺点:typeof null的值为Object,无法分辨是null还是Object
  • instanceof 判断一个实例是否属于某种类型
    Object instanceof Object //true

    • 缺点:只能判断对象是否存在于目标对象的原型链上,不能判断字面量的基本数据类型
  • Object.prototype.toString.call()

    • 一种最好的基本类型检测方式 Object.prototype.toString.call() ;它可以区分 null 、 string 、boolean 、 number 、 undefined 、 array 、 function 、 object 、 date 、 math 数据类型。

    • 缺点:不能细分为谁谁的实例

      // -----------------------------------------typeof
        typeof undefined // 'undefined' 
        typeof '10' // 'String' 
        typeof 10 // 'Number' 
        typeof false // 'Boolean' 
        typeof Symbol() // 'Symbol' 
        typeof Function // ‘function' 
        `typeof null // ‘Object’ `
        typeof [] // 'Object' 
        typeof {
         
         } // 'Object'
        // -----------------------------------------instanceof
        function Foo() {
         
          }
        var f1 = new Foo();
        var d = new Number(1)
        console.log(f1 instanceof Foo);// true
        console.log(d instanceof Number); //true
        console.log(123 instanceof Number); //false   -->不能判断字面量的基本数据类型
        // -----------------------------------------constructor
        var d = new Number(1)
        var e = 1
        function fn() {
         
         
          console.log("ming");
        }
        var date = new Date();
        var arr = [1, 2, 3];
        var reg = /[hbc]at/gi;
        console.log(e.constructor);//ƒ Number() { [native code] }
        console.log(e.constructor.name);//Number
        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()
        console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]" 
        console.log(Object.prototype.toString.call(null)); // "[object Null]" 
        console.log(Object.prototype.toString.call(123)); // "[object Number]" 
        console.log(Object.prototype.toString.call("abc")); // "[object String]" 
        console.log(Object.prototype.toString.call(true)); // "[object Boolean]" 
        function fn() {
         
         
          console.log("ming");
        }
        var date = new Date();
        var arr = [1, 2, 3];
        var reg = /[hbc]at/gi;
        console.log(Object.prototype.toString.call(fn));// "[object Function]" 
        console.log(Object.prototype.toString.call(date));// "[object Date]" 
        console.log(Object.prototype.toString.call(arr)); // "[object Array]"
        console.log(Object.prototype.toString.call(reg));// "[object RegExp]"
    

instanceof原理(5星)

instanceof原理:判断对象是否存在于目标对象的原型链上,一层一层向上查找,一直找到null为止 扩展到原型链上
a instanceof b a为对象 b为目标对象
a是否是b的实例对象

   function Foo() {
   
    }
    var f1 = new Foo();
    console.log(f1 instanceof Foo);// true
   function myInstance(L, R) {
   
   //L代表instanceof左边,R代表右边
      var RP = R.prototype
      var LP = L.__proto__
      while (true) {
   
   
        if(LP == null) {
   
   
          return false
        }
        if(LP == RP) {
   
   
          return true
        }
        LP = LP.__proto__ 
      }
    }
    console.log(myInstance({
   
   },Object)); 

null和undefined的区别(4星)

null:

  1. 此处不应该有值
  2. typeof null = object
  3. 转为数值为0
  4. 作为函数的参数,表示该函数的参数不是对象;作为对象原型链的终点

undefined:

  1. 此处应该有一个值但是还没有定义
  2. typeof undefined = undefined
  3. 转为数值为NaN
  4. 例如变量被声明了但没有赋值,就等于undefined;函数没有返回值默认返回undefined;对象没有赋值的属性,该属性的值为undefined

=====有什么区别?(5星)

最大的区别在于,全等号不执行类型转换。
相等是经过类型转换数值相等 返回true
全等号由三个等号表示(===),只有在无需类型转换运算数就相等的情况下,才返回 true

非全等号由感叹号加两个等号(!==)表示,只有在无需类型转换运算数不相等的情况下,才返回 true。

==是非严格意义上的相等,

  • 先检查数据类型是否相同

  • 如果相同,则比较两个数是否相等

  • 如数据类型类型不同,则先转换成相同的数据类型,再进一步进行比较。

    • Null == Undefined ->true
    • String == Number ->先将String转为Number,在比较大小
    • Boolean == Number ->先将Boolean转为Number,在进行比较
    • Object == String,Number,Symbol -> Object 转化为原始类型
      ===是严格意义上的相等,会比较两边的数据类型和值大小
  • 先检查的数据类型是否相同

  • 若相同,则比较二者是否相等,若相等返回true

  • 若不同,直接返回false

1=='1'//ture
1==[1]//true
1==false//false
1==true//true

1==='1'//false
1===[1]//false

//特例
null==undefined //true
null===undefined //false
NaN==NaN //false
NaN===NaN //false
//NaN(not a number)是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数未返回数值的情况,避免报错。NaN与任何值都不相等,包括NaN自身。

判断是否为整数(4星)

1、ES6 Number.isInteger()

Number.isInteger(3) //true
Number.isInteger(3.2) //false

2、Math.ceil()、Math.floor()、Math.round()方法

Math ceil()对一个数进行上取整 ceil天花板
Math.floor()对一个数进行下取整 floor地板
Math.round()四舍五入取整

3、使用取余的方式,任意整数取1的余数都是0

js 具有类型自动转换的效果,先判断数据类型是否是 number,然后再判断取 1 的余数是否等于 0.

function isInteger(num) {
   
   
  return typeof num === 'number' && num % 1 === 0
}

3、Math.floor

手写call、apply、bind (5星)

call、apply、bind都是改变this指向的方法
call

fun. call (thisArg, argl, arg2, ..) 
thisArg:在fun函数运行时指定的this值arg1,arg2:传递的其他参数

call可以直接调用函数,可以改变函数内的this指向  
call的主要作用可以实现继承
 var o = {
    name: 'andy'
  }
  function fn(a, b) {
    console.log(this)//原本this指向window 当执行fn.call后指向o
    console.log(a + b)
  }
  fn.call(o, 1, 2)

bind
bind:语法和call一模一样,区别在bind不调用函数

fn.call(obj, 1, 2); // 改变fn中的this,并且把fn立即执行
fn.bind(obj, 1, 2); // 改变fn中的this,fn并不执行 等待事件触发

apply
和call基本上一致,唯一区别在于传参方式
apply把放到一个数组(或者类数组)中传递进去

fn.call(obj, 1, 2);
fn.apply(obj, [1, 2]);

因为我们需要让所有的地方都可以去访问到,所以我们把自定义的函数写在Function的原型上,因为所有的函数 instanceof Function都是会返回true的,所以定义在Function的prototype上call和bind我们接收第二个以后的参数,所以可能会用到slice方法,而apply直接接受第二个参数就行了。那么如何显式的绑定this呢,如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。拿取参数的话,我们还是用最简单的方式arguments,没有参数的情况下我们就直接将函数无参返回,如果没有传入对象的话直接指向window,这样的话我们的手写函数就和原来的call,apply和bind没什么两样了。

Function.prototype.MyCall = function(context){
//直接将函数放在Function的prototype上,方便调用
		const ctx = context || window;
//这里是将传入对象存到ctx里边,如果没有传入的话就指向window,因为原来的call也是这样的
		ctx.func = this;
//改变this的指向,换句话说相当于是把这个函数直接给到传入的对象
		const args = Array.from(arguments).slice(1);
//这里就是要去拿到函数的参数,把类数组转为数组之后,减去第一个,给到args备用
		const res = arguments.length > 1 ? ctx.func(...args) : ctx.func();
//就是说如果有参数,把args解构传进去,没有的话就不传了
		delete ctx.func;
//这个时候的func已经没有用了,直接返回res
		return res
//一步一注释应该是简单易懂的
	}

apply跟call是一样的,只是参数的处理简单一点,就不一步一注释了。

Function.prototype.MyApply = function(context){
	const ctx = context || window;
	ctx.func = this;
	const res = arguments[1] ? ctx.func(...arguments[1]):ctx.func();
	delete ctx.func;
	return res
}

bind有所不同,bind函数返回一个绑定函数,最终调用需要传入函数实参和绑定函数的实参。如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。

Function.prototype.MyBind = function(context){
	console cxt = JSOn.parse(JSON.stringify(context)) || window;
	cxt.func = this;
	const args = Array.from(arguments).slice(1);
	return function(){
		const allArgs = args.concat(Array.from(arguments));
		return allArgs.length > 0 ? cxt.func(...allArgs):cxt.func();
	}

字面量创建对象和new创建对象有什么区别?手写一个new ?new时内部发生了什么?(5星)

区别:

字面量:

var obj = {
    name: "小明",
    sayHi:function () {
        console.log("我是:" + this.name);
    },
   }
  • 字面量创建对象更简单,方便阅读
  • 不需要作用域解析,速度更快

new:创建对象的过程复杂,过程是这样的

  • 创建一个新对象
  • 使新对象的__proto__指向原函数的原型对象
  • 改变this指向,指向新的对象并执行该函数,执行结果保存起来作为result
  • 判断执行函数的结果是不是null或Undefined,如果是则返回之前的新对象,如果不是则返回之前的执行结果result

手写new

    // 手写一个new
       var obj = new fn()
    function myNew(fn, arg) {
   
   
      // 创建一个空对象
      let obj = {
   
   }
      // 使空对象的隐式原型指向原函数的显式原型
      obj.__proto__ = fn.prototype
      // this指向obj
      let result = fn.call(obj, arg)
      // 返回
      return result instanceof Object ? result : obj
    }
        console.log(null instanceof Object)//false
        console.log(undefined instanceof Object)//false

字面量和new出来的对象和 Object.create(null)创建出来的对象有什么区别 ?(3星)

  • 字面量和new创建出来的对象会继承Object的方法和属性,他们的隐式原型会指向Object的显式原型,
  • Object.create(null)创建出来的对象原型为null,作为原型链的顶端,自然也没有继承Object的方法和属性

原型链和原型

什么是原型?什么是原型链?如何理解?(5星)

原型: 原型分为隐式原型(__proto__属性)和显式原型(原型对象prototype),每个对象都有一个隐式原型,它指向自己的构造函数的显式原型。

原型链: 多个__proto__组成的集合成为原型链

实例对象的__proto__指向构造函数原型对象

构造函数原型对象的__proto__指向Object原型对象

Object的原型对象__proto__指向null

每一个构造函数都有原型对象prototype,把所有不变的的方法都直接定义在原型对象上,然后从构造函数中new出来的实例对象就可以共享这些方法
实例对象都会有__proto__属性,指向构造函数的原型对象prototype,之所以实例对象可以使用构造函数原型对象的属性和方法,就是因为对象有__proto__属性的存在。

构造函数.prototype === 实例对象.__proto__

原型对象中有一个constructor属性,指向原来的构造函数

示意图:

在这里插入图片描述

JavaScript的成员查找机制(3星)

1.当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。

2.如果没有就查找它的原型(也就是__proto__指向的构造函数的原型对象)

3.如果还没有就查找原型对象的原型(即Object的原型对象)

4.依此类推一直找到Object为止(Object的原型对象__proto__=>查找机制的终点为null).

5.__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。

执行栈和执行上下文

什么是作用域,什么是作用域链? (4星)

  • 规定变量和函数的可使用范围称为作用域
  • 查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作用域链。作用域链就是代码块内有代码块,跟常规编程语言一样,上级代码块中的局部变量下级可用
  • 作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止

什么是执行上下文,什么是执行栈?(4星)

执行上下文(也就是代码的执行环境)

  • 全局执行上下文

    创建一个全局的window对象,并规定this指向window,执行js的时候就压入栈底,关闭浏览器的时候才弹出

  • 函数执行上下文

    每次函数调用时,都会新创建一个函数执行上下文

  • eval执行上下文

    eval的功能是把对应的字符串解析成JS代码并运行

在这里插入图片描述
执行栈:具有先进后出结构,用于存储代码执行期间创建的所有执行上下文

  • 当进入一个执行环境,就会创建出它的执行上下文,然后进行压栈,当程序执行完成时,它的执行上下文就会被销毁,进行弹栈
  • 栈底永远是全局环境的执行上下文,栈顶永远是正在执行函数的执行上下文
  • 只有浏览器关闭的时候全局执行上下文才会弹出

闭包

什么是闭包?闭包的作用?闭包的应用? (5星)

闭包(Closure)是指在函数内部创建的函数,它可以访问并持有该函数所在的环境中的变量,即使在外部函数执行结束后,闭包仍然可以访问这些变量。
内层作用域下可以获取外层作用域下的变量,而外层作用域下无法获取内层作用域下的变量。而闭包的出现,就解决了这个问题

(函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!)

特性:
函数内再嵌套函数
内部函数可以引用外层的参数和变量
参数和变量不会被垃圾回收机制回收
作用
能够实现封装和缓存,避免全局变量的污染

缺点:
消耗内存、不正当使用会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
解决方法是,在退出函数之前,将不使用的局部变量全部删除
应用

  • 防抖和节流

  • 封装私有变量

  • for循环中的保留i的操作

  • JS设计模式中的单例模式

    单例模式:用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象

  • 函数柯里化

基本步骤分析:

  1. 定义外部函数,要保护的变量以及可外部访问的内部函数。通过内部函数操作受保护变量,并将内部函数返回。
  2. 调用外部函数获取内部函数,然后基于内部函数操作受保护变量。
    function outer() {
   
   //外部函数
      var count = 0; //受保护变量(这个变量外部不可直接使用)
      return function () {
   
   //内部函数
        count++; //通过内部函数操作受保护变量
        console.log(count);
      };
    }//上面整个函数就是闭包设计
    var inner = outer(); //调用外部函数获取内部函数
    //匿名函数作为outer的返回值被赋值给了inner相当于inner=function () { count++;  console.log(count);} 
    //匿名函数被赋予全局变量,并且匿名函数内部引用着outer函数内的变量count,所以变量count无法被销毁

    inner(); //调用内部函数操作受保护变量

1.闭包作为返回值

在这里插入图片描述
在这段代码中,a()中的返回值是一个匿名函数,这个函数在a()作用域内部,所以它可以获取a()作用域下变量name的值,将这个值作为返回值赋给全局作用域下的变量b,实现了在全局变量下获取到局部变量中的变量的值

在这里插入图片描述
一般情况下,在函数fn执行完后,就应该连同它里面的变量一同被销毁,但是在这个例子中,匿名函数作为fn的返回值被赋值给了fn1,这时候相当于fn1=function(){var n = 0 … },并且匿名函数内部引用着fn里的变量num,所以变量num无法被销毁,而变量n是每次被调用时新创建的,所以每次fn1执行完后它就把属于自己的变量连同自己一起销毁,于是乎最后就剩下孤零零的num,于是这里就产生了内存消耗的问题
return function 只要函数遇见return就把后面的结果返回给函数的调用者

2.闭包作为参数

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值