本篇文章記錄本身對es5知識點的理解javascript
======================相關文章和開源庫=======================前端
系列文章vue
1.前端知識梳理-HTML,CSS篇
java
2.前端知識梳理-ES5篇
面試
3.前端知識梳理-ES6篇
數組
4.前端知識梳理-VUE篇
緩存
我的維護的開源組件庫bash
2.樹形組織結構組件
app
3.bin-admin ,基於bin-ui的後臺管理系統集成方案
4.bin-data ,基於bin-ui和echarts的數據可視化框架
5.其他生態連接
========================================================
es5有三種方式建立對象,分別是
// 第一種方式,字面量var
o1 = {name: "o1"}var
o2 = new Object({name: "o2"})// 第二種方式,經過構造函數var
M = function(name){ this.name = name }var
o3 = new M("o3")// 第三種方式,Object.createvar p = {name: "p"}var
o4 = Object.create(p) 對於對象,在這裏也不作贅述,這裏解釋一下js中什麼是標識符複製代碼
/*
標識符
- 在JS中全部的能夠由咱們自主命名的均可以稱爲是標識符
- 例如:變量名、函數名、屬性名都屬於標識符
- 命名一個標識符時須要遵照以下的規則:
1.標識符中能夠含有字母 、數字 、下劃線_ 、$符號
2.標識符不能以數字開頭
3.標識符不能是ES中的關鍵字或保留字
4.標識符通常都採用駝峯命名法
- JS底層保存標識符時其實是採用的Unicode編碼,
因此理論上講,全部的utf-8中含有的內容均可以做爲標識符
*/
這裏就看出js的缺陷了,若是對象中的屬性不符合標識符規範怎麼辦,也就是我用.操做符沒法獲取屬性的時候,好比說 var obj={ 1 : 1 } console.log(obj.1)
事實上咱們是沒法經過點操做符來獲取這個屬性的,這裏咱們只能用var obj={ ‘1’:1 }
來聲明對象,那該如何獲取屬性呢,js爲咱們提供了一個相似數組索引 [ ] 的方式來獲取對象
如上面的 var obj={name:’張三’}
咱們能夠用obj.name 或者 obj[‘name’] 這兩種方式來獲取 可是var obj={ ‘1’:1 } 只能用obj[‘1’]來獲取這個屬性值
那這兩種有什區別呢
. 運算符:右側必須是一個屬性名稱命名的簡單標識符,如.name
[]:右側必須是一個計算結果爲字符串的表達式
那既然[]是一個計算結果爲字符串的表達式,那就給[]賦予了強大的功能,如字符串拼接,三目運算符等
let obj={'1':123, name:'張三'}
console.log(obj['1'],obj.name,obj['name']) // 輸出 123 "張三" "張三"複製代碼
不一樣於Java中的比較,js有這兩種比較的方式,但他們有什麼區別呢
簡單來講: == 表明相同, ===表明嚴格相同, 爲啥這麼說呢
這麼理解: 當進行雙等號比較時候: 先檢查兩個操做數數據類型,若是相同, 則進行===比較, 若是不一樣, 則願意爲你進行一次類型轉換, 轉換成相同類型後再進行比較, 而===比較時, 若是類型不一樣,直接就是false.
比較過程:
雙等號==:
(1)若是兩個值類型相同,再進行三個等號(===)的比較
(2)若是兩個值類型不一樣,也有可能相等,需根據如下規則進行類型轉換在比較:
1)若是一個是null,一個是undefined,那麼相等
2)若是一個是字符串,一個是數值,把字符串轉換成數值以後再進行比較
三等號===:
(1)若是類型不一樣,就必定不相等
(2)若是兩個都是數值,而且是同一個值,那麼相等;若是其中至少一個是NaN,那麼不相等。(判斷一個值是不是NaN,只能使用isNaN( ) 來判斷)
(3)若是兩個都是字符串,每一個位置的字符都同樣,那麼相等,不然不相等。
(4)若是兩個值都是true,或是false,那麼相等
(5)若是兩個值都引用同一個對象或是函數,那麼相等,不然不相等
(6)若是兩個值都是null,或是undefined,那麼相等
那何時用==何時用===呢,我我的認爲的是
只有在判斷obj.a==null的時候使用雙等,等同於obj.a===undefind||obj.a===null
其餘一概使用===(三等號)由於咱們一般在判斷是否相等的時候首先就得肯定兩遍的類型
先來看一個現象,那麼爲何會出現這種狀況呢
由於 JS 採用 IEEE 754 雙精度版本(64位),而且只要採用 IEEE 754 的語言都有該問題。
咱們都知道計算機表示十進制是採用二進制表示的,因此 0.1 在二進制表示爲
// (0011) 表示循環
0.1 = 2^-4 * 1.10011(0011)
這個問題簡單來講是底層在表示0.1和0.2的時候採用的是二進制,在採用這個雙精度版本時,二進制相加後再轉換爲十進制的時候就會獲得結果是不等於0.3的
那麼咱們實際編碼時若是遇到這種狀況該怎麼判斷呢
原生的解決辦法以下
parseFloat((0.1 + 0.2).toFixed(10))或者轉換成整數計算後再轉換成小數
每一個函數都有 prototype
屬性,除了 Function.prototype.bind()
,該屬性指向原型。
每一個對象都有 __proto__
屬性,指向了建立該對象的構造函數的原型。其實這個屬性指向了 [[prototype]]
,可是 [[prototype]]
是內部屬性,咱們並不能訪問到,因此使用 _proto_ 來訪問。
對象能夠經過 __proto__
來尋找不屬於該對象的屬性,__proto__
將對象鏈接起來組成了原型鏈。
構造函數
function Foo(name,age){
this.name = name;
this.age = age;
//return this;默認有這一行
}
var f = new Foo('zhangshan',20);
//1.建立了一個新對象
//2.將this指向這個新對象
//3.執行構造函數裏面的代碼
//4.返回新對象(this)
var a={} //實際上是var a = new Object()語法糖
var a=[] //實際上是var a = new Array()語法糖
function Foo(){} // 實際上是var Foo = new Function()
// 使用instanceof判斷一個函數是不是一個變量的構造函數
// f instanceof Foo ==> true
// f.__proto__一層層往上尋找可否查詢到Foo.prototype
// f instanceof Object ==> true複製代碼
原型的規則和示例
// 1.全部的引用類型(數組,對象,函數),都具備對象特性,便可以自由擴展屬性(null除外)
var obj={};obj.a=100;
var arr={};arr.a=100;
function fn(){}
fn.a=100;
// 2.全部的引用類型(數組,對象,函數),都有一個__proto__(隱式原型)屬性,屬性值是一個普通的對象
console.log(obj.__proto__);
console.log(arr.__proto__);
console.log(fn.__proto__);
// 3.全部的函數,都有一個prototype屬性(顯式原型),屬性值是一個普通的對象
console.log(fn.prototype);
// 4.全部的引用類型(數組,對象,函數),__proto__屬性指向它的構造函數的prototype屬性值
console.log(obj.__proto__===Object.prototype);
// 5.當視圖獲得一個對象的某個屬性的時候,若是這個對象自己沒有這個屬性,那麼
去它的__proto__(即它的構造函數prototype)中去找
function Foo(name,age){
this.name = name;
}
Foo.prototype.alertName = function(){
alert(this.name);
}
//建立實例
var f = new Foo('zhangsan');
f.printName = function(){
console.log(this.name);
}
f.printName();//能夠獲取到printName這個屬性直接獲取
f.alertName();//獲取不到alertName屬性這是去找他的構造函數的prototype中去找
複製代碼
原型鏈
var f = new Foo('zhangsan');
f.toString();複製代碼
分析:
1.首先去自身的屬性中去找toString
方法,沒有則去隱式原型__proto__
去找
2.__proto__
指向其構造函數的顯式原型Foo.prototype
,如沒找到再去Foo.prototype
的隱式原型去找
3.Foo.prototype
的__proto__
又指向Object.prototype
顯式原型,而後再尋找其隱式原型__proto__
即查詢到toString
方法,這就是原型鏈
總結
Object
是全部對象的爸爸,全部對象均可以經過 __proto__
找到它 Function
是全部函數的爸爸,全部函數均可以經過 __proto__
找到它 Function.prototype
和 Object.prototype
是兩個特殊的對象,他們由引擎來建立 除了以上兩個特殊對象,其餘對象都是經過構造器 new
出來的 函數的 prototype
是一個對象,也就是原型 對象的 __proto__
指向原型, __proto__
將對象和原型鏈接起來組成了原型鏈
做用域和閉包是一個老生常談的問題,常常會出如今各大面試中。下面就來簡單介紹一下,
執行上下文
// 示例代碼:
console.log(a);//undefined
var a=100;
fn('zhangsan');// zhangsan 20
function fn(name){
age=20;
console.log(name,age);
var age;
}
// 解釋:
// 範圍 | 一段<script>標籤或者一個函數
// 全局 | 變量定義,函數聲明
// 函數 | 變量定義,函數聲明,this,arguments 複製代碼
this的指向
this
要在執行時才能確認,定義時沒法確認,使用bind
的時候必需要使用函數表達式
// 示例代碼:
var a = {
name:'A',
fn:function(){
console.log(this.name);
}
}
a.fn();//this===a
a.fn.call({name:'B'});//this==={name:'B'}
var fn1 = a.fn;
fn1();//this===window
// 分析:
// 1.做爲構造函數執行
// 2.做爲對象屬性執行
// 3.做爲普通函數執行
// 4.call apply bind
function fn1(name){
alert(name);
console.log(this);
}
fn1.call({x:100},'zhangsan');//call第一個參數就是this
// 執行後彈出'zhangsan' 打印{x:100}
var fn2 = function (name){
alert(name);
console.log(this);
}.bind({y:200})
fn2('zhangsan');
// 執行後彈出'zhangsan' 打印{y:200}複製代碼
做用域
// 1.沒有塊級做用域
if(true){
var name = 'zhangsan';
}
console.log(name);
// 2.有函數和全局做用域
var a = 100;
function fn(){
var a = 200;
console.log('fn',a);
}
console.log('global',a);
fn();
// 3.自由變量
var a = 100;
function fn(){
var b = 200;
console.log(a);//當前做用域沒有定義的變量叫作自由變量
console.log(b);
}
// 注:這裏打印a這個自由變量,則會去它的父級做用域去查詢a,所謂的父級做用域就
是在函數定義的時候的做用域
// 4.做用域鏈
var a = 100;
function F1(){
var b = 200;
function F2(){
var c = 300;
console.log(a);//a是自由變量,先去父級F1中找a,沒有再去F1的父級做用域找
console.log(b);//b是自由變量,先去父級F1中找b,找到後打印
console.log(c);
}
F2();
}
F1();
// 注:F2的父級做用域是F1,F1的父級做用域是全局,這就是做用域鏈複製代碼
什麼是閉包
閉包的定義很簡單:函數 A 返回了一個函數 B,而且函數 B 中使用了函數 A 的變量,函數 B 就被稱爲閉包。
function A() {
let a = 1
function B() {
console.log(a)
}
return B
}複製代碼
經典面試題,循環中使用閉包解決 var 定義函數的問題
for ( var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}複製代碼
首先由於 setTimeout 是個異步函數,全部會先把循環所有執行完畢,這時候 i 就是 6 了,因此會輸出一堆 6。
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i);
}複製代碼
到這裏咱們插入兩個常見的問題寫出如下程序的輸出結結果
var a = 6;
setTimeout(function () {
console.log(a);
a = 666;
}, 1000);
setTimeout(function () {
console.log(a);
a = 777;
}, 0);
a = 66;
//分析
// 1.執行第一行a=6
// 2.執行setTimeout後,傳入其中的函數會被暫存不會當即執行
// 3.執行最後一行a=66
// 4.等待全部程序執行完,處於空閒狀態時,當即查看有沒有暫存的執行隊列
// 5.發現暫存的執行函數,如沒有等待時間則當即執行,即第二個setTimeout,輸出a=66,執行a=777
// 6.暫存的執行函數有等待時間的,1秒後輸出a=777,再給a賦值666,所以會先輸出66,1秒後輸出777複製代碼
請輸出如下程序的結果,並簡單分析一下過程
代碼1: var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
console.log(object.getNameFunc()());
複製代碼
|
代碼2: var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
console.log(object.getNameFunc()());複製代碼
|
// 分別輸出 The Window, My Object
// 1.object.getNameFunc()返回一個函數,再調用()執行的時候上下文this爲全局。
// 2. object.getNameFunc()返回一個函數的時候執行了that=this;,這時候that緩存的是上下文的this。即爲 object,再調用()執行方法的時候打印的就是object的name
在滾動事件中須要作個複雜計算或者實現一個按鈕的防二次點擊操做。
這些需求均可以經過函數防抖動來實現。尤爲是第一個需求,若是在頻繁的事件回調中作複雜計算,頗有可能致使頁面卡頓,不如將屢次計算合併爲一次計算,只在一個精確點作操做。
PS:防抖和節流的做用都是防止函數屢次調用。區別在於,假設一個用戶一直觸發這個函數,且每次觸發函數的間隔小於wait,防抖的狀況下只會調用一次, 而節流的 狀況會每隔必定時間(參數wait)調用函數。
防抖動和節流本質是不同的。防抖動是將屢次執行變爲最後一次執行,節流是將屢次執行變成每隔一段時間執行。
袖珍版的防抖理解一下防抖的實現:
// func是用戶傳入須要防抖的函數
// wait是等待時間
const debounce = (func, wait = 50) => {
// 緩存一個定時器id
let timer = 0
// 這裏返回的函數是每次用戶實際調用的防抖函數
// 若是已經設定過定時器了就清空上一次的定時器
// 開始一個新的定時器,延遲執行用戶傳入的方法
return function(...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, wait)
}
}
// 不難看出若是用戶調用該函數的間隔小於wait的狀況下,上一次的時間還未到就被清除了,
並不會執行函數
複製代碼
這裏只是簡單說明一下這個概念,節流函數的實現,感興趣的能夠嘗試本身寫一下。
let a = {
age: 1
}
let b = a
a.age = 2
console.log(b.age) // 2
複製代碼
從上述例子中咱們能夠發現,若是給一個變量賦值一個對象,那麼二者的值會是同一個引用,其中一方改變,另外一方也會相應改變。
一般在開發中咱們不但願出現這樣的問題,咱們可使用淺拷貝來解決這個問題。
淺拷貝
首先能夠經過 Object.assign
來解決這個問題。
let a = {
age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1
複製代碼
固然咱們也能夠經過展開運算符(…)來解決
let a = {
age: 1
}
let b = {...a}
a.age = 2
console.log(b.age) // 1
複製代碼
一般淺拷貝就能解決大部分問題了,可是當咱們遇到以下狀況就須要使用到深拷貝了
let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = {...a}
a.jobs.first = 'native'
console.log(b.jobs.first) // native
複製代碼
淺拷貝只解決了第一層的問題,若是接下去的值中還有對象的話,那麼就又回到剛開始
深拷貝
let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE複製代碼
可是該方法也是有侷限性的:
在遇到函數、 undefined
或者 symbol
的時候,該對象也不能正常的序列化
let a = {
age: undefined,
sex: Symbol('male'),
jobs: function() {},
name: 'wb'
}
let b = JSON.parse(JSON.stringify(a))
console.log(b) // {name: "wb"}
複製代碼
你會發如今上述狀況中,該方法會忽略掉函數和 undefined
。
可是在一般狀況下,複雜數據都是能夠序列化的,因此這個函數能夠解決大部分問題,而且該函數是內置函數中處理深拷貝性能最快的。固然若是你的數據中含有以上三種狀況下,可使用遞歸來實現一個深拷貝函數(此部分後續會在vue篇進行實現),爲實現深拷貝函數,這裏再引入一個知識點
咱們知道可使用typeof函數來判斷一個類型,可是使用typeof判斷確不是很準確,以下
那麼若是我要精確判斷一個類型是對象仍是數組,或者是函數該如何實現呢?
這裏咱們可使用Object.prototype.toString.call(obj) 來精確判斷類型
function typeOf (obj) {
const toString = Object.prototype.toString
const map = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object'
}
return map[toString.call(obj)]
}
複製代碼