前端常考面试题
原型和原型链
说一下什么是原型
每个函数都有一个特殊属性,那就是prototype。就像是打印了以下函数
function person(){}
console.log(person.prototype)
显示出了函数所有的原型
![image.png](https://s2.loli.net/2023/06/08/eIwsCfRjATFnKd1.png)
那什么是原型链呢
所有对象都是由原型对象继承而来的,而原型对象本身也是一个对象,它也有自己的对象,这样层层上溯,就形成了原型链。原型链的最低端是null,因为null是没有对象的
const person = { name: 'ExMaterial', age: 20 };
console.log(person); // { name: 'ExMaterial', age: 20 }
console.log(person.toString()); // [object Object]
那原型和原型链存在的意义是什么?
实例对象可以共享构造函数的方法和属性,节省内存。构造函数原型上的方法和属性越多,节省内存就越大
function person(name, age) {
this.name = name;
this.age = age;
}
person.prototype.eating = function() {
return this.name + " is eating."
}
person.prototype.running = function() {
return this.name + " is running."
}
const p1 = new person('ExMaterial', 20)
console.log(p1.eating()); // ExMaterial is eating.
如何理解作用域和作用域链?
什么是作用域?
作用域就是函数里面定义的变量,不能直接从外面访问变量值
function scope() {
var insideVariable = 'insideVariable';
}
scope();
console.log(inVariable); // Uncaught ReferenceError: inVariable is not defined
在ES6之后,作用域可分为以下三种:
全局作用域
// 全局变量
var greeting = 'Hello World!';
function greet() {
console.log(greeting);
}
// 打印 'Hello World!'
greet();
函数作用域
function greet() {
var greeting = 'Hello World!';
console.log(greeting);
}
// 打印 'Hello World!'
greet();
// 报错: Uncaught ReferenceError: greeting is not defined
console.log(greeting);
块级作用域
{
// 块级作用域中的变量
let greeting = 'Hello World!';
var lang = 'English';
console.log(greeting); // Prints 'Hello World!'
}
// 变量 'English'
console.log(lang);
// 报错:Uncaught ReferenceError: greeting is not defined
console.log(greeting);
什么是作用域链?
一层一层的作用域嵌套关系就是作用域链,直接说就是函数里面套函数
var a = 100;
function globalFunc(){
var b = 200;
function currentFunc(){
var c = 300;
console.1og(a)://全局作用域的变量
}
currentFunc();
}
globalFunc();
你对闭包怎么理解?
什么是闭包
能够访问其他函数内部变量的函数,被称为闭包
function closure() {
var name = 'Mozilla';
function displayName() {
console.log(name);
}
return displayName;
}
var myFunc = closure();
myFunc();
闭包存在什么问题?
优点:
- 避免全局变量的污染
- 一个变量长期存储在内存中(缓存变量)
- 环境私有化,方法都是孤立的
缺点:
- 内存泄露(消耗)
- 常驻内存,增加内存使用量
来看下面一段代码:
function foo() {
var a = 3;
function result() {
console.log(a);
}
return result;
}
var test = foo();
test();
上述代码中,理论上来说,foo函数作用域隔绝了外部环境,所有变量引用都在函数内部完成,foo运行完成以后,内部的变量就应该被销毁,内存被回收。然而闭包导致了全局作用域始终存在一个test的变量在引用着foo内部的result函数,这就意味着foo内部定义的result函数引用数始终为3,垃圾运行机制就无法把它销毁这就是我们说的闭包本身会造成内部变量常驻内存。
谈谈对 this 对象的理解?
- this 总是指向函数的直接调用者(而非间接调用者)
- 如果有 new 关键字,this 指向 new 出来的那个对象
- 箭头函数的 this 是指向外部上下文的 this
如何来修改 this 指向
bind、apply 和 call 可以修改指向。使用某个上下文调用该函数,使用 .bind()
。 如果要立即调用函数,使用.call()
或 .apply()
,并修改上下文。
const mbs = {
name: '麻不烧',
say(prefix, age) {
console.log(`${prefix},my name is ${this.name},i am ${age} year old`)
}
}
const A = {
name:'小丁'
}
mbs.say.call(A,'hello',3) // 'hello,my name is 小丁,i am 3 year old'
浅拷贝和深拷贝
浅拷贝地址还是指向原来那个内存地址,深拷贝是重新开辟一个内存地址
深拷贝JSON.parse(JSON.stringify())有什么缺点,如何解决呢
js关键字有丢失,比如function
、null
、正则表达式
解决方法:循环对象,把对象的 KEY 和 VALUE 存放到一个新的变量里面
function deepClone(source) {
const targetObj = source.constructor === Array ? [] : {};
for (keys in source) {
if(source.hasOwnProperty(keys)) {
// 数组和对象
if(source[keys] && typeof source[keys] === 'object') {
// targetObj[keys] = targetObj[keys] === Array ? [] : {}; // 只是为了可读性,可要可不要
targetObj[keys] = deepClone(source[keys]);
} else {
// 基本类型
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}
js的基本类型和引用类型有哪些
基本数据类型指的是简单的数据段,有5种,包括null、undefined、string、boolean、number;
引用数据类型指的是有多个值构成的对象,包括object、array、date、regexp、function等。
vue3对比vue2的优势
- 响应式系统,proxy 替代 Object.defineProperty 监听对象,监听对象不需要再深度遍历,proxy可劫持整个对象。
Proxy
返回的是一个新对象,而Object.defineProperty
只能遍历对象属性直接修改。 - 体积包减少
说一下 vue2 的响应式原理
vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。利用了 Object.defineProperty()
这个方法重新定义了对象获取属性值(get)和设置属性值(set)。