從JS的運行
,設計
,數據
,應用
四個角度來梳理JS核心的知識點數組
JS運行瀏覽器
JS設計服務器
JS數據markdown
JS應用閉包
大概分爲四個階段app
詞法分析:將js代碼中的字符串分割爲有意義的代碼塊,稱爲詞法單元
異步
var a = 1
語法分析:將詞法單元
流轉換成一顆 抽象語法樹(AST)
,並對生成的AST樹節點進行處理,函數
爲何須要抽象語法樹呢?oop
預解析階段:性能
執行階段:
指定了函數和變量的做用範圍
全局做用域
和函數做用域
,全局變量和函數聲明會提高
function foo() {}
var foo = function () {}
var foo = new Function()
直接建立
和變量賦值
變量賦值函數
和賦值普通變量
的優先級按位置來,變量名相同前者被覆蓋有不一樣的做用域,就有不一樣的執行環境,咱們須要來管理這些上下文的變量
執行上下文棧
來維護,後進先出變量環境
和詞法環境
變量環境
裏就包含着當前環境裏可以使用的變量做用域鏈
詞法做用域
決定的靜態做用域
,由函數聲明的位置決定,和函數在哪調用無關,也就js這麼特殊this也是!
)var a = 2;
function foo() {
console.log(a); // 靜態2 動態3
}
function bar() {
var a = 3;
foo();
}
bar();
複製代碼
做用域
的限制,咱們沒法在函數做用域外部訪問到函數內部定義的變量,而實際需求須要,這裏就用到了閉包
1. 閉包做用
for循環遍歷進行事件綁定
輸出i值時爲for循環的長度+1for(var i = 0;i < 3;i++){
document.getElementById(`item${i+1}`).onclick = function() {
console.log(i);//3,3,3
}
}
複製代碼
閉包
Closure
的對象,用來存儲閉包變量for(var i = 0;i < 3;i++){
document.getElementById(`item${i+1}`).onclick = make(i);
}
function make(e) {
return function() {
console.log(e)//0,1,2
};
複製代碼
閉包注意
因爲疏忽或錯誤形成程序未能釋放已經再也不使用的內存
,就形成了內存泄漏
對象
、構造函數
、new
操做符理念,而拋棄掉了了複雜的class
(類)概念繼承
的機制,來把全部對象聯繫起來,就可使用構造函數沒法共享屬性和方法
prototype
屬性prototype
屬性,就是咱們常說的原型prototype
屬性,實例對象須要共享的方法,都放在此對象上,整個核心設計完成後,後面的API也就瓜熟蒂落
原型
proto
Object.getPrototype()
方法獲取原型constructor 原型沒有指向實例,由於一個構造函數能夠有多個對象實例 可是原型指向構造函數是有的,每一個原型都有一個constructor屬性指向關聯的構造函數
function Per() {} // 構造函數
const chi = new Per() // 實例對象
chi.__proto__ === Per.prototype // 獲取對象的原型 也是就構造函數的prototype屬性
Per.prototype.constructor === Per // constructor屬性 獲取當前原型關聯的構造函數
複製代碼
實例與原型
原型鏈
function Foo() {}
Foo.prototype.name = 'tom'
const foo = new Foo()
foo.name = 'Jerry'
console.log(foo.name); // Jerry
delete foo.name
console.log(foo.name); // tom
複製代碼
首先亮出你們熟悉的網圖
就是實例與構造函數,原型之間的鏈條關係
實例的 proto 指向 原型
構造函數的 prototype 屬性 指向 原型
原型的 constructor 屬性 指向 構造函數
全部構造函數的 proto 指向 Function.prototype
Function.prototype proto 指向 Object.prototype
Object.prototype proto 指向 null
函數對象原型(Function.prototype) 是負責造構造函數的機器,包含Object、String、Number、Boolean、Array,Function。 再由構造函數去製造具體的實例對象
function Foo() {}
// 1. 全部構造函數的 __proto__ 指向 Function.prototype
Foo.__proto__ // ƒ () { [native code] }
Function.__proto__ // ƒ () { [native code] }
Object.__proto__ // ƒ () { [native code] }
// 2. 全部構造函數原型和new Object創造出的實例 __proto__ 指向 Object.prototype
var o = new Object()
o.__proto__ // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ ...}
Function.prototype.__proto__ // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ ...}
// 3. Object.prototype 指向null
Object.prototype.__proto__ // null
複製代碼
obj.b();
// 指向obj`var b = obj.b; b();
// 指向全局window(函數方法其實就是window對象的方法,因此與上同理,誰調用就指向誰)var b = new Per();
// this指向當前實例對象obj.b.apply(object, []);
// this指向當前指定的值誰調用就指向誰
const obj = {a: 1, f: function() {console.log(this, this.a)}}
obj.f(); // 1
const a = 2;
const win = obj.f;
win(); // 2
function Person() {
this.a = 3;
this.f = obj.f;
}
const per = new Person()
per.f() // 3
const app = { a: 4 }
obj.f.apply(app); // 4
複製代碼
this
指向是動態做用域
,誰調用指向誰,
var obj = {
value: 1,
}
function foo (name, old) {
return {
value:this.value,
name,
old
}
}
foo.call(obj, 'tom', 12); // {value: 1, name: "tom", old: 12}
複製代碼
Function.prototype.call1 = function (context, ...args) {
if(typeof this !== 'function') {throw new Error('Error')} // 非函數調用判斷處理
context = context || window; // 非運算符斷定傳入參數 null則指向window
const key = Symbol(); // 利用symbol建立惟一的屬性名,防止覆蓋原有屬性
context[key] = this; // 把函數做爲對象的屬性,函數的this就指向了對象
const result = context[key](...args) // 臨時變量賦值爲執行對象方法
delete context[key]; // 刪除方法,防止污染對象
return result // return 臨時變量
}
複製代碼
改變this指向,大部分是爲了借用方法或屬性
Object
的toString
方法 Object.prorotype.toString.call()
function Chi() {Par.call(this)}
var obj = {
value: 1,
}
function foo (name, old) {
return {
value:this.value,
name,
old
}
}
foo.apply(obj, ['tom', 12], 24); // {value: 1, name: "tom", old: 12}
複製代碼
Function.prototype.apply1 = function (context, args) { // 與call實現的惟一區別,此處不用解構
if(typeof this !== 'function') {throw new Error('Error')} // 非函數調用判斷處理
context = context || window; // 非運算符斷定傳入參數 null則指向window
const key = Symbol(); // 利用symbol建立惟一的屬性名,防止覆蓋原有屬性
context[key] = this; // 把函數做爲對象的屬性,函數的this就指向了對象
const result = context[key](...args) // 臨時變量賦值爲執行對象方法
delete context[key]; // 刪除方法,防止污染對象
return result // return 臨時變量
}
複製代碼
const obj = {
value: 1
};
function foo(name, old) {
return {
value: this.value,
name,
old
}
}
const bindFoo = foo.bind(obj, 'tom');
bindFoo(12); // {value: 1, name: "tom", old: 12}
複製代碼
Function.prototype.bind1 = function (context, ...args) {
if(typeof this !== 'function') {throw new Error('Error')} // 非函數調用判斷處理
const self = this; // 保存當前執行環境的this
const Foo = function() {} // 保存原函數原型
const res = function (...args2) { // 建立一個函數並返回
return self.call( // 函數內返回
this instanceof res ? this : context, // 做爲構造函數時,this指向實例,:做爲普通函數this正常指向傳入的context
...args, ...args2) // 兩次傳入的參數都做爲參數返回
}
Foo.prototype = this.prototype; // 利用空函數中轉,保證在新函數原型改變時bind函數原型不被污染
res.prorotype = new Foo();
return res;
}
複製代碼
看看new
出的實例能作什麼
function Persion(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function () {
console.log(this.name)
}
var per = new Person('tom', 10)
per.name // 10 可訪問構造函數的屬性
per.sayName // tom 訪問prototype的屬性
複製代碼
new實現
var person = factory(Foo)
function factory() { // new是關鍵字,沒法覆蓋,函數替代
var obj = {}; // 新建對象obj
var con = [].shift.call(arguments); // 取出構造函數
obj._proto_ = con.prototype; // obj原型指向構造函數原型
var res = con.apply(obj, arguments); // 構造函數this指向obj
return typeof(res) === 'object' ? ret : obj;
}
複製代碼
寄生組合繼承實現
function P (name) { this.name = name; } // 父類上綁定屬性動態傳參
P.prototype.sayName = function() { // 父類原型上綁定方法
console.log(111)
}
function F(name) { // 子類函數裏,父類函數利用`call`函數this指向子類,傳參並執行
P.call(this, name)
}
const p = Object.create(P.prototype) // Object.create 不會繼承構造函數多餘的屬性和方法
p.constructor = F; // constructor屬性丟失,從新指向
F.prototype = p; // 子類原型 指向 中轉對象
const c = new F('tom'); // 子類實例 化
c.name // tom
c.sayName() // 111
複製代碼
JS分爲基本類型和引用類型
Boolean
Undefined
String
Number
Null
Symbol
Object
Funciton
Array
等js數據類型中分爲基本類型,和引用類型
兩個對象 指向了同一地址 修改其中一個就會影響另外一個
特殊的數組對象方法(深拷貝一層)
obj2 = Object.assign({}, obj1)
arr2 = [].concat(arr1)
arr2 = arr1.slice(0)
arr2 = Array.form(arr1)
arr2 = [...arr1];
以上方法都只能深拷貝一層
JSON.parse(JSON.stringify(obj))(多層) 不拷貝一個對象,而是拷貝一個字符串,會開闢一個新的內存地址,切斷了引用對象的指針聯繫
缺點
手動實現深拷貝(多層)
function Judgetype(e) {
return Object.prototype.toString.call(e).slice(8, -1).toLowerCase();
}
function Loop(param) {
let target = null;
if(Judgetype(param) === 'array') {
target = [];
for(let key of param.keys()){
target[key] = Deep(param[key]);
}
} else {
target = {};
Object.keys(obj).forEach((val) => {
target[key] = Deep(param[key]);
})
}
return target;
}
function Deep(param) {
//基本數據類型
if(param === null || (typeof param !== 'object' && typeof param !== 'function')) {
return param;
}
//函數
if(typeof param === 'function') {
return new Function('return ' + param.toString())();
}
return Loop(param);
}
複製代碼
typeof
沒法區分 object, null 和 array
typeof(1) // number
typeof('tom') // string
typeof(undefined) // undefined
typeof(null) // object
typeof(true) // boolean
typeof(Symbol(1)) // symbol
typeof({a: 1}) // object
typeof(function () {}) // function
typeof([]) // object
複製代碼
instanceof
[] instanceof Array; // true
{} instanceof Object;// true
var a = function (){}
a instanceof Function // true
複製代碼
Object.prototype.toString.call
目前最優方案
toString() 是 Object 的原型方法,調用該方法,默認返回當前對象的 [[Class]] 。這是一個內部屬性,其格式爲 [object Xxx] ,其中 Xxx 就是對象的類型。
對於 Object 對象,直接調用 toString() 就能返回 [object Object] 。而對於其餘對象,則須要經過 call / apply 來調用才能返回正確的類型信息
這裏就是call的應用場景,巧妙利用call借用Object的方法實現類型的判斷
function T(e) {
return Object.prototype.toString.call(e).slice(8, -1).toLowerCase();
}
T(1) // number
T('a') // string
T(null) // null
T(undefined) // undefined
T(true) // boolean
T(Symbol(1)) // symbol
T({a: 1}) // object
T(function() {}) // function
T([]) // array
T(new Date()) // date
T(/at/) // RegExp
複製代碼
==
非嚴格比較 容許 類型轉換===
嚴格比較 不容許 類型轉換引用數據類型棧中存放地址,堆中存放內容,即便內容相同 ,但地址不一樣,因此二者仍是不等的
const a = {c: 1}
const b = {c: 1}
a === b // false
複製代碼
==
可能會有類型轉換,不只在相等比較上,在作運算的時候也會產生類型轉換,這就是咱們說的隱式轉換布爾比較,先轉數字
true == 2 // false
||
1 == 2
// if(X)
var X = 10
if(X) // true
10 ==> true
// if(X == true)
if(X == true) // false
10 == true
||
10 == 1
複製代碼
數字和字符串作比較,字符串
轉數字
0 == '' // true
||
0 == 0
1 == '1' // true
||
1 == 1
複製代碼
對象類型和原始類型的相等比較
[2] == 2 // true
|| valueOf() // 調用valueOf() 取自身值
[2] == 2
|| toString() // 調用toString() 轉字符串
"2" == 2
|| Number() // // 數字和字符串作比較,`字符串`轉`數字`
2 == 2
複製代碼
小結
js使用某些操做符會致使類型變換, 常見是+,==
字符串
轉數字
課外小題 實現 a == 1 && a == 2 && a == 3
const a = {
i: 1,
toString: function () {
return a.i++;
}
}
a == 1 && a == 2 && a == 3
複製代碼
===
判斷時二者仍是不等的// 判斷二者非對象返回
// 判斷長度是否一致
// 判斷key值是否相同
// 判斷相應的key值裏的對應的值是否相同
這裏僅僅考慮 對象的值爲object,array,number,undefined,null,string,boolean
關於一些特殊類型 `function date RegExp` 暫不考慮
function Judgetype(e) {
return Object.prototype.toString.call(e).slice(8, -1).toLowerCase();
}
function Diff(s1, s2) {
const j1 = Judgetype(s1);
const j2 = Judgetype(s2);
if(j1 !== j2){
return false;
}
if(j1 === 'object') {
if(Object.keys(s1).length !== Object.keys(s2).length){
return false;
}
s1[Symbol.iterator] = function* (){
let keys = Object.keys( this )
for(let i = 0, l = keys.length; i < l; i++){
yield {
key: keys[i],
value: this[keys[i]]
};
}
}
for(let {key, value} of s1){
if(!Diff(s1[key], s2[key])) {
return false
}
}
return true
} else if(j1 === 'array') {
if(s1.length !== s2.length) {
return false
}
for(let key of s1.keys()){
if(!Diff(s1[key], s2[key])) {
return false
}
}
return true
} else return s1 === s2
}
Diff( {a: 1, b: 2}, {a: 1, b: 3}) // false
Diff( {a: 1, b: [1,2]}, {a: 1, b: [1,3]}) // false
複製代碼
其實對象遍歷 return 也能夠用for in, 關於遍歷原型的反作用能夠用hasOwnproperty判斷去彌補
for(var key in s1) {
if(!s1.hasOwnProperty(key)) {
if(!Diff(s1[key], s2[key])) {
return false
}
}
}
複製代碼
`最普通 for循環` // 較爲麻煩
for(let i = 0,len = arr.length; i < len; i++) {
console.log(i, arr[i]);
}
`forEach` 沒法 break return
`for in` 不適合遍歷數組,
`for...in` 語句在w3c定義用於遍歷數組或者對象的屬性
1. index索引爲字符串型數字,不能直接進行幾何運算
2. 遍歷順序有可能不是按照實際數組的內部順序
3. 使用for in會遍歷數組全部的可枚舉屬性,包括原型
`for of` 沒法獲取下標
// for of 兼容1
for(let [index,elem] of new Map( arr.map( ( item, i ) => [ i, item ] ) )){
console.log(index);
console.log(elem);
}
// for of 兼容2
let arr = [1,2,3,4,5];
for(let key of arr.keys()){
console.log(key, arr[key]);
}
for(let val of arr.values()){
console.log(val);
}
for(let [key, val] of arr.entries()){
console.log(key, val);
}
2.
複製代碼
缺點:會遍歷出對象的全部可枚舉的屬性, 好比prototype上的
var obj = {a:1, b: 2}
obj.__proto__.c = 3;
Object.prototype.d = 4
for(let val in obj) {
console.log(val) // a,b,c,d
}
// 優化
for(let val in obj) {
if(obj.hasOwnProperty(val)) { // 判斷屬性是存在於當前對象實例自己,而非原型上
console.log(val) // a,b
}
}
複製代碼
var obj = { a:1, b: 2 }
Object.keys(obj).forEach((val) => {
console.log(val, obj[val]);
// a 1
// b 2
})
複製代碼
var obj = { a:1, b: 2 }
obj[Symbol.iterator] = function* (){
let keys = Object.keys( this )
for(let i = 0, l = keys.length; i < l; i++){
yield {
key: keys[i],
value: this[keys[i]]
};
}
}
for(let {key, value} of obj){
console.log( key, value );
// a 1
// b 2
}
複製代碼
0.1 + 0.2 = 0.30000000000000004
function getLen(n) {
const str = n + '';
const s1 = str.indexOf('.')
if(s1) {
return str.length - s1 - 1
} else {
return 0
}
}
function add(n1, n2) {
const s1 = getLen(n1)
const s2 = getLen(n2)
const max = Math.max(s1, s2)
return (n1 * Math.pow(10, max) + n2 * Math.pow(10, max)) / Math.pow(10, max)
}
add(11.2, 2.11) // 13.31
複製代碼
function add(a, b) {
let i = a.length - 1;
let j = b.length - 1;
let carry = 0;
let ret = '';
while(i>=0|| j>=0) {
let x = 0;
let y = 0;
let sum;
if(i >= 0) {
x = a[i] - '0';
i--
}
if(j >=0) {
y = b[j] - '0';
j--;
}
sum = x + y + carry;
if(sum >= 10) {
carry = 1;
sum -= 10;
} else {
carry = 0
}
ret = sum + ret;
}
if(carry) {
ret = carry + ret;
}
return ret;
}
add('999999999999999999999999999999999999999999999999999999999999999', '1')
// 1000000000000000000000000000000000000000000000000000000000000000
複製代碼
場景:
定義:
實現思想:
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
複製代碼
場景:
定義:
function foo(func, wait) {
var context, args;
var flag = 0;
return function () {
var now = +new Date();
context = this;
args = arguments;
if(now - flag > 0) {
func.apply(context, args);
flag = now;
}
}
}
複製代碼
function foo(func, wait) {
var context, args;
var timeout;
return function() {
if(!timeout){
setTimeout(()=>{
timeout = null;
func.apply(context, args);
}, wait)
}
}
}
複製代碼
參數複用
,提早返回
,延遲執行
function add(...args) {
var fn = function(...args1) {
return add.apply(this, [...args, ...args1]);
}
fn.toString = function() {
return args.reduce(function(a, b) {
return a + b;
})
}
return fn;
}
add(1)(2)(3).toString(); // 6
複製代碼