上一篇: Day2 - 前端高頻面試題之基礎版javascript
下一篇: Day4 - 前端高頻面試題之瀏覽器相關html
變量提高是將變量聲明提高到做用域頂部,函數也能夠被提高,而且優先於變量提高前端
// var 存在提高,能在聲明以前使用
console.log(a) // undefined
var a = 1
複製代碼
// 函數提高是把整個函數提高到做用域頂部
console.log(a) // ƒ a() {}
function a() {}
var a = 1
複製代碼
在全局做用域下使用 let 和 const 聲明變量,變量不會被掛載到 頂層對象window或者global的屬性上java
console.log(b) // 報錯
let b = 1
const c = 1
window.b // undefined
window.c // undefined
-------
// let 聲明的變量僅在塊級做用域內有效
{
let a = 10;
}
a // ReferenceError: a is not defined.
// let 也不容許在相同做用域內,重複聲明同一個變量
複製代碼
let 實際上爲 JavaScript 新增了塊級做用域node
爲何須要塊級做用域?jquery
ES5 只有全局做用域和函數做用域,沒有塊級做用域,這帶來不少不合理的場景es6
一、內層變量可能會覆蓋外層變量面試
二、用來計數的循環變量泄露爲全局變量編程
在代碼塊內,使用let和const命令聲明變量以前,該變量都是不可用的。這在語法上,稱爲「暫時性死區」json
在沒有let
以前,typeof
運算符是百分之百安全的,永遠不會報錯。如今這一點不成立了。這樣的設計是爲了讓你們養成良好的編程習慣,變量必定要在聲明以後使用,不然就報錯。
typeof undeclared_variable // "undefined"
typeof x; // ReferenceError
let x;
---
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 報錯 由於參數x默認值等於另外一個參數y,而此時y尚未聲明,屬於「死區」
複製代碼
const
聲明一個只讀的常量。一旦聲明,常量的值就不能改變;
實際上,並非變量的值不得改動,而是變量指向的那個內存地址所保存的數據不得改動。
對於簡單數據類型來講,就是常量;對於複合數據類型來講(主要是對象和數組),變量指向的內存地址,保存的是一個指向實際數據的指針,const
只能保證這個指針是固定的(即老是指向另外一個固定的地址),至於它指向的數據結構是否是可變的,就徹底不能控制了。
const a = {
name: 'xyz',
age: 18
}
// 這裏至關於修改指針指向的值
a.name = 'abc'
console.log(a) // {name: 'abc', age: 18}
// 將 a 指向另外一個對象,就會報錯
a = {} // Uncaught TypeError: Assignment to constant variable.
----
const a = [];
a.push('Hello'); // 可執行
a.length = 0; // 可執行
a = ['Dave']; // 報錯
複製代碼
// 1.將json對象轉化爲json字符串,再判斷該字符串是否爲"{}"
var data = {};
var b = (JSON.stringify(data) == "{}");
alert(b);//true
// 2.for in 循環判斷
var obj = {};
var b = function() {
for(var key in obj) {
return false;
}
return true;
}
alert(b());//true
// 三、jquery的isEmptyObject方法
此方法是jquery將2方法(for in)進行封裝,使用時須要依賴jquery
var data = {};
var b = $.isEmptyObject(data);
alert(b);//true
// 4.Object.getOwnPropertyNames()方法
此方法是使用Object對象的getOwnPropertyNames方法,
獲取到對象中的屬性名,存到一個數組中,返回數組對象,
咱們能夠經過判斷數組的length來判斷此對象是否爲空
注意:此方法不兼容ie8,其他瀏覽器沒有測試
var data = {};
var arr = Object.getOwnPropertyNames(data);
alert(arr.length == 0);//true
// 5.使用ES6的Object.keys()方法
// 與4方法相似,是ES6的新方法, 返回值也是對象中屬性名組成的數組
var data = {};
var arr = Object.keys(data);
alert(arr.length == 0);//true
複製代碼
this
要指向的對象,也就是想指定的上下文;// call 和 apply 是爲了動態改變 this 而出現的
function Fruits() {}
Fruits.prototype = {
color: 'red',
getColor: function() {
return 'color is' + this.color
}
}
var apple = new Fruits()
apple.getColor() // color is red
var banana = {
color: 'yellow'
}
apply.getColor.call(banana) // color is yellow
apply.getColor.apply(banana) // color is yellow
// 當一個 object 沒有某個方法(本栗子中banana沒有say方法),
// 可是其餘的有(本栗子中apple有say方法),咱們能夠藉助call或apply用其它對象的方法來操做
複製代碼
// 實例:
var numbers = [5, 458 , 120 , -215]
Math.max.apply(null, numbers) // 458
Math.min.call(null, 5, 458 , 120 , -215) // -215
複製代碼
// 面試題:
// 定義一個 log 方法,讓它能夠代理 console.log 方法,常見的解決方法是:
function log(){
console.log.apply(null, arguments) // 第一個參數爲null,表明指向全局對象window或者global
}
log(1, 'www', '&&&') // 1 "www" "&&&"
// 進階:開頭加上(app):
function log(){
var args = Array.prototype.slice.call(arguments) // 須要將僞數組轉化爲標準數組
args.unshift('(app)')
console.log.apply(null, args)
}
log(1, '***') // (app) 1 ***
// 僞數組轉化爲標準數組
1、 Array.prototype.slice.call(arguments)
2、
var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.call.bind(slice)
slice(arguments) // arguments轉換成了標準數組
複製代碼
AMD、CMD、CommonJS
和ES6
的區別嗎?AMD
(異步模塊定義)是 RequireJS
在推廣過程當中對模塊定義的規範化產出
特色 :提早執行,推崇依賴前置;異步加載,不阻塞頁面加載,能並行加載多個模塊,可是不能按需加載
重要的API:
define(id?,[]?,callbakc)
// 定義聲明模塊 (模塊id標識(可選),數組 依賴的其餘模塊(可選),回調函數)
require([module],callback)
// 加載模塊 (數組 指定加載的模塊,回調函數)
還有一個配置屬性API require.config()
簡單的用法:AMD規範使用define方法定義模塊
// 主入口 index.js
require.config({
baseUrl: '',
map: {},
paths: {
"jquery": "../js/jquery-1.11.1.min",
"validate": "../js/jquery.validate.min",
"moduleTest":"test" //自定義AMD 模式的模塊
}, // 對外加載的模塊名稱
shim: {
'jquery.validate': ['jquery'], // 配置 jquery 依賴
'validate.form': ['jquery', 'validate']
} // 配置非AMD模式的文件
})
// 單個模塊 test.js
define([
'jquery',
'validate'
], function(_, _validate) {
console.log(_)
return {
add: function (x, y) {
return x + y
}
}
})
// 加載test模塊
require(['moduleTest'], function(test) {
// 依賴前置 就是依賴必須一開始就寫好,即便在最後用到
// …… doSomething()
test.add(1, 2)
})
複製代碼
是 Sea.js
在推廣過程當中對模塊定義的規範化產出
特色:延遲執行,推崇依賴就近;按需加載,不須要開始就加載全部的模塊,一個模塊就是一個JS文件
define(function(require, exports, module) {
let a = require('../a')
a.doSomething()
// 依賴就近 就是在使用前一步引入就能夠
let b = require('../b')
b.doSomething()
})
複製代碼
因爲Node.js主要用於服務器編程,文件在本地,加載都比較快,通常不用考慮非同步的狀況,因此CommonJS規範比較適合
而在瀏覽器端,要從服務器加載模塊,就須要採用非同步方式,所以通常使用AMD/CMD規範
CommonJS
模塊就是對象,每一個文件就是一個模塊,有本身的做用域;
特色:
「運行時加載」,全部代碼都運行在模塊做用域,不會污染全局做用域
模塊能夠屢次加載,可是隻會在第一次加載時運行一次,而後運行結果就被緩存了,之後再加載,就直接讀取緩存結果。要想讓模塊再次運行,必須清除緩存
模塊加載的順序,按照其在代碼中出現的順序
module、module.exports
)每一個模塊內部,module
表明當前模塊,是一個對象,它的exports
屬性(即module.exports
)是對外的接口,其餘文件加載該模塊,實際上就是讀取module.exports
變量;
爲了方便,Node
爲每一個模塊提供一個exports
變量,指向module.exports
,即let exports = module.exports
因此在對外輸出模塊接口時,能夠向exports
對象添加方法;
注意:不能直接將exports
變量指向一個值,由於這樣等於切斷了exports
與module.exports
的聯繫
// lib.js
var counter = 3;
function incCounter() {}
// 單個導出
module.exports.counter = counter
module.exports.incCounter = incCounter
// 或者導出一個對象
module.exports = {
counter: counter,
incCounter: incCounter,
}
複製代碼
require
命令用於加載模塊文件,腳本代碼在require
的時候,就會所有執行;
使用require
屢次加載同一個模塊時,只會在加載第一次時執行一次,後面再加載,都是直接取第一次運行的結果,除非手動清除系統緩存;
CommonJS
模塊的加載機制是,輸入的是被輸出的值的拷貝。也就是一旦輸出一個值,模塊內部的變化就影響不到這個值。
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
// 輸出內部變量counter和改寫這個變量的內部方法incCounter
module.exports = {
counter: counter,
incCounter: incCounter,
};
複製代碼
能夠取代 CommonJS
和AMD
規範,成爲瀏覽器和服務器通用的模塊解決方案。
ES6 模塊不是對象,而是經過export
命令顯式指定輸出的代碼,再經過import
命令輸入
特色:
它是編譯時加載」或者靜態加載;
import
會自動提高到代碼的頂層;
輸出的是值的引用,即原始值變化,import加載的值也會發生變化;
export
和 import
只能出如今模塊的頂層;
ES6 模塊之中,頂層的this指向undefined,即不該該在頂層代碼使用this
模塊功能主要由兩個命令構成:export
用於規定模塊的對外接口 和 import
用於引入其餘模塊提供的功能
// export.js 能夠對外輸出常量、方法和類
// 變量
export let a = 'xyz'
// 函數
export function fn () {}
// 類
export class class1 {}
// 須要特別注意的是,export命令規定的是對外的接口,必須與模塊內部的變量創建一一對應關係
export 1; // 報錯
var m = 1;
export m; // 報錯
// 正確寫法
// 寫法一
export var m = 1;
// 寫法二
var m = 1;
export {m};
// 寫法三
var n = 1;
export {n as m};
複製代碼
使用export
命令定義了模塊的對外接口之後,其餘 JS 文件就能夠經過import
命令加載這個模塊
大括號裏面的變量名,必須與被導入模塊(export.js
)對外接口的名稱相同。
import
命令是編譯階段執行的,在代碼運行以前,因此會被提高到模塊的頭部,首先執行
// import命令輸入的變量都是隻讀的,由於它的本質是輸入接口。也就是說,不容許在加載模塊的腳本里面,改寫接口
// 靜態加載,只加載export.js 文件中兩個變量,其餘不加載
import {a, fn} from './export.js'
a=22 // Syntax Error : 'a' is read-only
a.name = 'xyz' // 若是a是一個對象,改寫a的屬性是容許的
//import命令要使用as關鍵字,將輸入的變量重命名
import {fn as fn1} from './export.js'
// 總體加載
import * as all from './export.js'
複製代碼
目前階段,經過 Babel 轉碼,CommonJS 模塊的
require
命令和 ES6 模塊的import
命令,能夠寫在同一個模塊裏面,可是最好不要這樣作。由於import
在靜態解析階段執行,因此它是一個模塊之中最先執行的。
本質上,export default就是輸出一個叫作default的變量或方法
// 第一組
export default function crc32() { // 輸出
// ...
}
import crc32 from 'crc32'; // 輸入
// 第二組
export function crc32() { // 輸出
// ...
};
import {crc32} from 'crc32'; // 輸入
// 第一組是使用export default時,對應的import語句不須要使用大括號;
// 第二組是不使用export default時,對應的import語句須要使用大括號。
複製代碼
正是由於export default
命令其實只是輸出一個叫作default
的變量,因此它後面不能跟變量聲明語句。
// 正確
export var a = 1;
// 正確
var a = 1;
export default a; // 將變量a的值賦給變量default
// 錯誤
export default var a = 1;
// export default命令是輸出一個叫作default的變量,因此它後面不能跟變量聲明語句
// 正確
export default 42; // 指定了對外接口爲default
// 報錯
export 42; // 沒有指定對外的接口
複製代碼
有兩個重大差別。
// CommonJS 模塊輸出的是值的拷貝,也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3
// lib.js模塊加載之後,它的內部變化就影響不到輸出的mod.counter了。
// 這是由於mod.counter是一個原始類型的值,會被緩存。
複製代碼
// ES6 模塊是動態引用,而且不會緩存值,模塊裏面的變量綁定其所在的模塊。
// lib.js
export let counter = 3;
export function incCounter() {
counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
//ES6 模塊輸入的變量counter是活的,徹底反應其所在模塊lib.js內部的變化。
複製代碼
CommonJS模塊的重要特性是加載時執行,即腳本代碼在require
的時候,就會所有執行。CommonJS的作法是,一旦出現某個模塊被"循環加載",就只輸出已經執行的部分,還未執行的部分不會輸出。
官方文檔裏面的例子以下:腳本文件a.js
exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.js 執行完畢');
複製代碼
上面代碼之中,a.js
腳本先輸出一個done
變量,而後加載另外一個腳本文件b.js
。注意,此時a.js
代碼就停在這裏,等待b.js
執行完畢,再往下執行。
再看b.js
的代碼
exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('b.js 執行完畢');
複製代碼
上面代碼之中,b.js
執行到第二行,就會去加載a.js
,這時,就發生了"循環加載"。系統會去a.js
模塊對應對象的exports
屬性取值,但是由於a.js
尚未執行完,從exports
屬性只能取回已經執行的部分,而不是最後的值。
a.js
已經執行的部分,只有一行。
exports.done = false;
所以,對於b.js
來講,它從a.js
只輸入一個變量done
,值爲false
。
而後,b.js
接着往下執行,等到所有執行完畢,再把執行權交還給a.js
。因而,a.js
接着往下執行,直到執行完畢。咱們寫一個腳本main.js
,驗證這個過程。
var a = require('./a.js');
var b = require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
複製代碼
執行main.js
,運行結果以下。
$ node main.js
在 b.js 之中,a.done = false
b.js 執行完畢
在 a.js 之中,b.done = true
a.js 執行完畢
在 main.js 之中, a.done=true, b.done=true
複製代碼
上面的代碼證實了兩件事。一是,在b.js
之中,a.js
沒有執行完畢,只執行了第一行。二是,main.js
執行到第二行時,不會再次執行b.js
,而是輸出緩存的b.js
的執行結果,即它的第四行。
exports.done = true;
ES6模塊的運行機制與CommonJS不同,它遇到模塊加載命令import
時,不會去執行模塊,而是隻生成一個引用。等到真的須要用到時,再到模塊裏面去取值。
所以,ES6模塊是動態引用,不存在緩存值的問題,並且模塊裏面的變量,綁定其所在的模塊。
// m1.js
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
// m2.js
import {foo} from './m1.js';
console.log(foo);
setTimeout(() => console.log(foo), 500);
$ babel-node m2.js
bar
baz
// 代表,ES6模塊不會緩存運行結果,而是動態地去被加載的模塊取值,以及變量老是綁定其所在的模塊
複製代碼
ES6根本不會關心是否發生了"循環加載",只是生成一個指向被加載模塊的引用,須要開發者本身保證,真正取值的時候可以取到值。
// a.js
import {bar} from './b.js';
export function foo() {
bar();
console.log('執行完畢');
}
foo();
// b.js
import {foo} from './a.js';
export function bar() {
if (Math.random() > 0.5) {
foo();
}
}
複製代碼
按照CommonJS規範,上面的代碼是無法執行的。a
先加載b
,而後b
又加載a
,這時a
尚未任何執行結果,因此輸出結果爲null
,即對於b.js
來講,變量foo
的值等於null
,後面的foo()
就會報錯。
可是,ES6能夠執行上面的代碼。
$ babel-node a.js
執行完畢
複製代碼
a.js
之因此可以執行,緣由就在於ES6加載的變量,都是動態引用其所在的模塊。只要引用是存在的,代碼就能執行。
引用文章:
以上