前端面试总览
1.说说你对作用域链的理解
作用域链是在JavaScript中用于查找变量和函数的机制。它是由当前执行环境的变量对象(Variable Object)和其父级执行环境的变量对象组成的链式结构。
当在JavaScript中访问一个变量或函数时,解释器会首先在当前执行环境的变量对象中查找,如果找到了则直接使用该变量或函数。如果没有找到,解释器会继续在父级执行环境的变量对象中查找,直到找到该变量或到达全局执行环境。
这种链式的查找过程形成了作用域链。每个函数在定义时都会创建自己的作用域,并且作用域链会在函数定义时确定。当函数被调用执行时,会创建一个新的执行环境,并将该执行环境的作用域链设置为函数定义时的作用域链。这样,在函数内部就可以通过作用域链访问外部的变量和函数。
作用域链的特点:
- 作用域链是静态的:作用域链在函数定义时就被确定,不会随着函数的执行而改变。
- 作用域链是单向的:作用域链是由内向外的,子级执行环境可以访问父级执行环境的变量和函数,但反过来是不允许的。
- 变量查找按照就近原则:在作用域链中,变量和函数按照定义的顺序被查找,就近原则指的是在离当前执行环境最近的作用域中查找。
作用域链的理解对于理解变量的作用域、作用域链的创建和维护、闭包的概念以及变量的生命周期等都非常重要。正确理解作用域链有助于编写清晰、可维护的JavaScript代码。
2.JavaScript原型,原型链 ? 有什么特点?
在JavaScript中,每个对象都有一个原型(prototype)。原型是一个对象,它包含可以被其他对象继承的属性和方法。当访问一个对象的属性或方法时,如果该对象本身没有定义该属性或方法,JavaScript会沿着原型链向上查找,直到找到匹配的属性或方法。
特点:
- 原型是一个对象:每个对象都有一个内部属性
[[Prototype]]
,它指向该对象的原型。可以通过Object.getPrototypeOf(obj)
或__proto__
属性来访问对象的原型。 - 原型链是通过原型的链接而形成的:每个对象的原型也是一个对象,它也有自己的原型。这样就形成了一个原型链,将对象连接在一起。
- 原型链的终点是
null
:原型链最终会指向null
,即没有原型。null
是原型链的终点,表示没有更高层级的原型可供查找。
原型链的作用:
- 属性和方法继承:通过原型链,对象可以继承其原型上的属性和方法。如果对象自身没有定义某个属性或方法,它会从原型链中查找并使用原型对象上的属性或方法。
- 实现对象之间的关联:通过原型链,对象可以与其他对象建立关联,形成对象之间的关系,实现共享和复用。
示例代码:
// 创建一个对象
const person = {
name: "John",
age: 30,
};
// 使用原型创建一个新对象
const student = Object.create(person);
student.major = "Computer Science";
// 原型链:student -> person -> Object.prototype -> null
console.log(student.name); // 继承自原型对象,输出 "John"
console.log(student.age); // 继承自原型对象,输出 30
console.log(student.major); // student 自身的属性,输出 "Computer Science"
在上述示例中,student
对象通过Object.create()
方法使用person
对象作为原型创建而来。student
对象继承了person
对象的属性name
和age
。当访问student
对象的name
属性时,它会沿着原型链找到person
对象上的name
属性并返回。同样地,访问student
对象的age
属性也是通过原型链找到person
对象上的age
属性并返回。major
属性是student
对象自身定义的。
3.请解释什么是事件代理
事件代理(Event delegation)是一种在JavaScript中处理事件的技术,它利用事件冒泡机制将事件处理委托给父元素或祖先元素,而不是直接在目标元素上绑定事件处理程序。
事件代理的工作原理如下:
- 将事件处理程序绑定到父元素或祖先元素上,而不是直接绑定到目标元素。
- 当事件触发时,事件会通过事件冒泡的过程向上传播到父元素或祖先元素。
- 在父元素或祖先元素上,可以通过判断事件的目标元素(
event.target
)来确定是哪个子元素触发了事件。 - 根据目标元素的类型或其他条件,执行相应的操作或处理逻辑。
事件代理的优点:
- 减少事件处理程序的数量:通过将事件处理程序绑定到父元素或祖先元素上,可以减少页面中的事件处理程序数量,提高性能和内存使用效率。
- 动态绑定事件:对于动态添加或删除的子元素,无需重新绑定事件处理程序,因为事件仍然委托给父元素或祖先元素处理。
事件代理的应用场景:
- 列表或表格:对于包含大量列表项或表格行的情况,可以将事件处理程序绑定到列表或表格的父元素上,以减少事件处理程序的数量。
- 动态添加元素:对于通过JavaScript动态添加的元素,可以使用事件代理来处理事件,无需为每个新元素绑定事件处理程序。
- 性能优化:在某些情况下,直接在目标元素上绑定事件处理程序可能会导致性能问题,使用事件代理可以更好地处理事件。
需要注意的是,事件代理在一些特定场景下可能不适用,例如需要对事件目标元素进行精确控制或事件处理逻辑较为复杂的情况。在选择使用事件代理时,需要综合考虑具体情况和需求。
4.谈谈This对象的理解
在JavaScript中,this
是一个特殊的关键字,它表示当前执行上下文中的对象。this
的值在函数执行时动态绑定,取决于函数被调用的方式和上下文。
以下是关于this
对象的一些理解:
-
this
的绑定方式:- 默认绑定:当一个函数独立调用时,
this
指向全局对象(在浏览器中为window
对象,在Node.js中为global
对象)。 - 隐式绑定:当一个函数作为对象的方法调用时,
this
指向调用该方法的对象。 - 显式绑定:通过
call()
、apply()
、bind()
等方法可以显式地指定函数执行时的this
上下文。 - new绑定:当通过
new
关键字调用构造函数创建新的对象时,this
指向新创建的实例对象。 - 箭头函数绑定:箭头函数没有自己的
this
绑定,它会捕获并继承上层作用域的this
值。
- 默认绑定:当一个函数独立调用时,
-
动态绑定:
this
的值在函数执行时动态确定,而不是在函数定义时确定。因此,同一个函数在不同的调用环境中可能会有不同的this
值。 -
上下文对象:
this
通常用于引用当前函数所属的对象或上下文,从而在函数内部访问和操作对象的属性和方法。 -
注意事项:
- 在严格模式下,全局对象不会作为默认绑定的
this
值,而是undefined
。 - 箭头函数中的
this
值由包含箭头函数的最近一层非箭头函数的this
值决定。
- 在严格模式下,全局对象不会作为默认绑定的
理解this
的关键是理解函数的执行上下文和调用方式。this
的绑定规则较为复杂,需要根据具体的调用情况和函数定义方式来确定。正确理解和使用this
可以避免一些常见的错误,并更好地处理对象和函数之间的关系。
5.new操作符具体干了什么
new
操作符用于创建一个新的对象实例,它具体做了以下几个步骤:
-
创建一个空的对象:创建一个新的空对象,作为将要实例化的对象。
-
将对象的原型连接到构造函数:将新创建的对象的原型(
[[Prototype]]
)链接到构造函数的原型对象(Constructor.prototype
)。这样新对象就可以访问构造函数原型上的属性和方法。 -
执行构造函数:将构造函数作为普通函数调用,将新创建的对象绑定到构造函数中的
this
上下文。这样构造函数内部的代码就可以通过this
来引用新对象。 -
返回对象实例:如果构造函数没有显式返回一个对象,那么
new
操作符会隐式返回新创建的对象实例。如果构造函数显式返回一个对象,则返回该对象而不是新创建的实例。
下面是一个使用new
操作符创建对象实例的示例代码:
// 构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
// 创建对象实例
const person1 = new Person('John', 30);
console.log(person1.name); // 输出 "John"
console.log(person1.age); // 输出 30
在上述示例中,new Person('John', 30)
会执行Person
构造函数。new
操作符首先创建一个新的空对象,然后将该对象的原型链接到Person.prototype
上,接着将构造函数Person
作为普通函数调用,将新对象绑定到this
上下文。最后,如果构造函数没有显式返回一个对象,new
操作符会返回新创建的对象实例。
使用new
操作符可以方便地创建和初始化对象实例,它是面向对象编程中的重要机制之一。
6.null,undefined 的区别
null
和undefined
是JavaScript中两个特殊的值,表示变量的空或未定义状态,它们具有以下区别:
-
undefined
:表示一个声明了但未赋值的变量,或者访问对象属性或数组元素时不存在的值。let a; // 声明了但未赋值,默认值为 undefined console.log(a); // 输出 undefined const obj = {}; console.log(obj.property); // 对象中不存在的属性值为 undefined
-
null
:表示一个被明确赋值为空的变量,或者用于表示对象或数组不包含有效值。const b = null; // 显式赋值为 null console.log(b); // 输出 null const obj = { property: null }; // 对象属性值赋值为 null console.log(obj.property); // 输出 null
-
类型不同:
undefined
是一种数据类型(Undefined),而null
是一种特殊的对象类型(Null)。 -
被判断为假值的方式不同:在条件判断中,
undefined
被视为假值,而null
不是假值。if (undefined) { console.log("This won't be executed"); } if (null) { console.log("This will be executed"); }
-
转换为布尔值的结果不同:在布尔上下文中,
undefined
会被转换为false
,而null
会被转换为false
。console.log(Boolean(undefined)); // 输出 false console.log(Boolean(null)); // 输出 false
总结:
undefined
表示变量的未定义或未赋值状态,而null
表示变量被明确赋值为空。它们在类型、条件判断、布尔上下文和转换方面具有不同的行为。