天天看點

2020前端基礎面試題

javascript篇

一、資料類型

JavaScript 有幾種類型

  • 基本資料類型:undefined、null、boolean、number、string、symbol(es6的新資料類型)
  • 引用資料類型:object、array、function(統稱為object)

資料類型檢測

typeof

 對于基本資料類型來說,除了 

null

 都可以顯示正确的類型,typeof 對于對象來說,除了函數都會顯示 objec

typeof 5 // 'number'
typeof '5' // 'string'
typeof undefined // 'undefined'
typeof false// 'boolean'
typeof Symbol() // 'symbol'
console.log(typeof null)  //object
console.log(typeof NaN)   //number

typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'
           

instanceof

通過原型鍊來判斷資料類型的

p1 = new Person()
p1 instanceof Person // true
           

Object.prototype.toString.call()可以檢測所有的資料類型,算是一個比較完美的方法了。

var obj={}
var arr=[]
console.log(Object.prototype.toString.call(obj))    //[object Object]
console.log(Object.prototype.toString.call(arr))    //[object Array]
           

深淺拷貝

淺拷貝

Object.assign()    //es6的方法
           

Object.assign會合并對象生成一個新對象。如果對象的屬性是普通類型改變之後新對象不會改變,如果是引用類型改變後新對象也會改變,是以Object.assign實際上還是淺拷貝。

var obj={aa:1,b:{item:'45'}};
var newObj=Object.assign({},obj);
obj.aa=2;
obj.b.item='kk';
console.log(newObj.aa);        //1
console.log(newObj.b.item);    //kk
           

深拷貝

JSON.parse(JSON.stringify(obj))

利用JSON.stringify(obj)将對象先轉為json字元串,再JSON.parse()轉回為json對象可以實作深拷貝,這也是比較常用的一種方法。

用js實作一個深拷貝

其實深拷貝可以拆分成 2 步,淺拷貝 + 遞歸,淺拷貝時判斷屬性值是否是對象,如果是對象就進行遞歸操作,兩個一結合就實作了深拷貝。

function cloneDeep(source) {
      if (!isObject(source)) return source; // 非對象傳回自身
      var target = Array.isArray(source) ? [] : {};
      for (var key in source) {
        if (source.hasOwnProperty(i)) {
          if (isObject(source[key])) {
            target[key] = cloneDeep(source[key]); // 注意這裡
          } else {
            target[key] = source[key];
          }
        }
      }
      return target;
    }
    function isObject(obj) {
      return typeof obj === 'object' && obj != null;
    }
           

二、作用域

變量聲明提升

  • 在 JavaScript 中,函數聲明(function aa(){})與變量聲明(var)經常被 JavaScript 引擎隐式地提升到目前作用域的頂部。
  • 函數聲明的優先級高于變量,如果變量名跟函數名相同且未指派,則函數聲明會覆寫變量聲明
  • 聲明語句中的指派部分并不會被提升,隻有變量的名稱被提升

作用域鍊

因為函數的嵌套形成作用域的層級關系。當函數執行時,從目前作用域開始搜,沒有找到的變量,會向上層作用域查找,直至全局函數,這就是作用域鍊。

  • 在 JavaScript 中,作用域為 function(){}内的區域,稱為函數作用域。
  • 全局函數無法檢視局部函數的内部細節,但局部函數可以檢視其上層的函數細節,直至全局細節

閉包

閉包的原理就是作用域鍊,比函數F内部有一個函數G,函數 G可以通路到函數F中的變量,那麼函數G就是閉包。

function F() {
      let a = 1
      window.G = function () {
        console.log(a)
      }
    }
    F()
    G() // 1
           

三、原型和繼承

js 建立對象的幾種方式

對象字面量的方

var obj={};
           

new一個構造函數

function Pel(){}
    var p=new Pel();
    p.name="hu";
    p.age="25";
    p.address=function(){
 }
           

new一個内置對

var obj=new Object();
           

Object.create()建立對象

var test = Object.create({x:1});
           

給大家留一道思考題,new Object() 、 Object.create()、{},這三種方式建立對象有什麼差別。

JS 如何實作一個類

構造函數法

缺點:用到了 this 和 prototype,編寫複雜,可讀性差

function P(name, age){
     this.name = name;
     this.age= age;
   }
   P.prototype.sal= function(){
      
   }
   var pel= new P("jj", 1);
   pel.sell()
           

ES6 文法糖 class

class Point {
       constructor(x, y) {
         this.x = x;
         this.y = y;
       }
       toString() {
         return '(' + this.x + ', ' + this.y + ')';
       }
     }
  var point = new Point(2, 3);
           

原型鍊

一句話解析什麼是原型鍊

周遊一個實列的屬性時,先周遊實列對象上的屬性,再周遊它的原型對象,一直周遊到Object

任何一個類(函數)都有原型對象,原型對象至少有兩個屬性(constructor,proto)。constructor指向函數本身,proto指向父類原型對象。

函數上有一個prototype屬性,指向原型對象,通過它可以通路原型對象

函數的實列可以直接通路原型對象(因為實列上有proto指向構造函數的原型對象)

function Dog(){}        //類         
var obj=new Dog();      //實列
obj.name='滬江';
Dog.prototype.name="旺财";
Dog.prototype.eat=function(){
    console.log(this.name);
};
console.log(Dog.prototype.name);  //旺财
console.log(obj.prototype);      //undefined,prototype是類上才有的,實列上沒有
obj.eat();                       //滬江(先周遊實列對象上的屬性,再周遊它的原型對象)
           

繼承

Js如何實作繼承?

構造函數綁定:使用 call 或 apply 方法,将父對象的構造函數綁定在子對象上

function Cat(name,color){
  Animal.apply(this, arguments);
  this.name = name;
  this.color = color;
}
           

執行個體繼承:将子對象的 prototype 指向父對象的一個執行個體

Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
           

拷貝繼承:如果把父對象的所有屬性和方法,拷貝進子對象

function extend(Child, Parent) {
   var p = Parent.prototype;
   var c = Child.prototype;
   for (var i in p) {
      c[i] = p[i];
   }
   c.uber = p;
}
           

原型繼承:将子對象的 prototype 指向父對象的 prototype

function extend(Child, Parent) {
    var F = function(){};
     F.prototype = Parent.prototype;
     Child.prototype = new F();
     Child.prototype.constructor = Child;
     Child.uber = Parent.prototype;
}
           

ES6 文法糖 extends繼承

class ColorPoint extends Point {
    constructor(x, y, color) {
        super(x, y); // 調用父類的constructor(x, y)
        this.color = color;
    }
    toString() {
        return this.color + ' ' + super.toString(); // 調用父類的toString()
    }
}
           

四、new和this

new 操作符具體幹了什麼?

當我們new一個資料的時候,new操作符到底做了什麼?
  • 首先是建立執行個體對象{}
  • this 變量引用該對象,同時還繼承了構造函數的原型
  • 其次屬性和方法被加入到 this 引用的對象中
  • 并且新建立的對象由 this 所引用,最後隐式的傳回 this

new的模拟實作

function objectFactory() {

    var obj = new Object(),//從Object.prototype上克隆一個對象

    Constructor = [].shift.call(arguments);//取得外部傳入的構造器

    var F=function(){};
    F.prototype= Constructor.prototype;
    obj=new F();//指向正确的原型

    var ret = Constructor.apply(obj, arguments);//借用外部傳入的構造器給obj設定屬性

    return typeof ret === 'object' ? ret : obj;//確定構造器總是傳回一個對象

};
           

this 對象的了解

普通函數

  • this 總是指向函數的直接調用者
  • 如果有 new 關鍵字,this 指向 new 出來的執行個體對象
  • 在事件中,this 指向觸發這個事件的對象
  • IE 下 attachEvent 中的 this 總是指向全局對象 Window
  • 箭頭函數中,函數體内的this對象,就是定義時所在作用域的對象,而不是使用時所在的作用域的對象。
function foo() {
  console.log(this.a)
}
var a = 1
foo()           //1       
​
const obj = {
  a: 2,
  foo: foo
}
obj.foo()      //2
​
const c = new foo()   //undefined
           
  • 對于直接調用 foo 來說,不管 foo 函數被放在了什麼地方,this 一定是window
  • 對于 obj.foo() 來說,我們隻需要記住,誰調用了函數,誰就是 this,是以在這個場景下 foo 函數中的 this 就是 obj 對象
  • 對于 new 的方式來說,this 被永遠綁定在了 new出來的對象上,不會被任何方式改變 this

說完了以上幾種情況,其實很多代碼中的 this 應該就沒什麼問題了,下面讓我們看看箭頭函數中的 this

function a() {
  return () => {
    return () => {
      console.log(this)
    }
  }
}
a()()()        //Window
           
  • 首先箭頭函數其實是沒有 this 的,箭頭函數中的 this 隻取決包裹箭頭函數的第一個普通函數的 this。在這個例子中,因為包裹箭頭函數的第一個普通函數是 a,是以此時的 this 是 window。另外對箭頭函數使用 bind這類函數是無效的。

五、apply、call、bind

call

apply

bind

Function

對象自帶的三個方法,都是為了改變函數體内部 

this

 的指向。

apply 、 call 、bind

 三者第一個參數都是 

this

 要指向的對象,也就是想指定的上下文;

apply 、 call 、bind

 三者都可以利用後續參數傳參;

bind 是傳回對應 函數,便于稍後調用;

apply 、call

 則是立即調用 。

function fruits() {}

fruits.prototype = {
	color: 'red',
	say: function() {
		console.log(this.color);
	}
};

var apple = new fruits();

apple.say();   // red, 此時方法裡面的this 指的是fruits

banana = {color: 'yellow'};
apple.say.call(banana); //yellow,此時的this的指向已經通過call()方法改變了,指向的是banana,this.color就是banana.color='yellow';

apple.say.apply(banana);//yellow,同理,此時的this的指向已經通過apply()方法改變了,指向的是banana,this.color就是banana.color ='yellow';

apple.say.apply(null); //undefined, null是window下的,此時,this 就指向了window ,但是window下并沒有clolr這個屬性,是以this.clolr就是window.color=undefined;
           

call 傳入參數清單

apply 傳入數組

var array1 = [12,'foo'];
var array2 = ['Doe',100];

Array.prototype.push.call(array1, 'Doe',100)
Array.prototype.push.apply(array1, array2)
           

bind() 方法會建立一個 新函數,當調用這個新函數時,新函數會以建立它時傳入 bind() 方法的第一個參數 作為 this,傳入 bind() 方法的 第二個以及以後的參數加上綁定函數運作時本身的參數按照順序作為原函數的參數來調用原函數。

var bar = function(){
	console.log(this.x);
};
var foo = {
	x:3
};
bar();    // undefined
var func = bar.bind(foo); 

func(); // 3
           

六、資料處理

數組去重

var arr=['12','32','89','12','12','78','12','32'];
    // 最簡單數組去重法
    function unique1(array){
        var n = []; //一個新的臨時數組
        for(var i = 0; i < array.length; i++){ //周遊目前數組
            if (n.indexOf(array[i]) == -1)
                n.push(array[i]);
        }
        return n;
    }
    arr=unique1(arr);
           

排序

/**
	 * 按 sort 及  id 排序
	 * @param {Object} a
	 * @param {Object} b
	 */
 function   sortFun(a, b) {
       return a.sort - b.sort == 0 ? a.id - b.id : a.sort - b.sort
  };
 arr.sort(sortFun)   //從小到大排序
           

遞歸求和

function add(num1,num2){
	var num = num1+num2;
		if(num2+1>100){
		   return num;
	         }else{
	            return add(num,num2+1)
	        }		   	
     }
     var sum =add(1,2)
           

計算數組各項的重複次數

var arr=['胡将','胡将','hujiang','胡将','胡江','hujiang'];
var obj={};
arr.sort();    //先排序
for(var i=0;i<arr.length;){
	var con=0;
	for(var j=i;j<arr.length;j++){
		if(arr[i]===arr[j]){
			con++
		}
	}
	obj[arr[i]]=con; 
	i=i+con;    //跳過重複的值
}
console.log(obj);  //{ hujiang: 2, '胡将': 3, '胡江': 1 }
           

七、Event Loop

宏任務/微任務

除了廣義的同步任務和異步任務,我們對任務有更精細的定義:

  1. macro-task(宏任務):目前調用棧中執行的任務稱為宏任務。包括:script全部代碼、setTimeout、setInterval、setImmediate(浏覽器暫時不支援,隻有IE10支援,具體可見MDN)、I/O、UI Rendering。
  2. .micro-task(微任務): 目前(此次事件循環中)宏任務執行完,在下一個宏任務開始之前需要執行的任務為微任務。包括:Process.nextTick(Node獨有)、Promise、Object.observe(廢棄)、MutationObserver
  3. 不同類型的任務會進入對應的Event Queue,宏任務中的事件放在callback queue中,由事件觸發線程維護;微任務的事件放在微任務隊列中,由js引擎線程維護。

一句話解析下什麼是event loop

  • 主線程運作的時候會生成堆(heap)和棧(stack);
  • js 從上到下解析方法,将其中的同步任務按照執行順序排列到執行棧中;
  • 當程式調用外部的 API 時(比如 ajax、setTimeout 等),會将此類異步任務挂起,繼續執行執行棧中的任務。等異步任務傳回結果後,再按照順序排列到事件隊列中;
  • 主線程先将執行棧中的同步任務清空,然後檢查事件隊列中是否有任務,如果有,就将第一個事件對應的回調推到執行棧中執行,若在執行過程中遇到異步任務,則繼續将這個異步任務排列到事件隊列中。
  • 主線程每次将執行棧清空後,就去事件隊列中檢查是否有任務,如果有,就每次取出一個推到執行棧中執行,這個循環往複的過程被稱為“Event Loop 事件循環”

八、浏覽器頁面渲染過程

浏覽器渲染頁面的一般過程:

1.浏覽器解析html源碼,然後建立一個 DOM樹。并行請求 css/image/js在DOM樹中,每一個HTML标簽都有一個對應的節點,并且每一個文本也都會有一個對應的文本節點。DOM樹的根節點就是 documentElement,對應的是html标簽。

2.浏覽器解析CSS代碼,計算出最終的樣式資料。建構CSSOM樹。對CSS代碼中非法的文法它會直接忽略掉。解析CSS的時候會按照如下順序來定義優先級:浏覽器預設設定 < 使用者設定 < 外鍊樣式 < 内聯樣式 < html中的style。

3.DOM Tree + CSSOM --> 渲染樹(rendering tree)。渲染樹和DOM樹有點像,但是是有差別的。

DOM樹完全和html标簽一一對應,但是渲染樹會忽略掉不需要渲染的元素,比如head、display:none的元素等。而且一大段文本中的每一個行在渲染樹中都是獨立的一個節點。渲染樹中的每一個節點都存儲有對應的css屬性。

4.一旦渲染樹建立好了,浏覽器就可以根據渲染樹直接把頁面繪制到螢幕上。

以上四個步驟并不是一次性順序完成的。如果DOM或者CSSOM被修改,以上過程會被重複執行。實際上,CSS和JavaScript往往會多次修改DOM或者CSSOM。

css篇

一、盒模型

盒模型的組成,由裡向外content,padding,border,margin.

在IE盒子模型中,width表示content+padding+border這三個部分的寬度

在标準的盒子模型中,width指content部分的寬度

box-sizing的使用

box-sizing: content-box 是W3C盒子模型
  box-sizing: border-box 是IE盒子模型
           

box-sizing的預設屬性是content-box

二、居中

水準居中

  • 行内元素: 

    text-align: center

  • 塊級元素: 

    margin: 0 auto

  • position:absolute +left:50%+ transform:translateX(-50%)
  • display:flex + justify-content: center

垂直居中

  • 設定line-height 等于height
  • position:absolute +top:50%+ transform:translateY(-50%)
  • display:flex + align-items: center

  • display:table+display:table-cell + vertical-align: middle;

//不知道寬高

width: 78px;
  height: 78px;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translateX(-50%) translateY(-50%);
           

//知道寬高

height: 100px;
        width: 100px;
        position: absolute;
        left:50%;
        top: 50%;
        margin-left: -50px;
        margin-top: -50px;
        display:flex;
        justify-content: center;
        align-content: center;
           

關鍵詞:前端教育訓練

繼續閱讀