js面試手寫+編程題

文章目錄

工具類

實現防抖函數(debounce)

const debounce = (fn, delay) => {
	let timer = null;
	return (...args) => {
		clearTimeout(timer);
		timer = setTimeout( () => {
			fn.apply(this, args);
		}, delay);
	};
};

當無法使用定時器時,使用時間戳(時間間隔),同樣可以實現類似效果

//獲取時間:
function show(){
	var time1=new Date()
	var time2=Date.now() //比較兩次時間的間隔
	console.log(“time1—”+time1)
	console.log(“time2—”+time2)
}
//輸出----
time1—Tue Jan 28 2020 14:51:51 GMT+0800 (中國標準時間)
time2—1580194311067

實現節流函數(throttle)

const throttle = (fn, delay = 500) => {
	let flag = true;
	return (...args) => {
		if(!flag) return;
		flag = false;
		setTimeout(() => {
			fn.apply(this, args);
		}, delay);
	};
};

實現深克隆(deepclone)

function isArray (arr) {
	return Object.prototype.toString.call(arr) == '[object Array]';
}
//深度克隆
if(typeof obj !== "object" && typeof obj !== 'function'){
	return obj ;//原始類型直接返回
}
var o = isArray(obj) ? [] : {};
for(i in obj) {
	if(obj.hasOwnProperty(i)){
		o[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i];
	}
	return o;
}

迭代器Iterator

設計一個機制,可以多次迭代,每次調用next()方法,可以返回一個結果對象

{
	value:值,
	done:布爾值,表示是否已經結束了
}

封裝一個迭代器:next()可以看見 i,所以要閉包

生成器 Generator(加*函數):能夠返回一個迭代器
在生成器中,yield表示一次迭代的結果。這個結果將在迭代器調用next的時候,在返回的對象的value屬性中顯示
yield 每步產生的結果
加星函數的yield的東西,可以使Promise對象,在主程序中調用iterator.next()的時候,會等待Promise()對象的狀態從pending狀態轉爲其他狀態
在這裏插入圖片描述
在這裏插入圖片描述

dva中有生成器

瀑布流

在這裏插入圖片描述
放第二排的時候,找第一排最小高度那一張,放在它下面,而不是從左往右依次放。

問題

請用原生js實現一個函數,給頁面制定的任意一個元素添加一個透明遮罩(透明度可變,默認0.2),使這個區域點擊無效,要求兼容IE8+及各主流瀏覽器,遮罩層效果如下圖所示:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-V5R34Xmu-1584095000199)(img/element-mask.jpg)]

<style>
#target {
    width: 200px;
    height: 300px;
    margin: 40px;
    background-color: tomato;
}
</style>

<div id="target"></div>

<script>
function addMask(elem, opacity) {
    opacity = opacity || 0.2;

    var rect = elem.getBoundingClientRect();
    var style = getComputedStyle(elem, null);

    var mask = document.createElement('div');
    mask.style.position = 'absolute';
    var marginLeft = parseFloat(style.marginLeft);
    mask.style.left = (elem.offsetLeft - marginLeft) + 'px';
    var marginTop = parseFloat(style.marginTop);
    mask.style.top = (elem.offsetTop - marginTop) + 'px';
    mask.style.zIndex = 9999;
    mask.style.opacity = '' + opacity;
    mask.style.backgroundColor = '#000';

    mask.style.width = (parseFloat(style.marginLeft) +
        parseFloat(style.marginRight) + rect.width) + 'px';
    mask.style.height = (parseFloat(style.marginTop) +
        parseFloat(style.marginBottom) + rect.height) + 'px';

    elem.parentNode.appendChild(mask);
}

var target = document.getElementById('target');
addMask(target);

target.addEventListener('click', function () {
    console.log('click');
}, false);
</script>

請用代碼寫出(今天是星期x)其中x表示當天是星期幾,如果當天是星期一,輸出應該是"今天是星期一"

var days = ['日','一','二','三','四','五','六'];
var date = new Date();

console.log('今天是星期' + days[date.getDay()]);

下面這段代碼想要循環延時輸出結果0 1 2 3 4,請問輸出結果是否正確,如果不正確,請說明爲什麼,並修改循環內的代碼使其輸出正確結果

for (var i = 0; i < 5; ++i) {
  setTimeout(function () {
    console.log(i + ' ');
  }, 100);
}

不能輸出正確結果,因爲循環中setTimeout接受的參數函數通過閉包訪問變量i。javascript運行環境爲單線程,setTimeout註冊的函數需要等待線程空閒才能執行,此時for循環已經結束,i值爲5.五個定時輸出都是5
修改方法:將setTimeout放在函數立即調用表達式中,將i值作爲參數傳遞給包裹函數,創建新閉包

for (var i = 0; i < 5; ++i) {
  (function (i) {
    setTimeout(function () {
      console.log(i + ' ');
    }, 100);
  }(i));
}

現有一個Page類,其原型對象上有許多以post開頭的方法(如postMsg);另有一攔截函數chekc,只返回ture或false.請設計一個函數,該函數應批量改造原Page的postXXX方法,在保留其原有功能的同時,爲每個postXXX方法增加攔截驗證功能,當chekc返回true時繼續執行原postXXX方法,返回false時不再執行原postXXX方法

function Page() {}

Page.prototype = {
  constructor: Page,

  postA: function (a) {
    console.log('a:' + a);
  },
  postB: function (b) {
    console.log('b:' + b);
  },
  postC: function (c) {
    console.log('c:' + c);
  },
  check: function () {
    return Math.random() > 0.5;
  }
}

function checkfy(obj) {
  for (var key in obj) {
    if (key.indexOf('post') === 0 && typeof obj[key] === 'function') {
      (function (key) {
        var fn = obj[key];
        obj[key] = function () {
          if (obj.check()) {
            fn.apply(obj, arguments);
          }
        };
      }(key));
    }
  }
} // end checkfy()

checkfy(Page.prototype);

var obj = new Page();

obj.postA('checkfy');
obj.postB('checkfy');
obj.postC('checkfy');

完成下面的tool-tip

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ivDIdV7l-1584095000200)(img/tip-box.jpg)]

編寫javascript深度克隆函數deepClone

function deepClone(obj) {
    var _toString = Object.prototype.toString;

    // null, undefined, non-object, function
    if (!obj || typeof obj !== 'object') {
        return obj;
    }

    // DOM Node
    if (obj.nodeType && 'cloneNode' in obj) {
        return obj.cloneNode(true);
    }

    // Date
    if (_toString.call(obj) === '[object Date]') {
        return new Date(obj.getTime());
    }

    // RegExp
    if (_toString.call(obj) === '[object RegExp]') {
        var flags = [];
        if (obj.global) { flags.push('g'); }
        if (obj.multiline) { flags.push('m'); }
        if (obj.ignoreCase) { flags.push('i'); }

        return new RegExp(obj.source, flags.join(''));
    }

    var result = Array.isArray(obj) ? [] :
        obj.constructor ? new obj.constructor() : {};

    for (var key in obj ) {
        result[key] = deepClone(obj[key]);
    }

    return result;
}

function A() {
    this.a = a;
}

var a = {
    name: 'qiu',
    birth: new Date(),
    pattern: /qiu/gim,
    container: document.body,
    hobbys: ['book', new Date(), /aaa/gim, 111]
};

var c = new A();
var b = deepClone(c);
console.log(c.a === b.a);
console.log(c, b);

補充代碼,鼠標單擊Button1後將Button1移動到Button2的後面

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>TEst</title>
</head>
<body>

<div>
   <input type="button" id ="button1" value="1" />
   <input type="button" id ="button2" value="2" />
</div>

<script type="text/javascript">
    var btn1 = document.getElementById('button1');
    var btn2 = document.getElementById('button2');

    addListener(btn1, 'click', function (event) {
        btn1.parentNode.insertBefore(btn2, btn1);
    });

    function addListener(elem, type, handler) {
        if (elem.addEventListener) {
            elem.addEventListener(type, handler, false);
            return handler;
        } else if (elem.attachEvent) {
            function wrapper() {
                var event = window.event;
                event.target = event.srcElement;
                handler.call(elem, event);
            }
            elem.attachEvent('on' + type, wrapper);
            return wrapper;
        }
    }

</script>
</body>
</html>

網頁中實現一個計算當年還剩多少時間的倒數計時程序,要求網頁上實時動態顯示"××年還剩××天××時××分××秒"

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>TEst</title>
</head>
<body>

    <span id="target"></span>


<script type="text/javascript">
    // 爲了簡化。每月默認30天
    function getTimeString() {
        var start = new Date();
        var end = new Date(start.getFullYear() + 1, 0, 1);
        var elapse = Math.floor((end - start) / 1000);

        var seconds = elapse % 60 ;
        var minutes = Math.floor(elapse / 60) % 60;
        var hours = Math.floor(elapse / (60 * 60)) % 24;
        var days = Math.floor(elapse / (60 * 60 * 24)) % 30;
        var months = Math.floor(elapse / (60 * 60 * 24 * 30)) % 12;
        var years = Math.floor(elapse / (60 * 60 * 24 * 30 * 12));

        return start.getFullYear() + '年還剩' + years + '年' + months + '月' + days + '日'
            + hours + '小時' + minutes + '分' + seconds + '秒';
    }

    function domText(elem, text) {
        if (text == undefined) {

            if (elem.textContent) {
                return elem.textContent;
            } else if (elem.innerText) {
                return elem.innerText;
            }
        } else {
            if (elem.textContent) {
                elem.textContent = text;
            } else if (elem.innerText) {
                elem.innerText = text;
            } else {
                elem.innerHTML = text;
            }
        }
    }

    var target = document.getElementById('target');

    setInterval(function () {
        domText(target, getTimeString());
    }, 1000)
</script>

</body>
</html>

完成一個函數,接受數組作爲參數,數組元素爲整數或者數組,數組元素包含整數或數組,函數返回扁平化後的數組

如:[1, [2, [ [3, 4], 5], 6]] => [1, 2, 3, 4, 5, 6]

    var data =  [1, [2, [ [3, 4], 5], 6]];

    function flat(data, result) {
        var i, d, len;
        for (i = 0, len = data.length; i < len; ++i) {
            d = data[i];
            if (typeof d === 'number') {
                result.push(d);
            } else {
                flat(d, result);
            }
        }
    }

    var result = [];
    flat(data, result);

    console.log(result);

如何判斷一個對象是否爲數組

如果瀏覽器支持Array.isArray()可以直接判斷否則需進行必要判斷

/**
 * 判斷一個對象是否是數組,參數不是對象或者不是數組,返回false
 *
 * @param {Object} arg 需要測試是否爲數組的對象
 * @return {Boolean} 傳入參數是數組返回true,否則返回false
 */
function isArray(arg) {
    if (typeof arg === 'object') {
        return Object.prototype.toString.call(arg) === '[object Array]';
    }
    return false;
}

請評價以下事件監聽器代碼並給出改進意見

if (window.addEventListener) {
  var addListener = function (el, type, listener, useCapture) {
    el.addEventListener(type, listener, useCapture);
  };
}
else if (document.all) {
  addListener = function (el, type, listener) {
    el.attachEvent('on' + type, function () {
      listener.apply(el);
    });
  };
}

作用:瀏覽器功能檢測實現跨瀏覽器DOM事件綁定

優點:

  1. 測試代碼只運行一次,根據瀏覽器確定綁定方法
  2. 通過listener.apply(el)解決IE下監聽器this與標準不一致的地方
  3. 在瀏覽器不支持的情況下提供簡單的功能,在標準瀏覽器中提供捕獲功能

缺點:

  1. document.all作爲IE檢測不可靠,應該使用if(el.attachEvent)
  2. addListener在不同瀏覽器下API不一樣
  3. listener.apply使this與標準一致但監聽器無法移除
  4. 未解決IE下listener參數event。 target問題

改進:

var addListener;

if (window.addEventListener) {
  addListener = function (el, type, listener, useCapture) {
    el.addEventListener(type, listener, useCapture);
    return listener;
  };
}
else if (window.attachEvent) {
  addListener = function (el, type, listener) {
    // 標準化this,event,target
    var wrapper = function () {
      var event = window.event;
      event.target = event.srcElement;
      listener.call(el, event);
    };

    el.attachEvent('on' + type, wrapper);
    return wrapper;
    // 返回wrapper。調用者可以保存,以後remove
  };
}

如何判斷一個對象是否爲函數

/**
 * 判斷對象是否爲函數,如果當前運行環境對可調用對象(如正則表達式)
 * 的typeof返回'function',採用通用方法,否則採用優化方法
 *
 * @param {Any} arg 需要檢測是否爲函數的對象
 * @return {boolean} 如果參數是函數,返回true,否則false
 */
function isFunction(arg) {
    if (arg) {
        if (typeof (/./) !== 'function') {
            return typeof arg === 'function';
        } else {
            return Object.prototype.toString.call(arg) === '[object Function]';
        }
    } // end if
    return false;
}

編寫一個函數接受url中query string爲參數,返回解析後的Object,query string使用application/x-www-form-urlencoded編碼

/**
 * 解析query string轉換爲對象,一個key有多個值時生成數組
 *
 * @param {String} query 需要解析的query字符串,開頭可以是?,
 * 按照application/x-www-form-urlencoded編碼
 * @return {Object} 參數解析後的對象
 */
function parseQuery(query) {
    var result = {};

    // 如果不是字符串返回空對象
    if (typeof query !== 'string') {
        return result;
    }

    // 去掉字符串開頭可能帶的?
    if (query.charAt(0) === '?') {
        query = query.substring(1);
    }

    var pairs = query.split('&');
    var pair;
    var key, value;
    var i, len;

    for (i = 0, len = pairs.length; i < len; ++i) {
        pair = pairs[i].split('=');
        // application/x-www-form-urlencoded編碼會將' '轉換爲+
        key = decodeURIComponent(pair[0]).replace(/\+/g, ' ');
        value = decodeURIComponent(pair[1]).replace(/\+/g, ' ');

        // 如果是新key,直接添加
        if (!(key in result)) {
            result[key] = value;
        }
        // 如果key已經出現一次以上,直接向數組添加value
        else if (isArray(result[key])) {
            result[key].push(value);
        }
        // key第二次出現,將結果改爲數組
        else {
            var arr = [result[key]];
            arr.push(value);
            result[key] = arr;
        } // end if-else
    } // end for

    return result;
}

function isArray(arg) {
    if (arg && typeof arg === 'object') {
        return Object.prototype.toString.call(arg) === '[object Array]';
    }
    return false;
}
/**
console.log(parseQuery('sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8'));
 */

解析一個完整的url,返回Object包含域與window.location相同

/**
 * 解析一個url並生成window.location對象中包含的域
 * location:
 * {
 *      href: '包含完整的url',
 *      origin: '包含協議到pathname之前的內容',
 *      protocol: 'url使用的協議,包含末尾的:',
 *      username: '用戶名', // 暫時不支持
 *      password: '密碼',  // 暫時不支持
 *      host: '完整主機名,包含:和端口',
 *      hostname: '主機名,不包含端口'
 *      port: '端口號',
 *      pathname: '服務器上訪問資源的路徑/開頭',
 *      search: 'query string,?開頭',
 *      hash: '#開頭的fragment identifier'
 * }
 *
 * @param {string} url 需要解析的url
 * @return {Object} 包含url信息的對象
 */
function parseUrl(url) {
    var result = {};
    var keys = ['href', 'origin', 'protocol', 'host',
                'hostname', 'port', 'pathname', 'search', 'hash'];
    var i, len;
    var regexp = /(([^:]+:)\/\/(([^:\/\?#]+)(:\d+)?))(\/[^?#]*)?(\?[^#]*)?(#.*)?/;

    var match = regexp.exec(url);

    if (match) {
        for (i = keys.length - 1; i >= 0; --i) {
            result[keys[i]] = match[i] ? match[i] : '';
        }
    }

    return result;
}

完成函數getViewportSize返回指定窗口的視口尺寸

/**
* 查詢指定窗口的視口尺寸,如果不指定窗口,查詢當前窗口尺寸
**/
function getViewportSize(w) {
    w = w || window;

    // IE9及標準瀏覽器中可使用此標準方法
    if ('innerHeight' in w) {
        return {
            width: w.innerWidth,
            height: w.innerHeight
        };
    }

    var d = w.document;
    // IE 8及以下瀏覽器在標準模式下
    if (document.compatMode === 'CSS1Compat') {
        return {
            width: d.documentElement.clientWidth,
            height: d.documentElement.clientHeight
        };
    }

    // IE8及以下瀏覽器在怪癖模式下
    return {
        width: d.body.clientWidth,
        height: d.body.clientHeight
    };
}

完成函數getScrollOffset返回窗口滾動條偏移量

/**
 * 獲取指定window中滾動條的偏移量,如未指定則獲取當前window
 * 滾動條偏移量
 *
 * @param {window} w 需要獲取滾動條偏移量的窗口
 * @return {Object} obj.x爲水平滾動條偏移量,obj.y爲豎直滾動條偏移量
 */
function getScrollOffset(w) {
    w =  w || window;
    // 如果是標準瀏覽器
    if (w.pageXOffset != null) {
        return {
            x: w.pageXOffset,
            y: w.pageYOffset
        };
    }

    // 老版本IE,根據兼容性不同訪問不同元素
    var d = w.document;
    if (d.compatMode === 'CSS1Compat') {
        return {
            x: d.documentElement.scrollLeft,
            y: d.documentElement.scrollTop
        }
    }

    return {
        x: d.body.scrollLeft,
        y: d.body.scrollTop
    };
}

現有一個字符串richText,是一段富文本,需要顯示在頁面上.有個要求,需要給其中只包含一個img元素的p標籤增加一個叫pic的class.請編寫代碼實現.可以使用jQuery或KISSY.

function richText(text) {
    var div = document.createElement('div');
    div.innerHTML = text;
    var p = div.getElementsByTagName('p');
    var i, len;

    for (i = 0, len = p.length; i < len; ++i) {
        if (p[i].getElementsByTagName('img').length === 1) {
            p[i].classList.add('pic');
        }
    }

    return div.innerHTML;
}

請實現一個Event類,繼承自此類的對象都會擁有兩個方法on,off,once和trigger

function Event() {
    if (!(this instanceof Event)) {
        return new Event();
    }
    this._callbacks = {};
}
Event.prototype.on = function (type, handler) {
    this_callbacks = this._callbacks || {};
    this._callbacks[type] = this.callbacks[type] || [];
    this._callbacks[type].push(handler);

    return this;
};

Event.prototype.off = function (type, handler) {
    var list = this._callbacks[type];

    if (list) {
        for (var i = list.length; i >= 0; --i) {
            if (list[i] === handler) {
                list.splice(i, 1);
            }
        }
    }

    return this;
};

Event.prototype.trigger = function (type, data) {
    var list = this._callbacks[type];

    if (list) {
        for (var i = 0, len = list.length; i < len; ++i) {
            list[i].call(this, data);
        }
    }
};

Event.prototype.once = function (type, handler) {
    var self = this;

    function wrapper() {
        handler.apply(self, arguments);
        self.off(type, wrapper);
    }
    this.on(type, wrapper);
    return this;
};

編寫一個函數將列表子元素順序反轉

<ul id="target">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
</ul>

<script>
    var target = document.getElementById('target');
    var i;
    var frag = document.createDocumentFragment();

    for (i = target.children.length - 1; i &gt;= 0; --i) {
        frag.appendChild(target.children[i]);
    }
    target.appendChild(frag);
</script>

以下函數的作用是?空白區域應該填寫什麼

// define
(function (window) {
    function fn(str) {
        this.str = str;
    }

    fn.prototype.format = function () {
        var arg = __1__;
        return this.str.replace(__2__, function (a, b) {
            return arg[b] || '';
        });
    };

    window.fn = fn;
})(window);

// use
(function () {
    var t = new fn('<p><a href="{0}">{1}</a><span>{2}</span></p>');
    console.log(t.format('http://www.alibaba.com', 'Alibaba', 'Welcome'));
})();

define部分定義一個簡單的模板類,使用{}作爲轉義標記,中間的數字表示替換目標,format實參用來替換模板內標記
橫線處填:

  1. Array.prototype.slice.call(arguments, 0)
  2. /\{\s*(\d+)\s*\}/g

編寫一個函數實現form的序列化(即將一個表單中的鍵值序列化爲可提交的字符串)

<form id="target">
    <select name="age">
        <option value="aaa">aaa</option>
        <option value="bbb" selected>bbb</option>
    </select>
    <select name="friends" multiple>
        <option value="qiu" selected>qiu</option>
        <option value="de">de</option>
        <option value="qing" selected>qing</option>
    </select>
    <input name="name" value="qiudeqing">
    <input type="password" name="password" value="11111">
    <input type="hidden" name="salery" value="3333">
    <textarea name="description">description</textarea>
    <input type="checkbox" name="hobby" checked value="football">Football
    <input type="checkbox" name="hobby" value="basketball">Basketball
    <input type="radio" name="sex" checked value="Female">Female
    <input type="radio" name="sex" value="Male">Male
</form>


<script>

/**
 * 將一個表單元素序列化爲可提交的字符串
 *
 * @param {FormElement} form 需要序列化的表單元素
 * @return {string} 表單序列化後的字符串
 */
function serializeForm(form) {
  if (!form || form.nodeName.toUpperCase() !== 'FORM') {
    return;
  }

  var result = [];

  var i, len;
  var field, fieldName, fieldType;

  for (i = 0, len = form.length; i < len; ++i) {
    field = form.elements[i];
    fieldName = field.name;
    fieldType = field.type;

    if (field.disabled || !fieldName) {
      continue;
    } // enf if

    switch (fieldType) {
      case 'text':
      case 'password':
      case 'hidden':
      case 'textarea':
        result.push(encodeURIComponent(fieldName) + '=' +
            encodeURIComponent(field.value));
        break;

      case 'radio':
      case 'checkbox':
        if (field.checked) {
          result.push(encodeURIComponent(fieldName) + '=' +
            encodeURIComponent(field.value));
        }
        break;

      case 'select-one':
      case 'select-multiple':
        for (var j = 0, jLen = field.options.length; j < jLen; ++j) {
          if (field.options[j].selected) {
            result.push(encodeURIComponent(fieldName) + '=' +
              encodeURIComponent(field.options[j].value || field.options[j].text));
          }
        } // end for
        break;

      case 'file':
      case 'submit':
        break; // 是否處理?

      default:
        break;
    } // end switch
  } // end for

    return result.join('&');
}

var form = document.getElementById('target');
console.log(serializeForm(form));
</script>

使用原生javascript給下面列表中的li節點綁定點擊事件,點擊時創建一個Object對象,兼容IE和標準瀏覽器

<ul id="nav">
    <li><a href="http://11111">111</a></li>
    <li><a href="http://2222">222</a></li>
    <li><a href="http://333">333</a></li>
    <li><a href="http://444">444</a></li>
</ul>

Object:
{
    "index": 1,
    "name": "111",
    "link": "http://1111"
}

script:

var EventUtil = {
    getEvent: function (event) {
        return event || window.event;
    },
    getTarget: function (event) {
        return event.target || event.srcElement;
    },
    // 返回註冊成功的監聽器,IE中需要使用返回值來移除監聽器
    on: function (elem, type, handler) {
        if (elem.addEventListener) {
            elem.addEventListener(type, handler, false);
            return handler;
        } else if (elem.attachEvent) {
            function wrapper(event) {
                return handler.call(elem, event);
            };
            elem.attachEvent('on' + type, wrapper);
            return wrapper;
        }
    },
    off: function (elem, type, handler) {
        if (elem.removeEventListener) {
            elem.removeEventListener(type, handler, false);
        } else if (elem.detachEvent) {
            elem.detachEvent('on' + type, handler);
        }
    },
    preventDefault: function (event) {
        if (event.preventDefault) {
            event.preventDefault();
        } else if ('returnValue' in event) {
            event.returnValue = false;
        }
    },
    stopPropagation: function (event) {
        if (event.stopPropagation) {
            event.stopPropagation();
        } else if ('cancelBubble' in event) {
            event.cancelBubble = true;
        }
    }
};
var DOMUtil = {
    text: function (elem) {
        if ('textContent' in elem) {
            return elem.textContent;
        } else if ('innerText' in elem) {
            return elem.innerText;
        }
    },
    prop: function (elem, propName) {
        return elem.getAttribute(propName);
    }
};

var nav = document.getElementById('nav');

EventUtil.on(nav, 'click', function (event) {
    var event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);

    var children = this.children;
    var i, len;
    var anchor;
    var obj = {};

    for (i = 0, len = children.length; i < len; ++i) {
        if (children[i] === target) {
            obj.index = i + 1;
            anchor = target.getElementsByTagName('a')[0];
            obj.name = DOMUtil.text(anchor);
            obj.link = DOMUtil.prop(anchor, 'href');
        }
    }

    alert('index: ' + obj.index + ' name: ' + obj.name +
        ' link: ' + obj.link);
});

有一個大數組,var a = [‘1’, ‘2’, ‘3’, …];a的長度是100,內容填充隨機整數的字符串.請先構造此數組a,然後設計一個算法將其內容去重

    /**
    * 數組去重
    **/
    function normalize(arr) {
        if (arr && Array.isArray(arr)) {
            var i, len, map = {};
            for (i = arr.length; i >= 0; --i) {
                if (arr[i] in map) {
                    arr.splice(i, 1);
                } else {
                    map[arr[i]] = true;
                }
            }
        }
        return arr;
    }

    /**
    * 用100個隨機整數對應的字符串填充數組。
    **/
    function fillArray(arr, start, end) {
        start = start == undefined ? 1 : start;
        end = end == undefined ?  100 : end;

        if (end <= start) {
            end = start + 100;
        }

        var width = end - start;
        var i;
        for (i = 100; i >= 1; --i) {
            arr.push('' + (Math.floor(Math.random() * width) + start));
        }
        return arr;
    }

    var input = [];
    fillArray(input, 1, 100);
    input.sort(function (a, b) {
        return a - b;
    });
    console.log(input);

    normalize(input);
    console.log(input);

評價一下三種方法實現繼承的優缺點,並改進

function Shape() {}

function Rect() {}

// 方法1
Rect.prototype = new Shape();

// 方法2
Rect.prototype = Shape.prototype;

// 方法3
Rect.prototype = Object.create(Shape.prototype);

Rect.prototype.area = function () {
  // do something
};

方法1:

  1. 優點:正確設置原型鏈實現繼承
  2. 優點:父類實例屬性得到繼承,原型鏈查找效率提高,也能爲一些屬性提供合理的默認值
  3. 缺點:父類實例屬性爲引用類型時,不恰當地修改會導致所有子類被修改
  4. 缺點:創建父類實例作爲子類原型時,可能無法確定構造函數需要的合理參數,這樣提供的參數繼承給子類沒有實際意義,當子類需要這些參數時應該在構造函數中進行初始化和設置
  5. 總結:繼承應該是繼承方法而不是屬性,爲子類設置父類實例屬性應該是通過在子類構造函數中調用父類構造函數進行初始化

方法2:

  1. 優點:正確設置原型鏈實現繼承
  2. 缺點:父類構造函數原型與子類相同。修改子類原型添加方法會修改父類

方法3:

  1. 優點:正確設置原型鏈且避免方法1.2中的缺點
  2. 缺點:ES5方法需要注意兼容性

改進:

  1. 所有三種方法應該在子類構造函數中調用父類構造函數實現實例屬性初始化
function Rect() {
    Shape.call(this);
}
  1. 用新創建的對象替代子類默認原型,設置Rect.prototype.constructor = Rect;保證一致性
  2. 第三種方法的polyfill:
function create(obj) {
    if (Object.create) {
        return Object.create(obj);
    }

    function f() {};
    f.prototype = obj;
    return new f();
}

DOM事件模型是如何的,編寫一個EventUtil工具類實現事件管理兼容

  • DOM事件包含捕獲(capture)和冒泡(bubble)兩個階段:捕獲階段事件從window開始觸發事件然後通過祖先節點一次傳遞到觸發事件的DOM元素上;冒泡階段事件從初始元素依次向祖先節點傳遞直到window
  • 標準事件監聽elem.addEventListener(type, handler, capture)/elem.removeEventListener(type, handler, capture):handler接收保存事件信息的event對象作爲參數,event.target爲觸發事件的對象,handler調用上下文this爲綁定監聽器的對象,event.preventDefault()取消事件默認行爲,event.stopPropagation()/event.stopImmediatePropagation()取消事件傳遞
  • 老版本IE事件監聽elem.attachEvent(‘on’+type, handler)/elem.detachEvent(‘on’+type, handler):handler不接收event作爲參數,事件信息保存在window.event中,觸發事件的對象爲event.srcElement,handler執行上下文this爲window使用閉包中調用handler.call(elem, event)可模仿標準模型,然後返回閉包,保證了監聽器的移除。event.returnValue爲false時取消事件默認行爲,event.cancleBubble爲true時取消時間傳播
  • 通常利用事件冒泡機制託管事件處理程序提高程序性能。
/**
 * 跨瀏覽器事件處理工具。只支持冒泡。不支持捕獲
 * @author  ([email protected])
 */

var EventUtil = {
    getEvent: function (event) {
        return event || window.event;
    },
    getTarget: function (event) {
        return event.target || event.srcElement;
    },
    // 返回註冊成功的監聽器,IE中需要使用返回值來移除監聽器
    on: function (elem, type, handler) {
        if (elem.addEventListener) {
            elem.addEventListener(type, handler, false);
            return handler;
        } else if (elem.attachEvent) {
            var wrapper = function () {
              var event = window.event;
              event.target = event.srcElement;
              handler.call(elem, event);
            };
            elem.attachEvent('on' + type, wrapper);
            return wrapper;
        }
    },
    off: function (elem, type, handler) {
        if (elem.removeEventListener) {
            elem.removeEventListener(type, handler, false);
        } else if (elem.detachEvent) {
            elem.detachEvent('on' + type, handler);
        }
    },
    preventDefault: function (event) {
        if (event.preventDefault) {
            event.preventDefault();
        } else if ('returnValue' in event) {
            event.returnValue = false;
        }
    },
    stopPropagation: function (event) {
        if (event.stopPropagation) {
            event.stopPropagation();
        } else if ('cancelBubble' in event) {
            event.cancelBubble = true;
        }
    },
    /**
     * keypress事件跨瀏覽器獲取輸入字符
     * 某些瀏覽器在一些特殊鍵上也觸發keypress,此時返回null
     **/
     getChar: function (event) {
        if (event.which == null) {
            return String.fromCharCode(event.keyCode);  // IE
        }
        else if (event.which != 0 && event.charCode != 0) {
            return String.fromCharCode(event.which);    // the rest
        }
        else {
            return null;    // special key
        }
     }
};

算法性質

  1. 8個外表一樣的小球 其中7個球重量相同 1個球爲[異常球] 可能重量比較重也可能比較輕 利用天平稱重至少多少次可以確保找出這個[異常球],並需要知道到底是輕了還是重了。
    這是一道非常有意思的題,答案爲3次。
    答案
    一、將8個球先取四個組成A、B兩組,每組2個。
    二、、將A、B組進行第一次稱,若不同重則有一組有問題。
    三、將重組兩個球第二次稱,若不同重則有一個有問題。 將重組重球(若選輕球則以下結論相反)與輕組一球進行第三次稱。 若球重,則這粒爲重球爲異常球;若相同,則剩餘那個爲輕異常球。

2 . 實現超出整數存儲範圍的兩個大整數相加function add(a,b)。注意a和b以及函數的返回值都是字符串。

在這裏插入圖片描述

Js實現Symbol


// 當調用 Symbol 的時候,會採用以下步驟:
//1. 如果使用 new ,就報錯
//2. 如果 description 是 undefined,讓 descString 爲 undefined
//3. 否則 讓 descString 爲 ToString(description)
//4. 如果報錯,就返回
//5. 返回一個新的唯一的 Symbol 值,它的內部屬性 [[Description]] 值爲 descString
(function () {
    var root = this;
    var generateName = (function () {
        var postfix = 0;
        return function (descString) {
            postfix++;
            return '@@' + descString + '_' + postfix
        }
    })()
    var SymbolPolyfill = function Symbol(description) {
        // 實現特性第 2 點:Symbol 函數前不能使用 new 命令
        if (this instanceof SymbolPolyfill) throw new TypeError('Symbol is not a constructor');
        // 實現特性第 5 點:
        // 如果 Symbol 的參數是一個對象,toString 方法,將其轉爲字符串,然後才生成一個 Symbol 值。
        var descString = description === undefined ? undefined : String(descString);
        var symbol = Object.create({
            toString: function () {
                return this.__Name__;
            },
            valueOf: function () {
                return this;
            }
        })
        // 語法: Object.defineProperties(obj, props)
        // obj: 將要被添加屬性或修改屬性的對象
        // props: 該對象的一個或多個鍵值對定義了將要爲對象添加或修改的屬性的具體配置
        Object.defineProperties(symbol, {
            '__Description__': {
                value: descString,
                writable: false,
                enumerable: false,
                configurable: false
            },
            '__Name__': {
                value: generateName(descString),
                writable: false,
                enumerable: false,
                configurable: false
            }
        })
        // 實現特性第 6 點,因爲調用該方法,返回的是一個新對象,兩個對象之間,只要引用不同,就不會相同
        //  Symbol 函數的參數只是表示對當前 Symbol 值的描述,相同參數的 Symbol 函數的返回值是不相等的。
        return symbol
    }
    var forMap = {};
    // Symbol.keyFor 方法返回一個已登記的 Symbol 類型值的 key
    Object.defineProperties(SymbolPolyfill, {
        'for': {
            value: function (description) {
                var descString = description === undefined ? undefined : String(description)
                return forMap[descString] ? forMap[descString] : forMap[descString] = SymbolPolyfill(descString);
            },
            writable: true,
            enumerable: false,
            configurable: true
        },
        'keyFor': {
            value: function (symbol) {
                for (var key in forMap) {
                    if (forMap[key] === symbol) return key;
                }
            },
            writable: true,
            enumerable: false,
            configurable: true
        }
    })
    root.SymbolPolyfill = SymbolPolyfill
})();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章