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