行业知识
【手撕面试题】JavaScript(高频知识点四)
目录
()面试官:请简述一下JS中map和forEcah的区别?
面试官:请简述一下JS中如何阻止默认事件和事件冒泡,原理和方法?
()面试官:请简述一下JS中的原型,原型链?有什么特点?
面试官:请简述一下JS中如何判断array和object?
面试官:请指出JS中宿主对象和原生对象的区别?
面试官:JS中async await和 promise 及 generator 的区别在哪?
面试官:请解释一下什么是工厂模式,有什么优缺点?
面试官:请阐述一下JS中的同源策略是什么?
面试官:请简述一下JS中的hoisting是什么?
面试官:请简述一下JS中的isNan函数?
每天10道题,100天后,搞定所有前端面试的高频知识点,加油!!!在看文章的同时,希望不要直接看答案,先思考一下自己会不会,如果会,自己的答案是什么?想过之后再与答案比对,是不是会更好一点,当然如果你有比我更好的答案,欢迎评论区留言,一起探讨技术之美。
面试官:请简述一下JS中map和forEcah的区别?
我:呃~,是这样的,map和forEach都是数组的内置方法,它们都是用于遍历数组元素的,区别在于它们的用途和返回值是不同的。
map:会根据提供的函数对原始数组的每个元素进行某种转换,返回一个新数组,因此是可以与其它数组方法类似filter、reduce等进行链式调用。
forEach:会对原始数组中的每一个元素进行遍历然后执行某些操作,不是为了获取新数组,所以没有返回值。
总之,如果需要新数组则使用map,如果只需遍历数组并执行某些操作则用forEach。
面试官:请简述一下JS中如何阻止默认事件和事件冒泡,原理和方法?
我:呃~,以下是关于这两个概念的原理解释以及如何在JS中实现它们的方法:
阻止默认事件:当元素触发某个事件时,浏览器通常会执行该事件的默认行为,比如点击链接会跳转页面、提交表单等。要阻止默认事件,可以在事件处理函数中使用 Event 对象的 preventDefault() 方法,例如代码:
// 当点击元素时,会阻止默认的点击行为,不会触发链接跳转或其他默认操作。element.addEventListener('click', function(event) { event.preventDefault();}); 阻止事件冒泡:事件冒泡是指事件从最内层的元素开始向外层元素逐级触发的过程。如果需要阻止事件冒泡,可以使用 Event 对象的 stopPropagation() 方法。例如代码:
// 当点击元素时,会阻止事件向父级元素传播,即不会触发父级元素的点击事件。element.addEventListener('click', function(event) { event.stopPropagation();}); 这两个方法都是通过对Event对象进行操作来实现的,它们分别修改了Event对象的defaultPrevented和propagationStopped属性,从而影响了事件的处理过程。
面试官:请简述一下JS中的原型,原型链?有什么特点?
我:呃~,在JS中,每个对象都有一个原型(prototype),而原型又是一个对象。原型链是一种用于实现继承和属性查找的机制。
原型:每个对象(除了 null)都有一个原型,原型也是一个对象,它包含对象共享的属性和方法。当访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript 就会去该对象的原型中查找,如果原型中也没有,则会继续在原型的原型中查找,直到找到为止。
原型链:原型链是由对象之间的原型关系所形成的链式结构。当访问一个对象的属性或方法时,如果该对象本身没有,就会沿着原型链向上查找,直到找到为止。这样就实现了属性和方法的继承和共享。
特点:
1)继承:通过原型链,子对象可以继承父对象的属性和方法。
2)共享:原型对象中的属性和方法是所有实例共享的,这样可以节省内存,提高性能。
3)动态性:可以动态地向原型对象中添加新的属性和方法,所有基于该原型对象创建的实例都能够访问到新添加的属性和方法。
下面是原型和原型链的代码示例:
// 创建一个构造函数function Person(name) { this.name = name;}// 向构造函数的原型中添加方法Person.prototype.sayHello = function() { console.log('Hello, my name is ' + this.name);};// 创建一个实例var person1 = new Person('Alice');person1.sayHello(); // 输出 "Hello, my name is Alice"// 通过原型链访问方法console.log(person1.hasOwnProperty('name')); // true,直接在实例中找到属性console.log(person1.hasOwnProperty('sayHello')); // false,通过原型链找到方法 面试官:请简述一下JS中如何判断array和object?
我:呃~,在JS中,可以使用不同的方法来判断一个变量是数组还是对象,如下:
判断数组:
1)使用 Array.isArray() 方法:这是最常用的方法,它会检查一个变量是否是数组,并返回一个布尔值。
var arr = [1, 2, 3];console.log(Array.isArray(arr)); // true
2)使用 instanceof 运算符:instanceof 运算符用于检查一个对象是否是某个构造函数的实例,可以用来判断一个变量是否是数组。
var arr = [1, 2, 3];console.log(arr instanceof Array); // true
3)使用 Object.prototype.toString.call() 方法,这个方法可以返回表示该对象的字符串。对于数组,它会返回包含 "Array" 的字符串。
const arr = [1, 2, 3]; console.log(Object.prototype.toString.call(arr) === '[object Array]'); // 输出:true
判断对象:
1)使用 typeof 运算符:typeof 运算符返回一个表示变量类型的字符串。当变量是对象时,typeof 返回 'object'。
var obj = { key: 'value' };console.log(typeof obj === 'object'); // true 2)使用 instanceof 运算符:除了用于判断数组,instanceof 运算符也可以用于判断一个对象是否是另一个对象的实例。
var obj = { key: 'value' };console.log(obj instanceof Object); // true 3)使用 Object.prototype.toString.call() 方法,对于对象,这个方法会返回包含 "Object" 的字符串。
const obj = {}; console.log(Object.prototype.toString.call(obj) === '[object Object]'); // 输出:true 面试官:请指出JS中宿主对象和原生对象的区别?
我:呃~,在JS中,宿主对象和原生对象是两种不同对象类型,其之间有一些重要的区别:
宿主对象:宿主对象是由JavaScript的运行环境(如浏览器或Node.js)提供的对象。这些对象提供了与JavaScript运行环境交互的接口,并提供了执行特定任务(如访问DOM、与服务器通信等)的能力,例如在浏览器环境中,window、document 和 XMLHttpRequest 等对象就是宿主对象。
原生对象:原生对象(或称为内置对象)是JavaScript语言本身定义的对象类型。这些对象在ECMAScript规范中定义,是JavaScript解释器或引擎的一部分。这些对象提供了JavaScript语言的核心功能,如Array、Date、String等内置对象。
两者区别:
1)宿主对象是由宿主环境提供的,原生对象是由 ECMAScript 标准定义的。
2)宿主对象的行为和特性取决于宿主环境的实现,原生对象在不同的 JavaScript 实现中应该是一致的。
3)宿主对象具有较高的扩展性,原生对象在 ECMAScript 标准中有详细的规范和定义。
所以在实际开发中,了解宿主对象和原生对象的区别有助于开发者更好地理解和利用不同类型的对象,并且能够避免在跨平台开发中出现意外的行为差异。
面试官:JS中async await和 promise 及 generator 的区别在哪?
我:呃~,它们是用于处理异步操作的三种不同的机制,它们各有特点和用途,下面是它们之间的主要区别:
async/await:基于Promise的异步编程的语法糖,允许使用更同步的语法来编写异步代码,async关键字用于声明一个函数是异步的,而await关键字用于在异步函数内部等待一个Promise的解析,可以更容易地处理错误,因为可以使用try/catch块来捕获异步操作中的错误。
Promise:是一个表示异步操作最终完成或失败及其结果值的对象,通常与.then()和.catch()方法一起使用,以处理异步操作的结果或错误,是链式的,比传统的回调函数更容易管理异步代码,因为可以更容易地追踪和组合异步操作。
Generator:是一种返回迭代器的函数,可以在函数执行的多个点之间暂停和恢复,通常与yield关键字一起使用,以在函数执行过程中暂停和恢复,允许在函数执行的不同点之间切换,需要额外的库或工具来管理异步流程,并且不如async/await直观或易于使用。
在实际开发中,通常优先选择使用 async/await 来编写异步代码,因为它更简洁、更易读,并且能够充分利用 Promise 的功能。而 Generator 在某些特定场景下(如实现异步迭代器)可能会有一定的用武之地。
面试官:请解释一下什么是工厂模式,有什么优缺点?
我:呃~,工厂模式是一种设计模式,用于创建对象的方式。它将对象的创建过程封装在一个函数或方法中,使得在需要创建对象时可以调用这个函数或方法来完成对象的实例化,如下代码是经典工厂模式示例:
// 工厂函数function createCar(type) { if (type === 'SUV') { return new SUV(); } else if (type === 'Sedan') { return new Sedan(); } else { return new DefaultCar(); }}// 使用工厂函数创建对象const myCar = createCar('SUV'); 优点:
1)封装对象创建逻辑:工厂模式可以将对象的创建过程封装起来,使得调用方无需了解对象的具体创建细节,只需要调用工厂函数即可获取所需的对象。
2)代码复用:通过工厂模式,可以避免在多个地方重复编写对象的创建逻辑,提高了代码的复用性。
3)灵活性:工厂模式可以根据需要动态地创建不同类型的对象,从而使得系统具有更好的灵活性和扩展性。
缺点:
1)难以识别对象类型:由于工厂函数通常返回一个抽象的父类或接口,调用方很难直接识别返回的具体对象类型,这可能导致调用方需要进行额外的类型判断和转换。
2)对象定制能力有限:工厂模式将对象的创建逻辑封装在工厂函数中,导致对象的创建过程相对固定,难以提供灵活的定制能力。
总之:工厂模式是一种简单而灵活的对象创建方式,适用于需要封装对象创建逻辑,并且需要动态创建不同类型对象的场景。然而,它也存在一些局限性,特别是在对象定制能力和类型识别方面。在实际开发中,需要根据具体情况权衡使用工厂模式的利弊。
面试官:请阐述一下JS中的同源策略是什么?
我:呃~,同源策略是一种安全机制,用于Web浏览器中防止一个源的文档或脚本访问另一个源的资源。在JS中,同源策略是一种重要的安全限制,它有助于保护用户的隐私和安全,防止恶意网站对用户信息的窃取和篡改。
同源策略的要求是:协议、域名、端口都要相同,如果以上三点中有任何一点不相同,就会被视为跨域请求,浏览器会阻止页面中的JavaScript代码对另一个源的资源进行访问。
同源策略的目的是:防止恶意网站通过脚本访问用户的敏感信息(如Cookie、LocalStorage等),或者通过脚本篡改其他网站的内容。通过同源策略,浏览器确保每个源的数据和行为都受到严格的限制,从而提高了Web应用的安全性。
当然在某些情况下,Web应用程序需要进行跨域通信或操作,开发者可以使用一些技术,如JSONP(但这种方法存在安全风险,因为它使用标签进行跨域请求,可能会暴露用户的敏感信息)、CORS(跨来源资源共享)等。这些技术允许在符合一定条件的情况下,进行安全的跨域通信。
面试官:请简述一下JS中的hoisting是什么?
我:呃~,Hoisting是指在代码执行过程中,变量和函数的声明会被提升到其所在作用域的顶部,但初始化操作不会被提升。这意味着在代码执行之前,JavaScript会先处理变量和函数的声明,使它们可以在其定义之前被访问。举例如下:
console.log(myVar); // 输出:undefinedvar myVar = 5;// 上述代码实际上被处理为:var myVar;console.log(myVar); // 输出:undefinedmyVar = 5;
而对于函数声明,则会被完整提升:
sayHello(); // 输出:Hello!function sayHello() { console.log('Hello!');}// 实际处理后的代码:function sayHello() { console.log('Hello!');}sayHello(); // 输出:Hello! 需要注意的是,虽然变量和函数的声明会被提升,但它们实际上并没有被提前执行,而是在原始位置的代码执行时才会被赋予初始值。因此,在编写JavaScript代码时,虽然存在提升的机制,但为了代码的可读性和可维护性,建议尽量避免依赖提升特性,按照自然的顺序编写变量和函数的定义。
面试官:请简述一下JS中的isNan函数?
我:呃~,在JS中,isNaN() 函数用于确定一个值是否为NaN(Not a Number)。它接受一个参数,然后尝试将该参数转换为数字,如果转换后的结果是NaN,则返回true,否则返回false。
但是isNaN() 函数有一些行为可能会让人感到困惑。例如,当参数是字符串时,如果字符串不能被转换为数字,则返回true。但是,如果字符串包含空格或空字符串,则会返回false。此外,当参数是对象时,会先调用对象的valueOf() 方法,然后再进行判断。下面是一些例子:
isNaN(NaN); // trueisNaN(123); // falseisNaN('hello'); // true,因为无法转换为数字isNaN('123'); // false,可以转换为数字isNaN(' '); // false,虽然是空字符串,但可以转换为0isNaN({}); // true,调用了对象的valueOf()方法后返回true 为了避免isNaN() 函数的一些奇怪行为,通常建议在判断一个值是否为NaN时,使用更严格的方法,如使用Number.isNaN() 函数,该函数在ES6中引入,只有在参数是真正的NaN时才返回true。