Erlo

金三银四,那些烧脑的JS面试题及原理

收藏 2022-02-09 12:30:25   399   开源中国
页面报错/反馈
点赞
  • Q: JS代码是按顺序执行的吗?
  • A: JS代码执行过程中,需要先做变量提升,而之所以需要实现变量提升是因为JS代码在执行之前需要先编译

1、变量提升

变量和函数声明会被存放到变量环境中,变量的默认值会被设置为undefined

var scope = 'global scope'
function a(){
  // 3、顶层变量环境声明了scope初始化为undefined
  function b(){ 
    // 2、b函数的上层作用域是a,向上找scope
    console.log(scope)
  }
  return b;
  // 1、虽然声明在return语句后面,依然会提升到a函数作用域的顶层
  var scope = 'local scope'
}
a()() // undefined

1.1、同名处理

  • 同名函数,选择最后声明的
  • 变量和函数同名,选择函数
var a = 1
var getNum = function() {
  a = 2
}
function getNum() {
  a = 3
}
getNum()
console.log(a) // 2
// 变量和函数同名选择提升函数,函数提升包含初始化和赋值,接着执行函数,var声明的getNum被赋值为一个函数执行完成更改变量a为2

1.2、提升阶段

创建 初始化 赋值
let 提升 x x
var 提升 提升 x
function 提升 提升 提升

在块作用域内,let声明的变量仅在创建时被提升,在初始化之前使用变量,就会形成一个暂时性死区

var name = 'World'
;(function () {
  if (typeof name === 'undefined') {
    var name = "HuaMu"; 
    console.info('Goodbye ' + name)
  } else {
    console.info('Hello ' + name)
  }
})()
// if分支内的name会被提升到外层,且同全局变量同名,则访问不到外层的name,var仅创建和初始化,并未赋值,则值为undefined,满足if条件
// Goodbye HuaMu

2、调用栈

在执行上下文创建完成后,JS引擎会将执行上下文压入栈中,通常把这种用来管理执行上下文的栈称为执行上下文,又称调用栈

2.1、函数调用

  • JS引擎会为函数创建执行上下文,并将其压入调用栈
  • JS引擎执行函数代码
  • 执行完毕后,JS引擎将该函数的执行上下文弹出栈

2.2、栈溢出

当分配的调用栈空间被占满时,会引发“栈溢出”问题。即超过了最大栈容量或最大调用深度

2.2.1、场景

3、作用域

作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。主要有全局作用域、函数作用域以及块级作用域

  • 当前作用域与上层作用域有同名变量时,无法访问和影响上层变量
let a = 1
function b(a) {
  a = 2
  console.log(a)
}
b(a) // 2
console.log(a) // 1

4、作用域链

通过作用域查找变量的链条称为作用域链,而作用域链是通过词法作用域来确定的。词法作用域由函数声明的位置来决定,是静态的,即在代码阶段就决定好了,和函数是怎么调用的没有关系

// 连等操作是从右向左执行的,相当于b = 10、let a = b,相当于隐式创建为一个全局变量b 
let a = b = 10; 
;(function(){ 
  // 跟着作用域链查找到全局变量b,并修改为20
  // 由于重新声明了,a变量只是局部变量,不影响全局变量a
  let a = b = 20 
})()
console.log(a) // 10
console.log(b) // 20

函数只会在第一次执行的时候被编译,因此编译时变量环境和词法环境最顶层数据已确定

var i = 1
function b() {
  console.log(i)
}
function a() {
  var i = 2
  b()
}
// 由于a函数在全局作用域被定义,即便b函数在a函数内执行,它也只能访问到全局的作用域
a() // 1

5、闭包

一个作用域引用着一个本该被销毁的作用域,称之为闭包。即一个函数引用着父作用域的变量,在父函数执行结束后依然进行调用

6、this

this是函数执行上下文对象,是动态变化的值,它没有作用域的限制,嵌套函数中的this不会从外层函数中继承,可通过箭头函数、self处理

6.1、类型

  • 全局执行上下文中的this: window
  • 函数执行上下文中的this: 严格模式 ? undefined: window
var v_out = 'v_out';
let c_out = 'c_out';
var inner = {
  v_out: 'v_in',
  c_out: 'c_in',
  v_func: function () {
    return this.v_out
  },
  c_func: function () {
    return this.c_out
  },
  func:()=>{
    return this.v_out
  }
};
// 获取对象作用域内的函数,在全局环境下调用 this 指向 window
const v_method = inner.v_func;
const c_method = inner.c_func; 
// 顶层 v_out 变量会提升挂载到 window 
v_method(); // 'v_out'
// 在块作用域内,const声明的变量不会挂载到 window,且父作用域不能访问子作用域
c_method(); // undefined

// 赋值表达式和逗号表达式会返回最后一个值本身,即inner.v_func函数本身,调用位置是全局环境
(inner.v_func, inner.v_func)();  // 'v_out'
(inner.v_func = inner.v_func)(); // 'v_out'

// 对象的方法调用,this指向该对象
inner.v_func()   // 'v_in'
(inner.v_func)() // 'v_in'

// 箭头函数没有自己的执行上下文,它继承调用函数的this,在这里是window
inner.func() // 'v_out'

6.2、更改this指向

绑定优先级为:new > 显示绑定(call、apply、bind) > 隐式绑定(调用函数对象) > 默认绑定(window)

6.2.1、通过函数的call、apply、bind方法设置

c_method.call(inner)
c_method.apply(inner)
c_method.bind(inner)()
简单实现:call、apply
  • 将当前函数链接到指定的上下文中,即将函数设置为对象属性
  • 当前函数在context上下文中执行
  • 移除context中已执行的当前函数
/**
 * 简单实现apply
 * @param {Function} fn 当前运行的函数
 * @param {Object} context 指定的上下文
 * @param {Array} args 参数集合
 * @returns 
 */
const apply = (fn,context=window,args=[])=>{
  // Symbol是es6增加的第六个基本类型,对于对象属性就是uuid
  const id = Symbol();
  // 将当前函数链接到指定的上下文中
  context[id] = fn;
  // 当前函数在context上下文中执行
  const res = context[id](...args)
  // 移除context中已执行的当前函数
  delete context[id]
  return res;
}

// -------test------- //
const context = {
  value:1
}

function fn (name,isGirl){
  console.log("
			 
			 		  
登录查看全部

参与评论

评论留言

还没有评论留言,赶紧来抢楼吧~~

返回顶部

    给这篇文章打个标签吧~

    棒极了 糟糕透顶 好文章 PHP JAVA JS 小程序 Python SEO MySql 确认