前端js面试题

javascript原型与原型链

目录

javascript原型与原型链

constructor,__proto__,prototype的三角关系

 作用域和自由变量

闭包和this

2. this

js异步之宏任务(marcroTask)和微任务(microTask)

什么是宏任务和微任务


1. prototype
每个函数都有一个prototype属性,被称为显示原型

2._ _proto_ _
每个实例对象都会有_ _proto_ _属性,其被称为隐式原型

每一个实例对象的隐式原型_ _proto_ _属性指向自身构造函数的显式原型prototype

3. constructor
每个prototype原型都有一个constructor属性,指向它关联的构造函数。

4. 原型链
获取对象属性时,如果对象本身没有这个属性,那就会去他的原型__proto__上去找,如果还查不到,就去找原型的原型,一直找到最顶层(Object.prototype)为止。Object.prototype对象也有__proto__属性值为null。

这里需要注意的是Object是属于原型链的顶层,所有构造函数的的prototype都指向 Object.prototype

constructor,__proto__,prototype的三角关系


假设有一个构造函数Fn  一个Fn的实例化对象 obj

构造函数Fn的prototype属性指向了构造函数的原型对象Fn.prototype

实例对象obj的__proto__属性指向了构造函数的原型对象Fn.prototype

构造函数的原型对象的constructor属性又指回了构造函数Fn,

 作用域和自由变量

1.作用域:

作用域就是一个变量可以使用的范围,主要分为全局作用域和函数作用域和块级作用域

全局作用域就是Js中最外层的作用域

函数作用域是js通过函数创建的一个独立作用域,函数可以嵌套,所以作用域也可以嵌套

Es6中新增了块级作用域(由大括号包裹,比如:if(){},for(){}等)

2.自由变量

当前作用域外的变量都是自由变量,一个变量在当前作用域没有定义,但是被使用了,就会向上级作用域,一层一层依次查找,直至找到为止,找到这个变量后就会停止,不会继续查找这个变量,如果全局作用域都没有找到这个变量就会报错这个自由变量查找的过程就是作用域链

3.变量提升:

每个var声明的变量,function声明的函数存在变量提升。let const不存在变量提升

在js中声明之前未定义,会在js的最上方会形成一个预解析池,用来存储声明了但没有先定义的变量名

var声明的变量,function声明的函数存在变量提升
let const 不会变量提升

1. javascript中声明并定义一个变量时,会把声明提前,以下会先打印出undefined,再打印出10

console.log(a)
var a = 10
console.log(a)

相当于

var a
console.log(a);//undefined
a = 10
console.log(a) //10

2. 函数声明也是,以下函数相当于把整个fn提到作用域的最上面,所以调用fn时会正常打印jack

fn('jack');//jack
function fn (name){
console.log(name)
}

3. 不过函数表达式不行,以下是一个函数表达式,JavaScript会把var fn提到作用域最上面,没有吧函数提上去,所以会报错。

fn("jack");//报错
var fn = function(name) {
console.log(name);
};

下面是面试变量提升的面试笔试真题

  var x = 30;
  function test(){
      alert(x);
      var x = 10;
      alert(x);
      x=20;
      function x(){

      };
      alert(x);
  }
  test();

分析:

第一步:

test是函数,每个函数作用域也首先要进行预解析,var,function声明的变量或者函数首先变量提升,但是函数的优先级高于变量,所以预解析池最终结果为function x(){}

步骤二:

从上往下,逐步执行: 第一个alert(x)中的x向上查找,直至找到预解析池,function x(){},
第二个alert(x)中的x向上查找,找到var x=10;, 弹出 10;

第二个alert(x)中的x向上查找,找到 x=10;, 弹出 20;
 

类型判断

数据类型判断大概有四种typeof、instanceof、constructor、Object.prototype.toString.call()

1.Type:

基本数据类型中:Number,String,Boolean,undefined 以及引用数据类型中Function ,可以使用typeof检测数据类型,分别返回对应的数据类型小写字符。

另:用typeof检测构造函数创建的Number,String,Boolean都返回object

基本数据类型中:null 。引用数据类型中的:Array,Object,Date,RegExp。不可以用typeof检测。都会返回小写的object

2 . instanceof
除了使用typeof来判断,还可以使用instanceof。instanceof运算符需要指定一个构造函数,或者说指定一个特定的类型,它用来判断这个构造函数的原型是否在给定对象的原型链上。

3.constructor

constructor是prototype对象上的属性,指向构造函数。根据实例对象寻找属性的顺序,若实例对象上没有实例属性或方法时,就去原型链上寻找,因此,实例对象也是能使用constructor属性的

4 . 使用Object.prototype.toString.call()检测对象类型

可以通过toString() 来获取每个对象的类型。为了每个对象都能通过 Object.prototype.toString() 来检测,需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式来调用,传递要检查的对象作为第一个参数,称为thisArg。

闭包和this

闭包

简单的理解就是函数中套了一个函数,内层函数可以访问外层函数中的变量

有时候需要用到函数内的局部变量,在正常情况下是不能读取到的,这个时候就需要用到闭包。

闭包可以封装对象的私有属性和方法,就是可以简单的理解成闭包就是一个私有作用域,可以定义属性和方法vue中的data就是一种闭包的形式

闭包作为回调函数可以实现函数的复用

优点闭包因为长期驻扎在内存中。可以重复使用变量,不会造成变量污染

缺点闭包会使函数中的变量都被保存在内存中内存消耗很大所以不能滥用闭包否则会造成网页的性能问题可能会导致内存泄露解决方法是在退出函数之前将不使用的变量全部删除

2. this

5大调用场景:

普通函数、
对象方法、
call apply bind
class
箭头函数

  1. 普通函数中的this
  2.  function fn(){
        console.log(this);
      }
      fn();  //相当于下面的window.fn();
      window.fn();
    

    window调用了fn,所以this指向window

  3. 对象方法中出现this
  4.  let pox={
        name:'小红',
        run:function(){
          console.log(this.name)//this
        }
      } 
      pox.run();// pox 小红
    

    pox调用的run,所以run方法中的this指向pox

  5. call() /apply() /bind() 都可以改变this指向
  6.  let obj={name:'小明'}
    
      let pox={
        name:'小红',
        run:function(){
          console.log(this.name)
        }
      } 
      // 对象方法中的this.指向方法的调用者。
      pox.run();// pox 小红
      pox.run.call(obj)// 小明
      pox.run.apply(obj);// 小明
      pox.run.bind(obj)();//小明
    

    相同点:call() /apply() /bind() 都可以改变this指向,指向第一个参数
    不同点:bind()需要手动执行

  7. class中的this指向new 后的实例对象
    class Person{
       constructor(name,age){
        this.name=name;
        this.age=age
       }
       say(){
        console.log(`我叫${this.name}年龄是${this.age}`)
       }
     }

     let lsy=new Person('web',21);
     lsy.say();// 我叫web年龄是21
     console.log(lsy);// {name:'web',age:21}

class中的this指向new 后的实例对象

  1. 箭头函数中的this.指向父级上下文对象
  let obj={name:'小明'}
  var name='杨志' //不能用let声明,不存在变量提升
  let pox={
    name:'小红',
    run:()=>{
      console.log(this.name)//this
    }
  } 
  // 对象方法中的this.指向方法的调用者。
  pox.run();// 杨志
  pox.run.call(obj)// 杨志
  pox.run.ally(obj);// 杨志
  pox.run.bind(obj)();//杨志

class中的 this时刻指向父级的上下文对象。并且不可以被 call()/apply()/bind()修改。

对象方法中的this,指向当前对象(因为当前对象执行了方法)。

setTimeout函数中的this,相当于普通函数中的this,因为setTimeout触发的函数执行,并不是外部对象执行的。

setTimeout中函数是箭头函数,this为当前对象。因为箭头函数中的this始终是父级上下文中的this.

注意:

this取什么值,是在执行时确认的,定义时无法确认
 

手写bind:

// 模拟 bind
Function.prototype.bind1 = function () {
    // 将参数拆解为数组
    const args = Array.prototype.slice.call(arguments)

    // 获取 this(数组第一项)
    const t = args.shift()

    // fn1.bind(...) 中的 fn1
    const self = this

    // 返回一个函数
    return function () {
        return self.apply(t, args)
    }
}

function fn1(a, b, c) {
    console.log('this', this)
    console.log(a, b, c)
    return 'this is fn1'
}

const fn2 = fn1.bind1({x: 100}, 10, 20, 30)
const res = fn2()
console.log(res)

this总结 (重要)
普通函数中调用,this指向window
对象方法中调用,this指向当前对象
call apply bind中调用, this指向被传入的对象
class中的方法中调用, this指向实例对象
箭头函数,this就是父级上下文中的this

3. 闭包的应用场景
闭包应用场景1,封装对象的私有属性和方法

隐藏数据
做一个简单的缓存工具

// 闭包隐藏数据,只提供 API
function createCache() {
    const num=100
    const data = {} // 闭包中的数据,被隐藏,不被外界访问
    return {
        num:num,
        set: function (key, val) {
            data[key] = val
        },
        get: function (key) {
            return data[key]
        }
    }
}

const c = createCache()
console.log(c.num)//num此时就作为c私有属性
c.set('a', 100) //set此时作为c的私有方法
console.log( c.get('a') )


闭包应用场景2,闭包作用回调函数

		   <body>
			    <a href="#" id="as1">20</a>
			    <a href="#" id="as2">40</a>
			</body>
			<script>
			 function changeSize(size){
			     return function(){
			         document.body.style.fontSize=size+'px';
			     }
			 }
			 var size20=changeSize(20);
			 var size40=changeSize(40);
			
			 document.getElementById('as1').onclick=size20;
			 document.getElementById('as2').onclick=size40;
			
			</script>

闭包应用场景3,函数节流防抖

			<body>
			<!-- 函数防抖是指在函数被高频触发时当停止触发后延时n秒再执行函数,
					(即每次触发都清理延时函数再次开始计时),一般用于resize scroll,mousemove -->
			<!-- 函数节流 原理 函数被高频出发时延时n秒后才会再次执行,防抖主要是用户触发一次时间后,延迟一段时间触发,
					 而节流会规定的事件内触发一次事件 -->
			</body>
			<script>
			// 	函数节流:是确保函数特定的时间内至多执行一次。
			//  函数防抖:是函数在特定的时间内不被再调用后执行。
			//防抖
			var debounce = function(func, delay) {
			  var timer = null
			  return function() {
			      var that = this;
			      var args = arguments;
			       
			      if(timer) {
			          clearTimeout(timer);
			      }
			 
			      timer = setTimeout(function() {
			          func.apply(that, args);
			      }, delay)
			  }
			}
			 
			ipt.addEventListener('keyup', debounce(function(e){
			  console.log(e.target.value);
			}, 400))
			</script>

一、什么是闭包:
①要理解闭包,首先理解javascript特殊的变量作用域,变量的作用于无非就是两种:全局变量,局部变量。
②javascript语言的特殊处就是函数内部可以读取外部作用域中的变量。
③我们有时候需要得到函数内的局部变量,但是在正常情况下,这是不能读取到的,这时候就需要用到闭包。在javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包是指有权访问另一个函数作用域中的变量的函数。其本质是函数的作用域链中保存着外部函数变量对象的引用。

二.闭包的应用场景:
①函数作为参数被传递
②函数作为返回值被返回
③实际应用(隐藏数据):为什么说隐藏数据了呢,因为普通用户只能通过get、set等api对数据进行查看和更改等操作,没法对data直接更改,达到所谓隐藏数据的效果;jquery就利用了这一特性,必须调用$.ajax()才能访问内部属性方法。
封装功能时(需要使用私有的属性和方法),
函数防抖、函数节流
单例模式

三.闭包的优点:
(一)变量长期驻扎在内存中
(二)另一个就是可以重复使用变量,并且不会造成变量污染
①全局变量可以重复使用,但是容易造成变量污染。不同的地方定义了相同的全局变量,这样就会产生混乱。”
②局部变量仅在局部作用域内有效,不可以重复使用,不会造成变量污染。
③闭包结合了全局变量和局部变量的优点。可以重复使用变量,并且不会造成变量污染

四.闭包的缺点:
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

js异步之宏任务(marcroTask)和微任务(microTask)

什么是宏任务和微任务

宏任务包括:setTimeout setInterval Ajax DOM事件
微任务:Promise async/await
微任务比宏任务的执行时间要早

异步和单线程
异步和单线程是相辅相成的,js是一门单线程脚本语言,所以需要异步来辅助

异步和同步的区别:

异步不会阻塞程序的执行,
同步会阻塞程序的执行,
前端使用异步的场景:

定时任务:setTimeout,setInverval
网络请求:ajax请求,动态<img>加载
事件绑定
 

//ajax请求代码示例
console.log('start')
$.get('./data1.json',function(data1){
	console.log(data1)
})
console.log('end')

同步异步问题
首先,你要知道javascript是单线程语言。js任务需要排队顺序执行,如果一个任务耗时过长,后边一个任务也的等着,但是,假如我们需要浏览新闻,但新闻包含的超清图片加载很慢,总不能网页一直卡着直到图片完全出来,所以将任务设计成了两类:

同步任务
异步任务

当我们打开网站时,网页的渲染过程就是一大堆同步任务,像页面骨架和页面元素的渲染,而加载图片、音乐之类的任务就是异步任务,看一下下边导图:


同步和异步任务分别进入不同的执行“场所”,同步进入主线程,异步进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入任务队列(Event Queue)。主线程内的任务执行完毕为空,就去任务队列(Event Queue)读取对应的函数,进入主线程执行。

上述过程会不断重复,也就是常说的Event Loop(事件循环)。

但是,JS异步还有一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入任务队列(event queue),然后再执行微任务,将微任务放入微任务队列(micro task queue),但是,这两个queue不是一个queue。当你往外拿的时候先从微任务队列里拿这个回调函数,然后再从宏任务的queue拿宏任务的回调函数,如下图:


 

任务列队和event loop(事件循环)
单线程就意味着,所有的任务都要排队,前一个结束,才会执行后面的任务。如果列队是因为计算量大,CPU忙不过来,倒也算了。但是更多的时候,CPU是闲置的,因为IO设备处理得很慢,例如
ajax读取网络数据。js设计者便想到,主线程完全可以不管IO设备,将其挂起,然后执行后面的任务。等后面的任务结束掉,在反过头来处理挂起的任务。

好,我们来梳理一下:

1)所有的同步任务都在主线程上执行,行成一个执行栈。

2)除了主线程之外,还存在一个任务列队,只要异步任务有了运行结果,就在任务列队中植入一个时间标记。

3)主线程完成所有任务(执行栈清空),就会读取任务列队,先执行微任务队列在执行宏任务队列。

4)重复上面三步。

只要主线程空了,就会读取任务列队,这就是js的运行机制,也被称为 event loop(事件循环)。

三、执行顺序
1.主线程上宏任务、微

console.log('---start---');//第一轮主线程
 
setTimeout(() => {
  console.log('setTimeout');  // 将回调代码放入个宏任务队列,第二轮宏任务执行
}, 0);
 
new Promise((resolve, reject) => {
  console.log('---Promise第一轮微任务同步执行---');//第一轮微任务同步执行
  resolve()
}).then(()=>{
  console.log('Promise.then实例成功回调执行'); // 将回调代码放入微任务队列,第一轮宏任务执行完后立即执行
});
 
console.log('---end---');//第一轮主线程结束

任务执行顺序

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值