this指向代码题
1.讲出几大类函数this指向 - 普通函数,对象函数,构造函数,箭头函数
(1)普通函数:this指向全局对象window.
(2)对象函数:this指向函数所在的对象。
(3)构造函数:this指向该构造函数形成的对象实例。
(4)箭头函数:本身不存在this,已外层元素的this指向作为自己的this指向。
2.讲出apply,call,bind的区别,以及使用方法
区别:
- apply,call修改完this指向,立即自动执行。bind需要手动调用。
- call和bind传入参数为对象,apply为数组。
使用方法
- call和bind传入对象作为参数,apply传入数组。
- apply和call传入参数后会自动修改this指向,bind修改完this指向,需要手动调用一遍。
3.
Plain
var name = 'global'
const obj = {
name: 'obj',
say() {
console.log(this.name)
const fn = function () {
console.log(this.name)
}
fn()
}
}
obj.say()
- 两次
console.log分别输出什么?
obj,global.
- 为什么?
第一次say由obj调用,this指向obj,fn由window调用,this指向window。
- 如果把
fn改成箭头函数:
Plain
const fn = () => {
console.log(this.name)
}
输出又是什么?为什么?
obj,obj.第一次say由boj调用,this指向obj,fn为箭头函数,this由外层的say决定,所以也指向obj。
- 有没有什么改变this的思路,讲讲实现bind的思路
改变思路:把函数变成目标对象的一个临时方法,由该对象来调用它。
bind实现思路:bind 本身不会立即执行函数,而是返回一个新的函数。我会先保存原函数和需要绑定的 this,然后在返回函数
执行的时候,通过 call 或 apply 调用原函数,并把绑定的参数和后续调用时传入的参数合并起来。除此之外,
还需要考虑返回的函数被 new 调用的情况,因为这种情况下 this 应该指向新创建的实例,而不是之前绑定的对象。
Var let const 全局预编译 函数预编译
- 讲出let,const,var的区别,块级作用域,变量提升,以及后来不使用var的原因
- 块级作用域:let,const具有块级作用域,var不具有块级作用域。
- 变量提升:var存在变量提升,let,const虽然也存在变量提升,但他们同时存在暂时性死区,即从当前作用域顶部到变量完成声明,这一区间内该变量是不可访问的。
- 原因:var的变量提升使var声明的变量可以先使用后声明,以及var可以重复生命同一变量,使得代码可读性和安全性大大降低,所以不用。
Plain
var a = 1;
let b = 2;
function test() {
console.log(a);
console.log(b);
var a = 3;
let b = 4;
console.log(a);
console.log(b);
}
test();
console.log(a);
console.log(b);
- 控制台会输出什么?
先输出undefined,然后报错。
- 哪一行会报错,为什么会报错?
函数内第一次打印b,let声明的函数存在暂时性死区,禁止访问。
- 如果把函数里面的
let b = 4改成var b = 4,输出又会有什么变化?
undefined,undefined,3,4,1,2
垃圾回收机制
- 什么是垃圾回收机制?判断机制是什么?
垃圾回收机制:定期扫描内存,找出那些不再被使用的变量或对象,并自动释放它们占用的内存空间。
判断机制:第一个是引用次数,当引用次数为0时触发垃圾回收。第二个是标记清除法,从根元素开始,将所有可达的变量标记为1,将剩下的变量清除。
- 什么是内存泄漏?前端常见的内存泄漏场景有哪些?
内存泄漏:程序申请了一块内存,使用完毕后,由于某些原因没有被垃圾回收释放,且这块内存再也无法被程序访问或使用了。
常见场景:意外的全局变量,未清理的定时器,事件监听器未解绑,闭包使用完变量后为设为null,DOM 节点被 JavaScript 变量引用,导致节点无法被回收。
- 你有了解过V8 引擎对垃圾回收机制的优化吗? 比如分代回收式垃圾回收?
根据变量的存活时间,把变量分为新生代和老生代,新生代存放新创建的对象。空间小,回收极频繁。老生代存放存活时间久或体积巨大的对象。空间大,回收频率低。对不同代的变量采用不同的回收算法。
闭包 作用域链
- JS有哪几种作用域?
全局作用域,函数作用域,块作用域。
- 什么是作用域链?变量查找时按照什么顺序进行?
当我们查找某个变量时,我们会首先在当前作用域查找,如果没有就到外层作用域查找,层层查找,直到找到变量所在位置,或查找到全局作用域,这个过程就形成了作用域链,按照由内向外的顺序。
- 什么是闭包?
闭包指可以访问另一个作用域中变量的函数。
- 输出什么?为什么?
1,2,1,3,2.因为每次调用 outer() 都会生成一个全新的、独立的闭包,fn1和fn2是两个完全不同的函数对象,他们互不影响。
JavaScript
function outer() {
let count = 0;
return function () {
count++;
console.log(count);
};
}
const fn1 = outer();
const fn2 = outer();
fn1();
fn1();
fn2();
fn1();
fn2();
原型与原型链
JavaScript
function Person(name) {
this.name = name;
}
Person.prototype.name = "prototype";
Person.prototype.age = 18;
const p = new Person("Jason");
console.log(p.name);
console.log(p.age);
console.log(p.hasOwnProperty("name"));
console.log(p.hasOwnProperty("age"));
console.log(p.__proto__ === Person.prototype);
console.log(Person.prototype.constructor === Person);
p.age = 20;
console.log(p.age);
console.log(Person.prototype.age);
delete p.age;
console.log(p.age);
- 九次
console.log分别输出什么?
Jason,18,true,false,true,true,20,18,18.
p.name最开始在哪里获取?p.__proto__指向什么?
p.name开始获取自实例p自身,p.__proto__ 指向 Person.prototype
- 给
p.age赋值后,为什么Person.prototype.age没有被修改?删除p.age后,为什么仍然能访问到18?
二者不是一个东西,p.age在p这个实例对象上,Person.prototype.age在构造函数的原型对象上。
删除p.age,在实例对象上找不到age属性,会沿着原型链继续找,找到Person.prototype.age。
- 什么是原型链?试着讲讲实例
p的完整原型链以及原型链的末尾是什么。
每个实例对象上都有一个__proto__属性指向构造函数的原型对象,原型对象又有自己的原型对象,层层向上查找,直到找到顶级原型对象null,就形成了原型链,末尾是null.
深浅拷贝
- 讲讲你理解的深浅拷贝,并说出两个拷贝对象的浅拷贝方法
浅拷贝:只拷贝对象的第一层属性。如果属性值是基本类型,拷贝的是值;如果属性值是引用类型,拷贝的是内存地址。这意味着新旧对象共享内部的子对象,修改子对象会相互影响。
深拷贝:递归拷贝对象的所有层级。在堆内存中完整开辟一块新空间,将原对象完全复制一份。新旧对象完全独立,互不影响。
浅拷贝方法:展开运算符,object.assign().
- 什么是循环引用?如果你想自己实现深拷贝,讲讲循环引用可能导致的问题以及解决方法
循环引用:循环引用是指对象内部的属性直接或间接地引用了自身。
手动递归深拷贝导致的问题:如果不加处理,递归函数会陷入死循环,导致调用栈溢出。
解决方法:使用 WeakMap作为哈希表进行缓存。每次拷贝一个对象前,先检查缓存中是否已经存在该对象的拷贝,如果存在则直接返回,切断无限递归。
- 使用
JSON.parse(JSON.stringify(obj))实现深拷贝有什么局限?它无法正确处理什么数据(至少两种)?给出一种更适合的深拷贝原生方法。
无法处理函数和特殊对象。采用structuredClone。
隐式转换
- 什么时候会出现隐式转换?请说说不同运算符的转换规则是什么。
( + - * / ==)
发生时机:当运算符两侧的操作数类型不一致时,JS 引擎为了能执行运算,会自动将其中一方或双方转换为相同类型。这通常发生在算术运算、比较运算(==) 以及 if / 逻辑上下文中。
加号:如果任一操作数是字符串,则将另一个操作数转为字符串,进行拼接,否则转数字:如果都不是字符串,则将它们都转为数字,进行加法。
- * /:将两个操作数都转为数字(Number),然后进行数学计算。如果任意一方无法转为有效数字,结果就是 NaN。
==:比较前会进行类型转换,遵循以下优先级:
(1)null == undefined 为 true(互转时视为相等)。
(2)数字 vs 字符串:将字符串转为数字再比较。
(3)布尔值参与:将布尔值转为数字(true 变 1,false 变 0)再比较。
(4)对象 vs 原始类型(字符串/数字/Symbol):将对象转为原始类型(To再比较。
(5)如果两侧都是对象:比较的是内存地址,只有引用同一个对象才返回 true,不会进行类型转换。
[] == false的输出是什么,并判断过程
输出 true。先将false转为0,再将\[\]转为空字符串,再将空字符串转为0,0==0,所以为true.
数据类型
- js有几种数据类型?分别是什么?指出哪些是原始类型,哪些是引用类型?
Symbol和BigInt的作用?
8种,null,undefined,string,number,boolen,symbol,bigint,object.
原始类型:null,undefined,string,number,boolen,symbol,bigint
引用类型:object
symbol:创建绝对唯一的标识符.
bigint:精确表示超大整数.
- 获取数据类型方式有哪些?各自的特点
typeof:快速区分原始类型和函数,无法区分null和对象,以及具体的对象类型。
instanceof:检测引用类型的具体构造函数,无法检测原始类型,跨窗口场景下失效。
Object.prototype.toString.call():最准确的内置类型识别,无法区分自定义类实例。
- 为什么typeof null==Object ?
这是 JS 的历史遗留 bug:JS 最初用低位标记表示数据类型,000 开头代表对象类型,而 null 的所有二进制位都是 0(表示空指针),刚好落入对象的标记范围,所以 typeof null 返回 'object'。这个问题一直保留至今是为了兼容旧代码,没有修复。
任务队列
JS中哪些任务是同步任务 宏任务 微任务?下列代码的控制台输出是什么?
JavaScript
button.addEventListener('click',()=>{
Promise.resolve().then(()=> console.log('Microtask 1'));
console.log('Listener 1');
});
button.addEventListener('click', ()=>{
Promise.resolve().then(()=>console.log('Microtask 2'));
console.log('Listener 2');
});
同步任务:大部分常规语句,函数调用,Promise 的执行器函数,async 函数中 await 之前的代码。
宏任务:定时器类,I/O操作,UI渲染,MessageChannel 的消息回调,事件监听。
微任务:Promise 的链式回调,async/await 中 await 之后的代码,MutationObserver,queueMicrotask() 显式调用的回调,Node.js 环境中的 process.nextTick()。
输出:Listener 1,Listener 2,Microtask 1,Microtask 2
异步编程
JS运行特点?为什么需要异步编程?什么是回调地狱?如何解决回调地狱?
JS运行特点:单线程运行,异步编程
为什么需要异步编程:让浏览器可以将未完成任务挂起,继续处理其他工作,等结果准备好后再处理,同时监听用户输入、加载数据、播放动画,而不会互相阻塞。
回调地狱:需要使用多个回调函数时,需要在函数中层层嵌套,使得代码难以阅读和维护,运行结果处理分散,形成回调地狱。
如何解决回调地狱:Promise链式调用,统一错误处理.
ES6新特性
JavaScript
const id = Symbol("id");
const user = {
name: "Tom",
age: 18,
[id]: 1001
};
function fn(user) {
const { name, age } = user;
console.log(arguments.length);
console.log(name, age);
console.log(user.id);
console.log(user[id]);
}
fn(user, "extra");
- 四次
console.log分别输出什么?
2,Tom 18,undefined,1001
- 为什么
arguments.length是2?
因为 arguments 保存的是函数调用时实际传入的所有实参。
const { name, age } = user是什么语法?
对象的解构赋值。
user[id]和user.id有什么区别?
前者访问symbol属性,后者访问字符串属性
- Symbol 作为对象属性名有什么作用?
Symbol 创建出来的值具有唯一性,可以作为对象属性名,避免和其他普通属性或者其他 Symbol 属性发生命名冲突。