js面试题


| JS函数中的new和return

当一个函数内部有return语句,且调用时加了new前缀时,两种情况:
1、return后面返回的是基本数据类型的值,该函数返回的是new操作符创建的新的对象
2、return后面返回的是引用数据类型的值,该函数返回的是return语句后面的内容

function Object(){
    return 1+1
}
const obj = new Object();
console.log(obj);//{}

function Object1(){
    this.name = 'Tom';
    return '123'
}
const obj1 = new Object1();
console.log(obj1);//{name:'Tom'}

function Object2(){
   this.name = 'Tom';
   return [1,2,3]
}
const obj2 = new Object2();
console.log(obj2);//[1,2,3]

0.1+0.2===0.3吗?为什么?如何解决?

在两数相加时,会先转换成二进制,0.1 和 0.2 转换成二进制的时候尾数会发生无限循环,然后进行对阶运算,JS 引擎对二进制进行截断,所以造成精度丢失。
解决办法:

(0.1*10+0.2*10)/10 //0.3

js数据类型

基本类型:Number、Boolean、String、null、undefined、symbol(ES6 新增的),BigInt(ES2020) 引用类型:引用类型统称为object类型,细分的话有:Object 类型、Array 类型、Date 类型、RegExp 类型、Function 类型 等。

Number() 的存储空间是多大?如果后台发送了一个超过最大自己的数字怎么办

Math.pow(2, 53) ,53 为有效数字,会发生截断,不精确。

事件流

事件流包括三个阶段: (1)事件捕获阶段 (2)处于目标阶段 (3)事件冒泡阶段。
多个onlick事件会被覆盖,多个addEventListener(监听方法不同-第二个参数function不一样,否则覆盖)可多次执行,第三个参数false代表冒泡,true代表捕获。

事件委托原理及优缺点

原理:利用冒泡的原理,把事件添加到父元素上,委托它们父级代为执行事件。
优点:提高性能,减少内存,对于新添加的元素也会有之前的事件。
缺点:1.事件委托基于冒泡,不冒泡的事件不支持
2.层级过多,冒泡过程中可能被中间层阻止
3.如果把所有事件都用事件委托,可能会出现事件误判,即不该触发事件的被绑定了
总结缺点:层级不易过多。

事件循环机制

先同步,再异步(先微任务,再宏任务)。
微任务:Promise、 MutaionObserver、process.nextTick(Node.js环境)
宏任务:script(整体代码)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 环境)

事件是如何实现的

基于发布订阅模式,就是在浏览器加载的时候会读取事件相关的代码,但是只有实际等到具体的事件触发的时候才会执行。

new 一个函数发生了什么

1、创造一个全新的对象
2、这个对象会被执行 [[Prototype]] 连接,将这个新对象的 [[Prototype]] 链接到这个构造函数.prototype 所指向的对象
3、这个新对象会绑定到函数调用的 this
4、如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象

//自己定义的new方法
let newMethod = function (Parent, ...rest) {
    // 1.以构造器的prototype属性为原型,创建新对象;
    let child = Object.create(Parent.prototype);
    // 2.将this和调用参数传给构造器执行
    let result = Parent.apply(child, rest);
    // 3.如果构造器没有手动返回对象,则返回第一步的对象
    return typeof result  === 'object' ? result : child;
};
//创建实例,将构造函数Parent与形参作为参数传入
const child = newMethod(Parent, 'echo', 26);
child.sayName() //'echo';

闭包

闭包:函数套函数。

function f1(){
  let _n = 100;
  return function f2(){
    console.log(++_n);
  }
}
let fn=f1();
fn();
fn=null;//手动内存释放

NaN是什么?typeof NaN返回什么?

NaN 是 ‘not a number’ 的缩写,表示 “不是一个数字”。NaN 和任何变量都不相等,包括 NaN 自己。

typeof NaN //number
console.log(NaN === NaN); // false

JS 隐式转换,显示转换

"+" 操作符,如果有一个为字符串,那么都转化到字符串然后执行字符串拼接。
"-" 操作符,转换为数字,相减 (-a, a * 1 a/1) 都能进行隐式强制类型转换。

1+'1'='11'
1-'1'=0
1*'1'=1
1 + true = 2
1 + false = 1

js中defer和async的区别

defer (延迟脚本)
延迟脚本:defer属性只适用于外部脚本文件。
如果给script标签定义了defer属性,这个属性的作用是表明脚本在执行时不会影响页面的构造。也就是说,脚本会被延迟到整个页面都解析完毕后再运行。因此,如果script元素中设置了defer属性,相当于告诉浏览器立即下载,但延迟执行。

async(异步脚本)
异步脚本:async属性也只适用于外部脚本文件,并告诉浏览器立即下载文件。
但与defer不同的是:标记为async的脚本并不保证按照指定它们的先后顺序执行。

isPrototypeOf()和instanceof

instanceof运算符希望左操作数是一个对象,右操作数标识对象的类。如果左侧的对象是右侧类的实例,则表达式返回true;否则返回false。
isPrototypeOf检测一个对象是否是另一个对象的原型(或者处于原型链中)。

let d=new Date()
d instanceof Date //true

let p={x:1}
let o=Object.create(p)
p.isPrototypeOf(o) //true

function a(){}
let b=new a()
a.prototype.isPrototypeOf(b) //true

如何判断一个对象为空对象

Object.keys(obj).length===0

for in注意事项

let a={a:2,'33':5}
for(let i in a){console.log(a[i])}// 5 2 先返回String为数字的key导致返回顺序混乱
Object.prototype.pp="pp"
for(let i in a){console.log(a[i])}// 5 2 pp 会遍历原型链上的枚举

Object.create()和new object()的区别

Object.create(null) 创建的对象是一个空对象,在该对象上没有继承 Object.prototype 原型链上的属性或者方法,例如:toString(), hasOwnProperty()等方法。
new object()继承Object。

防抖和节流

防抖:函数变为最后一次执行。
节流:函数隔一段时间执行。

/*
* fn [function] 需要防抖的函数
* delay [number] 毫秒,防抖期限值
*/
function debounce(fn,delay){
    let timer = null //借助闭包
    return function() {
        if(timer){
            clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时
            timer = setTimeout(fn,delay) 
        }else{
            timer = setTimeout(fn,delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时
        }
    }
}

继承方式和优缺点

1.原型链继承

function Parent(){
  this.name = "parent";
  this.list = ['a'];
}
Parent.prototype.sayHi = function(){
  console.log('hi');
}
function Child(){

}
Child.prototype = new Parent();
var child = new Child();
console.log(child.name);
child.sayHi();

优点:可继承构造函数的属性,父类构造函数的属性,父类原型的属性
缺点:无法向父类构造函数传参;且所有实例共享父类实例的属性,若父类共有属性为引用类型,一个子类实例更改父类构造函数共有属性时会导致继承的共有属性发生变化

var a = new Child();
var b = new Child();
a.list.push('b');
console.log(b.list); // ['a','b']

2.构造函数继承

function Parent(name, id){
  this.id = id;
  this.name = name;
  this.printName = function(){
    console.log(this.name);
  }
}
Parent.prototype.sayName = function(){
  console.log(this.name);
};
function Child(name, id){
  Parent.call(this, name, id);
  // Parent.apply(this, arguments);
}
var child = new Child("jin", "1");
child.printName(); // jin
child.sayName() // Error

优点:可解决原型链继承的缺点
缺点:不可继承父类的原型链方法,构造函数不可复用

3.组合继承

function Parent(name, id){
  this.id = id;
  this.name = name;
  this.list = ['a'];
  this.printName = function(){
    console.log(this.name);
  }
}
Parent.prototype.sayName = function(){
  console.log(this.name);
};
function Child(name, id){
  Parent.call(this, name, id);
  // Parent.apply(this, arguments);
}
Child.prototype = new Parent();
var child = new Child("jin", "1");
child.printName(); // jin
child.sayName() // jin

var a = new Child();
var b = new Child();
a.list.push('b');
console.log(b.list); // ['a']

优点:可继承父类原型上的属性,且可传参;每个新实例引入的构造函数是私有的
缺点:会执行两次父类的构造函数,消耗较大内存,子类的构造函数会代替原型上的那个父类构造函数

4.原型式继承

var parent = {
  names: ['a']
}
function copy(object) {
  function F() {}
  F.prototype = object;    
  return new F();
}
var child = copy(parent);

缺点:共享引用类型

5.寄生式继承

function createObject(obj) {
  var o = copy(obj);
  o.getNames = function() {
    console.log(this.names);
    return this.names;
  }
  return o;
}

优点:可添加新的属性和方法

6.寄生组合式继承

function inheritPrototype(subClass, superClass) {
  // 复制一份父类的原型
  var p = copy(superClass.prototype);
  // 修正构造函数
  p.constructor = subClass;
  // 设置子类原型
  subClass.prototype = p;
}

function Parent(name, id){
  this.id = id;
  this.name = name;
  this.list = ['a'];
  this.printName = function(){
    console.log(this.name);
  }
}
Parent.prototype.sayName = function(){
  console.log(this.name);
};
function Child(name, id){
  Parent.call(this, name, id);
  // Parent.apply(this, arguments);
}
inheritPrototype(Child, Parent);

原型和原型链

原型:
1、所有引用类型都有一个[prototype]属性,属性值是一个普通的对象。
2、所有函数都有一个prototype(原型)属性,属性值是一个普通的对象。
3、所有引用类型的[[prototype]](属性指向它构造函数的prototype。

原型链:
当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的[[prototype]]隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的[[prototype]]中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。

数组能够调用的函数有那些?

push
pop
splice
slice
shift
unshift
sort
find
findIndex
map/filter/reduce 等函数式编程方法
还有一些原型链上的方法:toString/valudOf

如何判断数组类型

Array.isArray

函数中的arguments是数组吗?类数组转数组的方法了解一下?

是类数组,是属于鸭子类型的范畴,长得像数组。
Array.prototype.slice.apply(arguments)

PWA使用过吗?serviceWorker的作用?

PWA 全称为 Progressive Web App,中文译为渐进式 Web APP。
Service Worker的作用 :
1.网络代理,转发请求,伪造响应
2.离线缓存
3.消息推送
4.后台消息传递

箭头函数和普通函数有啥区别?箭头函数能当构造函数吗?

普通函数通过 function 关键字定义, this 无法结合词法作用域使用,在运行时绑定,只取决于函数的调用方式,在哪里被调用,调用位置。(取决于调用者,和是否独立运行)
箭头函数使用被称为 “胖箭头” 的操作 => 定义,箭头函数不应用普通函数 this 绑定的四种规则,而是根据外层(函数或全局)的作用域来决定 this,且箭头函数的绑定无法被修改(new 也不行)。
箭头函数常用于回调函数中,包括事件处理器或定时器
箭头函数和 var self = this,都试图取代传统的 this 运行机制,将 this 的绑定拉回到词法作用域
没有原型、没有 this、没有 super,没有 arguments,没有 new.target
不能通过 new 关键字调用
一个函数内部有两个方法:[[Call]] 和 [[Construct]],在通过 new 进行函数调用时,会执行 [[construct]] 方法,创建一个实例对象,然后再执行这个函数体,将函数的 this 绑定在这个实例对象上
当直接调用时,执行 [[Call]] 方法,直接执行函数体
箭头函数没有 [[Construct]] 方法,不能被用作构造函数调用,当使用 new 进行函数调用时会报错。

function foo() {
  return (a) => {
    console.log(this.a);
  }
}

var obj1 = {
  a: 2
}

var obj2 = {
  a: 3 
}

var bar = foo.call(obj1);
bar.call(obj2); //2

bind apply call区别

bind 返回的是一个新的函数,你必须调用它才会被执行。
call第二个参数是与方法一一对应。
apply第二个参数是数组。

var db={name:'Tom',age:22}
var obj={
   name:'Jerry',
   age:this.age,
   myFun(fm,t){
       console.log(`${this.name}年龄${this.age},来自${fm}去往${t}`)
   }
}
obj.myFun.call(db,'北京','上海')
obj.myFun.apply(db,['北京','上海'])
obj.myFun.bind(db,'北京','上海')()

js实现一个apply函数


// 思路:将要改变this指向的方法挂到目标this上执行并返回
Function.prototype.myapply = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('not funciton')
  }
  context = context || window
  context.fn = this
  let result
  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }
  delete context.fn
  return result

js柯里化函数

柯里化,英语:Currying(果然是满满的英译中的既视感),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。常用于把第一个参数返回值进行复用。

// 普通的add函数
function add(x, y) {
    return x + y
}

// Currying后
function curryingAdd(x) {
    return function (y) {
        return x + y
    }
}
add(1, 2) // 3
curryingAdd(1)(2)   // 3

js获取方法中的所有参数

function test(a,b,c,d) {
  Array.prototype.slice.call(arguments); //a b c d
}
   test("a","b","c","d");


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM