2017前端面試題及答案總結|掘金技術徵文

Yeaseon_Zhang發表於2019-03-04

作者: Yeaseon
Blog:yeaseonzhang.github.io
原文連結

“金三銀四,金九銀十”,用來形容求職最好的幾個月。但是隨著行業的飽和,初中級前端er就業形勢不容樂觀。

行業狀態不可控,我們能做的當然只是讓自己變得更加具有競爭力。

今年自己也用了幾個月的時間來準備筆記面試,鞏固基礎知識。特此將自己在這個過程總結的題目分享出來,希望對於求職和準備求職的同學有所幫助。

CSS

列舉不同的清除浮動的技巧

/* 1.新增新元素 */
<div class="outer">
  <div class="div1"></div>
  <div class="div2"></div>
  <div class="div3"></div>
  <div class="clearfix"></div>
</div>
.clearfix {
  clear: both;
}

/* 2.為父元素增加樣式 */
.clearfix {
  overflow: auto;
  zoom: 1; // 處理相容性
}

/* 3.:after 偽元素方法 (作用於父元素) */
.outer {
  zoom: 1;
  &:after {
    display: block;
    height: 0;
    clear: both;
    content: `.`;
    visibillity: hidden;
  }
}複製程式碼

一畫素邊框

使用sass語法。

/* 定義 */
@mixin border-1px ($color) {
    position: relative;
    &:after {
        display: block;
        position: absolute;
        left: 0;
        bottom: 0;
        width: 100%;
        border-top: 1px solid $color;
        context: ``;
    }
}

@media (-webkit-min-device-pixel-radio: 1.5), (min-device-pixel-radio: 1.5) {
    border-1px {
        &:after {
            -webkit-transform: scaleY(0.7);
            transform: scaleY(0.7);
        }
    }
}

@media (-webkit-min-device-pixel-radio: 2), (min-device-pixel-radio: 2) {
    border-1px {
        &:after {
            -webkit-transform: scaleY(0.5);
            transform: scaleY(0.5);
        }
    }
}

/* 使用方式 */
@inclue border-1px(rgba(7, 17, 27, .1));複製程式碼

形成BFC(Block Formatting Context)的幾種方式

BFC全稱”Block Formatting Context”, 中文為“塊級格式化上下文”。BFC元素特性表現原則就是,內部子元素再怎麼翻江倒海,翻雲覆雨都不會影響外部的元素。

  • floatleft|right
  • overflowhidden|auto|scroll
  • displaytable-cell|table-caption|inline-block
  • positionabsolute|fixed

佈局

/* HTML */
<div class="container">
  <div class="left">Left silder</div>
  <div class="content">Main content</div>
</div>

/* CSS */
.container {
  overflow: hidden;
}

.left {
  float: left;
  width: 200px;
  margin-bottom: -9999px;
  padding-bottom: 9999px;
  background-color: #eee;
}

.content {
  margin-left: 200px;
  margin-bottom: -9999px;
  padding-bottom: 9999px;
  background-color: #ccc;
}

.left, .content {
  min-height: 200px;
  height: auto !important;
}複製程式碼

JS

asyncdefer區別

非同步(async) 指令碼將在其載入完成後立即執行,而 延遲(defer) 指令碼將等待 HTML 解析完成後,並按載入順序執行。

location.replace()location.assign()區別

location.replace()的url不會出現在history中

new操作符

  • 建立一個空物件,並且this變數引用該物件,同時還繼承了 該函式的原型
  • 屬性和方法被加入到this引用的物件中
  • 新建立的物件由this所引用,並且最後隱式的返回this

AMD CMD CommonJS

/* AMD是RequireJS對模組化的定義
 * CMD是seaJS對模組化的定義
 * CommonJS是Node對模組化的規範
 **/

/* AMD 依賴關係前置 */
define([`./a`, `./b`], function (a, b) {
    a.something();
    b.something();
})

/* CMD 按需載入,依賴就近 */
define(function (require, exports, module) {
    var a = require(`./a`);
    a.something();

    var b = require(`./b`);
    b.something();
})複製程式碼

DOM 操作

// 建立節點
createDocumentFragment()
createElement()
createTextNode()

// 新增 移除 替換 插入
appendChild()
removeChild()
replaceChild()
insertBefore()

// 查詢
getElementsByTagName()
getElementsByName()
getElementsByClassName()
getElementById()
querySelector()
querySelectorAll()複製程式碼

JS設定css樣式的幾種方式

/* 1.直接設定style屬性 */
element.style.height = `100px`;

/* 2.直接設定屬性 */
element.setAttribute(`height`, `100px`);

/* 3.使用setAttribute設定style屬性 */
element.setAttribute(`style`, `height: 100px !important`);

/* 4.使用setProperty設定屬性,通過第三個引數設定important */
element.style.setProperty(`height`, `300px`, `important`);

/* 5.設定cssText */
element.style.cssText += `height: 100px !important`;複製程式碼

阻止預設行為

function stopDefault( e ) {
    // 阻止預設瀏覽器動作(W3C)
    if ( e && e.preventDefault ) {
        e.preventDefault();
    } else {
        // IE中阻止函式器預設動作的方式
        window.event.returnValue = false;
    }
    return false;
}複製程式碼

阻止冒泡

function stopBubble(e) {
    // 如果提供了事件物件,則這是一個非IE瀏覽器
    if ( e && e.stopPropagation ) {
        // 因此它支援W3C的stopPropagation()方法
        e.stopPropagation();
    } else {
        // 否則,我們需要使用IE的方式來取消事件冒泡
        window.event.cancelBubble = true;
    }
}複製程式碼

Ajax互動過程

  • 建立XMLHttpRequest物件,也就是建立一個非同步呼叫物件.
  • 建立一個新的HTTP請求,並指定該HTTP請求的方法、URL及驗證資訊.
  • 設定響應HTTP請求狀態變化的函式.
  • 傳送HTTP請求.
  • 獲取非同步呼叫返回的資料.
  • 使用JavaScript和DOM實現區域性重新整理.

考察知識點最廣的JS面試題

function Foo() {
    getName = function () { alert(1); }
    return this;
}
Foo.getName = function () { alert(2); }
Foo.prototype.getName = function () { alert(3); }
var getName = function () { alert(4); }
function getName () { alert(5); }

/* 寫出輸出 */
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();複製程式碼

具體講解參見一道常被人輕視的前端JS面試題

JS陣列深淺拷貝

  • slice實現
var arr = [`old`, 1, true, null, undefined];

var new_arr = arr.slice();

new_arr[0] = `new`;

console.log(arr) // ["old", 1, true, null, undefined]
console.log(new_arr) // ["new", 1, true, null, undefined]複製程式碼
  • concat實現
var arr = [`old`, 1, true, null, undefined];

var new_arr = arr.concat();

new_arr[0] = `new`;

console.log(arr) // ["old", 1, true, null, undefined]
console.log(new_arr) // ["new", 1, true, null, undefined]複製程式碼

以上兩種方法只是淺拷貝,如果陣列元素是基本型別,就會拷貝一份新的;但是如果陣列元素是物件或者陣列,就只會拷貝引用(類似指標),修改其中一個就會影響另外一個。

var arr = [`old`, 1, true, [`old1`, `old2`], {old: 1}];

var new_arr = arr.concat();

new_arr[0] = `new`;
new_arr[3][0] = `new1`;

console.log(arr) // ["old", 1, true, [`new1`, `old2`], {old: 1}]
console.log(new_arr) // ["new", 1, true, [`new1`, `old2`], {old: 1}]複製程式碼
  • JSON.stringify實現陣列深拷貝
var arr = [`old`, 1, true, [`old1`, `old2`], {old: 1}];

var new_arr = JSON.parse(JSON.stringify(arr));

new_arr[0] = `new`;
new_arr[3][0] = `new1`;

console.log(arr) // ["old", 1, true, [`old1`, `old2`], {old: 1}]
console.log(new_arr) // ["new", 1, true, [`new1`, `old2`], {old: 1}]複製程式碼

簡單粗暴,但是問題是不能拷貝函式,不推薦。

然後我們來手動實現深淺拷貝。

  • 淺拷貝
var shallowCopy = function (obj) {
    // 判斷是否是陣列或者物件
    if (typeof obj !== `object`) {
        return
    }
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}複製程式碼
  • 深拷貝
var deepCopy = function (obj) {
    if (typeof obj !== `object`) {
        return
    }
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === `object` ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj
}複製程式碼

陣列去重

  • filter + indexOf
function unique (arr) {
    var res = arr.filter(function (item, index, array) {
        return array.indexOf(item) === index;
    })
    return res;
}複製程式碼
  • filter + sort
function unique (arr) {
    return arr.concat().sort().filter(function (item, index, array) {
        return !index || item !== array[index - 1];
    })
}複製程式碼
  • ES6
function uniqu3 (arr) {
    return [... new Set(arr)];
}複製程式碼

找出陣列中的最大值

  • reduce
var arr = [6, 4, 1, 8, 2, 11, 3];

function max (prev, next) {
    return Math.max(prev, next)
}

console.log(arr.reduce(max));複製程式碼
  • apply
var arr = [6, 4, 1, 8, 2, 11, 3];

console.log(Math.max.apply(null, arr));複製程式碼
  • ES6
var arr = [6, 4, 1, 8, 2, 11, 3];

function max (arr) {
    return Math.max(...arr);
}

console.log(max(arr));複製程式碼

打亂陣列的方法

var arr = [];
for (var i = 0; i < 100; i++) {
    arr[i] = i;
}

arr.sort(function () {
    return 0.5 - Math.random();
});複製程式碼

陣列扁平化

var arr = [1, [2, [3, 4]]];

function flatten(arr) {

    while (arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
    }

    return arr;
}

console.log(flatten(arr))複製程式碼

排序

// 冒泡
function bubbleSort2(arr) {
    var len = arr.length;
    for (var i = 0; i <= len - 1; i++) {
        for (var j = 0; j <= len - i; j++) {
            if (arr[j + 1] < arr[j]) {
                var temp;
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
    return arr;
}

// 快速排序
function quickSort(arr) {
    if(arr.length == 0) {
        return [];    // 返回空陣列
    }

    var cIndex = Math.floor(arr.length / 2);
    var c = arr.splice(cIndex, 1);
    var l = [];
    var r = [];

    for (var i = 0; i < arr.length; i++) {
        if(arr[i] < c) {
            l.push(arr[i]);
        } else {
            r.push(arr[i]);
        }
    }

    return quickSort(l).concat(c, quickSort(r));
}複製程式碼

數字格式化 1234567890 -> 1,234,567,890

function formatNum (num) {
    return num.replace(/B(?=(d{3})+(?!d))/g, `,`);
}
var num = `1234567890`;
var res = formatNum(num);
console.log(res);複製程式碼

打亂陣列的方法

var arr = [];
for (var i = 0; i < 100; i++) {
    arr[i] = i;
}

arr.sort(function () {
    return 0.5 - Math.random();
})複製程式碼

尾呼叫優化

即只儲存內層函式的呼叫幀(只有開啟嚴格模式,才會生效),只有不再用到外層函式的內部變數,內層函式的呼叫幀才會取代外層函式的呼叫幀,否則無法進行“尾呼叫優化。

function factorial(n) {
    if (n === 1) return 1;
    return n * factorial(n-1);
}
factorial(5)

/* 優化尾遞迴 */
function factorial(n, total) {
    if (n === 1) return total;
    return factorial(n - 1, n * total);
}
factorial(5, 1)複製程式碼

柯里化

實現add(1,2)add(1)(2)均輸出3

function add () {
    let sum = 0;
    Array.prototype.forEach.call(arguments, function (item, index){
        if (typeof item !== `number`) {
            return false;
        } else {
            sum += item;
        }
    })
    var tmp = function () {
        Array.prototype.forEach.call(arguments, function (item, index){
            if (typeof item !== `number`) {
                return false;
            } else {
               sum += item;
            }
        })
        return tmp;
    }
    tmp.toString = function () {
        return sum
    }
    return tmp;
}
add(1, 2); // 3
add(1)(2); // 3
add(1, 2, 3)(1, 4)(2, 2)(1) // 16複製程式碼

ES8 新特性

  • 字串填充
str.padStart(targetLength [, padString])
str.padEnd(targetLength [, padString])複製程式碼
  • valuesentries函式
Object.values(obj)
Object.entries(obj)複製程式碼
  • getOwnPropertyDescriptors函式
Object.getOwnPropertyDescriptors(obj)複製程式碼
  • 函式引數逗號結尾
function es8(var1, var2, var3,) {}複製程式碼
  • 非同步函式
    async關鍵詞定義的函式宣告瞭一個可以非同步執行的函式,返回一個AsyncFunction型別的物件。
fucntion fetchTextByPromise () {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(`es8`);
        }, 2000);
    });
}
async function sayHello () {
    const externalFetchedText = await fetchTextByPromise();
    console.log(`Hello, ${externalFetchedText}`);
}
sayHello();複製程式碼

資料型別判斷

var class2type = {};

`Boolean Number String Function Array Date RegExp Object Error Null Undefined`.split(` `).map((item, index) => {
    class2type[`[object ` + item + `]`] = item.toLowerCase();
})

function type (obj) {
    return typeof obj === `object` || typeof obj === `function` ?
        class2type[{}.toString.call(obj)] || `object` : typeof obj;
}複製程式碼

防抖

/*
 * func:需要呼叫的函式
 * wait: 防抖時間
 * immediate:布林值,是否立即執行
 **/

var debounce = function (func, wait, immediate) {
  var timeout;
  return function () {
    var context = this;
    var args = arguments;

    if (timeout) clearTimeout(timeout);

    if (immediate) {  // 是否立即執行func
      var callNow = !timeout;
      timeout = setTimeout(function () {
        timeout = null;
      }, wait);

      if (callNow) {
        func.apply(context, args);
      }
    } else {
      timeout = setTimeout(function () {
        func.apply(context, args);
      }, wait);
    }
  }
}複製程式碼

簡單的字串模板

var TemplateEngine = function(tpl, data) {
    var re = /<%([^%>]+)?%>/g, match;
    while(match = re.exec(tpl)) {
        tpl = tpl.replace(match[0], data[match[1]])
    }
    return tpl;
}

var template = `<p>Hello, my name is <%name%>. I`m <%age%> years old.</p>`;
console.log(TemplateEngine(template, {
    name: "Yeaseon",
    age: 24
}));

// `<p>Hello, my name is Yeaseon. I`m 24 years old.</p>`;複製程式碼

applycallbind

  • applycall

在嚴格模式下,未指定環境物件而呼叫函式,則this 值不會轉型為window。除非明確把函式新增到某個物件或者呼叫apply()或call(),否則this 值將是undefined。

在非嚴格模式下,call、apply的第一個引數傳遞為null或undefined時,函式體內的this會指向預設的宿主物件,在瀏覽器中則是window。

  • applycallbind比較
var obj = {
    x: 81
};

var foo = {
    getX: function () {
        return this.x;
    }
}

console.log(foo.getX.bind(obj)());
console.log(foo.getX.apply(obj));
console.log(foo.getX.call(obj));複製程式碼

很明顯,bind方法後面多了一對括號。也就是說,當你希望改變上下文環境之後並且立即執行,而是回撥執行的時候(多用於事件監聽器函式),使用bind()方法,而apply/call則會立即執行函式。

  • 定義一個 log 方法,讓它可以代理 console.log 方法。
function log(){
  console.log.apply(console, arguments);
};
log(1);    //1
log(1,2);    //1 2複製程式碼
  • 給每一個 log 訊息新增一個”(app)”的前輟,比如:
log("hello world");    //(app)hello world複製程式碼
function log(){
  var args = Array.prototype.slice.call(arguments);
  args.unshift(`(app)`);
  console.log.apply(console, args);
};複製程式碼
  • apply實現bind
function bind (fn, context) {
    return function () {
        return fn.apply(context, argument);
    }
}複製程式碼

建立物件

  • 工廠模式
function createPerson(name, age, job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        alert(this.name);
    };
    return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");複製程式碼
  • 建構函式模式
function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert(this.name);
    };
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");複製程式碼
  • 原型模式
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"複製程式碼
  • 建構函式 + 原型模式
function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}
Person.prototype = {
    constructor : Person,
    sayName : function(){
        alert(this.name);
    }
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true複製程式碼

JS實現Jquery的extend()方法

function extend() {
    // 預設不進行深拷貝
    var deep = false;
    var name, options, src, copy, clone, copyIsArray;
    var length = arguments.length;
    // 記錄要複製的物件的下標
    var i = 1;
    // 第一個引數不傳佈爾值的情況下,target 預設是第一個引數
    var target = arguments[0] || {};
    // 如果第一個引數是布林值,第二個引數是 target
    if (typeof target == `boolean`) {
        deep = target;
        target = arguments[i] || {};
        i++;
    }
    // 如果target不是物件,我們是無法進行復制的,所以設為 {}
    if (typeof target !== "object" && !isFunction(target)) {
        target = {};
    }

    // 迴圈遍歷要複製的物件們
    for (; i < length; i++) {
        // 獲取當前物件
        options = arguments[i];
        // 要求不能為空 避免 extend(a,,b) 這種情況
        if (options != null) {
            for (name in options) {
                // 目標屬性值
                src = target[name];
                // 要複製的物件的屬性值
                copy = options[name];

                // 解決迴圈引用
                if (target === copy) {
                    continue;
                }

                // 要遞迴的物件必須是 plainObject 或者陣列
                if (deep && copy && (isPlainObject(copy) ||
                        (copyIsArray = Array.isArray(copy)))) {
                    // 要複製的物件屬性值型別需要與目標屬性值相同
                    if (copyIsArray) {
                        copyIsArray = false;
                        clone = src && Array.isArray(src) ? src : [];

                    } else {
                        clone = src && isPlainObject(src) ? src : {};
                    }

                    target[name] = extend(deep, clone, copy);

                } else if (copy !== undefined) {
                    target[name] = copy;
                }
            }
        }
    }

    return target;
};複製程式碼

自定義事件(通過觀察者模式)

function EventTarget () {
    this.handlers = {};
}

EventTarget.prototype = {
    constructor: EventTarget,
    addHandler: function (type, handler) {
        if (typeof this.handlers[type] == `undefined`) {
            this.handlers[type] = [];
        }
        this.handlers[type].push(handler)
    },
    fire: function (event) {
        if (!event.target) {
            event.target = this;
        }
        if (this.handlers[event.type] instanceof Array) {
            var handlers = this.handlers[event.type];
            for (var i = 0, len = handlers.length; i < len; i++) {
                handlers[i](event);
            }
        }
    },
    removeHandler: function (type, handler) {
        if (this.handlers[type] instanceof Array) {
            var handlers = this.handlers[type];
            for (var i = 0, len = handlers.length; i < len; i++) {
                if (handlers[i] === handler) {
                    break;
                }
            }
            handlers.splice(i, 1);
        }
    }
}複製程式碼

安全

跨域的幾種方法

  1. 主域相同的跨域
document.domain複製程式碼
  1. window.postMessage

  2. JSONP跨域(只支援GET)

function todo(data){
  console.log(`The author is: `+ data.name);
}

var script = document.createElement(`script`);
/* 向伺服器www.yeaseonzhang.com發出請求。注意,該請求的查詢字串有一個callback引數,用來指定回撥函式的名字。 */
script.src = `http://www.yeaseonzhang.com/author?callback=todo`;
document.body.appendChild(script);
/* 伺服器收到這個請求以後,會將資料放在回撥函式的引數位置返回。 */
todo({"name": "fewjq"});
/* 由於<script>元素請求的指令碼,直接作為程式碼執行。這時,只要瀏覽器定義了todo函式,該函式就會立即呼叫。作為引數的JSON資料被視為JavaScript物件。*/複製程式碼
  1. websocket跨域

XSS 和 CSRF

效能

CSS 優化

  • 正確的時機呼叫CSS
  • 使用媒體查詢標記<link>,選擇性載入
  • 減少css檔案數量
  • 壓縮css程式碼

渲染

相關文章