title: js面向?qū)ο?br>
date: 2017年8月17日 18:58:05
updated: 2017年8月27日 09:27:20
面向?qū)ο?/h1>
JavaScript 中的數(shù)據(jù)類型
值類型和引用類型復制
var foo = 'bar' // 存的是值
var bar = foo
var obj = { // obj 存的是地址
foo: bar
}
var obj1 = obj // obj1 中存儲的是和 obj 一樣的地址
obj.foo = 'baz' // 地址一樣, 指向的內(nèi)用一樣, 所以修改的是同一個對象
console.log(obj, obj1)
基本類型數(shù)據(jù): undefined
null
Boolean
Number
String
直接按值存放
基本類型在內(nèi)存中占據(jù)固定大小的空間, 被保存在棧內(nèi)存中
從一個變量向另一個變量復制基本類型的值 -- 復制的是值的副本
引用類型數(shù)據(jù): 變量保存的是一個指針, 這個指針地址指向堆內(nèi)存中的數(shù)據(jù).
引用類型的值是對象, 保存在堆內(nèi)存
保存引用類型的變量保存的并不是對象本身, 而是一個指向該對象的指針
從一個變量向另一個變量復制引用類型的值的時候, 復制的是引用指針, 因此兩個變量指向的是同一個對象.
值類型和引用類型參數(shù)傳遞
var a = 123
var b = {
foo: 'bar'
}
function f(a, b) {
a = 456 // var 形參a = 實參a 復制值
b.foo = 'baz' // var 形參b = 實參b 復制引用
b = { // b 中的地址指向新的對象 與 之前的對象斷開連接
foo: 'bbb'
}
}
f(a, b)
console.log(a, b) // 123, Object{foo: 'bbb'}
- 基本類型數(shù)據(jù): 按值傳遞
- 引用類型數(shù)據(jù): 按引用傳遞
深拷貝與淺拷貝
-
淺拷貝
當拷貝對象時, 如果屬性時對象或者數(shù)組, 這時候傳遞的也只是一個地址. 兩者的屬性值指向同一內(nèi)存空間.
var a = {
key1:"11111"
}
function copy(p) {
var c = {}
for (var i in p) {
c[i] = p[i]
}
return c
}
a.key2 = ['小輝','小輝']
var b = copy(a)
b.key3 = '33333'
alert(b.key1) //11111
alert(b.key3) //33333
alert(a.key3) //undefined
alert(a.key2) // ['小輝','小輝']
alert(b.key2) // ['小輝','小輝']
b.key2.push("大輝")
alert(b.key2) //小輝,小輝校赤,大輝
alert(a.key2) //小輝掠归,小輝憔足,大輝
-
深拷貝
不希望拷貝前后的對象之間有關聯(lián), 那么這個時候就會用到深拷貝.
function isObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]'
}
function isArray(obj) {
return Object.prototype.toString.call(obj) === '[object Array]'
}
// 利用遞歸實現(xiàn)深拷貝對象復制
function extend(target, source) {
for(var key in source) {
// 判斷如果當前遍歷項 source[key] 是一個數(shù)組轩缤,則先讓 target[key] = 數(shù)組
// 然后遍歷 source[key] 將其中的每一項都復制到 target[key] 中
if (isArray(source[key])) {
target[key] = []
// 遍歷 source[key] 復制到 target[key] 中
extend(target[key], source[key])
} else if (isObject(source[key])) {
target[key] = {}
extend(target[key], source[key])
} else {
target[key] = source[key]
}
}
}
類型檢測
typeof
-
instanceof
-- 引用類型推薦使用
Object.prototype.toString.call()
javascript執(zhí)行過程
-
預解析
-
全局預解析
- 所有變量個函數(shù)聲明都會提前
- 同名的函數(shù)和變量函數(shù)的優(yōu)先級高
-
函數(shù)內(nèi)部預解析
- 所有的變量, 函數(shù) 和 形參 都會參與預解析
執(zhí)行
面向?qū)ο?- 創(chuàng)建對象
簡單方式
var foo = 'bar' // 存的是值
var bar = foo
var obj = { // obj 存的是地址
foo: bar
}
var obj1 = obj // obj1 中存儲的是和 obj 一樣的地址
obj.foo = 'baz' // 地址一樣, 指向的內(nèi)用一樣, 所以修改的是同一個對象
console.log(obj, obj1)
基本類型數(shù)據(jù): undefined
null
Boolean
Number
String
直接按值存放
基本類型在內(nèi)存中占據(jù)固定大小的空間, 被保存在棧內(nèi)存中
從一個變量向另一個變量復制基本類型的值 -- 復制的是值的副本
引用類型數(shù)據(jù): 變量保存的是一個指針, 這個指針地址指向堆內(nèi)存中的數(shù)據(jù).
引用類型的值是對象, 保存在堆內(nèi)存
保存引用類型的變量保存的并不是對象本身, 而是一個指向該對象的指針
從一個變量向另一個變量復制引用類型的值的時候, 復制的是引用指針, 因此兩個變量指向的是同一個對象.
var a = 123
var b = {
foo: 'bar'
}
function f(a, b) {
a = 456 // var 形參a = 實參a 復制值
b.foo = 'baz' // var 形參b = 實參b 復制引用
b = { // b 中的地址指向新的對象 與 之前的對象斷開連接
foo: 'bbb'
}
}
f(a, b)
console.log(a, b) // 123, Object{foo: 'bbb'}
淺拷貝
當拷貝對象時, 如果屬性時對象或者數(shù)組, 這時候傳遞的也只是一個地址. 兩者的屬性值指向同一內(nèi)存空間.
var a = {
key1:"11111"
}
function copy(p) {
var c = {}
for (var i in p) {
c[i] = p[i]
}
return c
}
a.key2 = ['小輝','小輝']
var b = copy(a)
b.key3 = '33333'
alert(b.key1) //11111
alert(b.key3) //33333
alert(a.key3) //undefined
alert(a.key2) // ['小輝','小輝']
alert(b.key2) // ['小輝','小輝']
b.key2.push("大輝")
alert(b.key2) //小輝,小輝校赤,大輝
alert(a.key2) //小輝掠归,小輝憔足,大輝
深拷貝
不希望拷貝前后的對象之間有關聯(lián), 那么這個時候就會用到深拷貝.
function isObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]'
}
function isArray(obj) {
return Object.prototype.toString.call(obj) === '[object Array]'
}
// 利用遞歸實現(xiàn)深拷貝對象復制
function extend(target, source) {
for(var key in source) {
// 判斷如果當前遍歷項 source[key] 是一個數(shù)組轩缤,則先讓 target[key] = 數(shù)組
// 然后遍歷 source[key] 將其中的每一項都復制到 target[key] 中
if (isArray(source[key])) {
target[key] = []
// 遍歷 source[key] 復制到 target[key] 中
extend(target[key], source[key])
} else if (isObject(source[key])) {
target[key] = {}
extend(target[key], source[key])
} else {
target[key] = source[key]
}
}
}
typeof
instanceof
-- 引用類型推薦使用Object.prototype.toString.call()
預解析
-
全局預解析
- 所有變量個函數(shù)聲明都會提前
- 同名的函數(shù)和變量函數(shù)的優(yōu)先級高
-
函數(shù)內(nèi)部預解析
- 所有的變量, 函數(shù) 和 形參 都會參與預解析
執(zhí)行
直接通過 new Object()
創(chuàng)建
var person = new Object()
person.name = 'Jack'
person.age = 18
person.sayHello = function(){
console.log('Hello ' + this.name)
}
字面量的方式創(chuàng)建
var person = {
name: 'jack'
age: 18
sayHello: function(){
console.log('Hello ' + this.name)
}
}
存在的問題: 生成多個 person
的實例對象, 代碼過于冗余, 重復性太高
工廠函數(shù)
function createPerson (name, age) {
return {
name: name
age: age
sayHello: function(){
console.log('Hello ' + this.name)
}
}
}
var p1 = createPerson('xiaoqiang', 28)
var p2 = createPerson('xiaogang', 18)
通過工廠模式解決了創(chuàng)建多個相似對象代碼冗余的問題, 但是也帶來了新的問題 -- 使用工廠模式無法判斷對象的類型, 與字面量沒什么不同, 都是 Object
構(gòu)造函數(shù)
function Person (name, age) {
this.name = name
this.age = age
this.sayHello = function(){
console.log('Hello ' + this.name)
}
}
var p1 = new Person('xiaoqiang', 18)
p1.sayHello()
var p2 = new Person('xiaogang', 28)
p2.sayHello()
構(gòu)造函數(shù) Person()
與 工廠函數(shù) createPerson()
的不同:
- 沒有顯示的創(chuàng)建對象
- 直接將屬性和方法賦給了
this
對象 - 沒有
return
語句 - 函數(shù)名是大寫的
Person
構(gòu)造函數(shù)與普通函數(shù)的不同
- 構(gòu)造函數(shù)與普通函數(shù)類似, 不同的是約定 -- 構(gòu)造函數(shù)采用
Pascal
命名 (首字母大寫) - 調(diào)用構(gòu)造函數(shù)采用
new 構(gòu)造函數(shù)名()
- 調(diào)用構(gòu)造函數(shù)返回的就是新創(chuàng)建的對象, 不需要寫
return
-
return 簡單類型
仍返回新創(chuàng)建的對象 -
return 引用類型
返回引用
function Person(name, age){ this.name = name this.age = age // return // return 0 // return '123' // return true // return false // return [1,2,3] // return {} } var p = new Person() console.log(p) // 沒有 return -- 返回 new Person // return -- 返回 new Person // return 0 -- 返回 new Person // return '123' -- 返回 new Person // return true -- 返回 new Person // return false -- 返回 new Person // return [1,2,3] -- 返回 [1,2,3] // return {} -- 返回 {}
-
- 在構(gòu)造函數(shù)內(nèi)部使用
this
來表示新創(chuàng)建的對象, 使用方式this.XXX = XXX
創(chuàng)建 Person
的實例, 需要使用 new
操作符. 這種方式調(diào)用構(gòu)造函數(shù)會經(jīng)歷以下4個階段:
1.創(chuàng)建一個新對象
2.將構(gòu)造函數(shù)的作用域賦給新對象 -- this 指向新對象
3.執(zhí)行構(gòu)造函數(shù)中的代碼
4.返回新對象
// 構(gòu)造函數(shù)就是普通函數(shù)
// 好處:
// 1. 創(chuàng)建對象的代碼更優(yōu)雅了
// 2. 可以判斷類型了垦搬,可以很方便的判斷實例和構(gòu)造函數(shù)之間的關系
function Person(name, age) {
// 如果使用 new 操作符調(diào)用該函數(shù)网严,則:
// 1. 先創(chuàng)建一個對象 var instance = {}
// 2. 然后將內(nèi)部的 this 指向 instance this = instance
// 3. 用戶通過操作 this 從而操作 instance
// 4. 在方法代碼的最后將 this 或者 instance 作為返回值默認返回
this.name = name
this.age = age
this.sayHello = function () {
console.log('helloi ' + this.name)
}
// 在函數(shù)的結(jié)尾處會將 this 返回识樱,也就是 instance
// return this
}
function Car() { }
// 普通函數(shù)調(diào)用,內(nèi)部 this 指向 window
// Person('小明', 15)
// 使用 new 操作符調(diào)用函數(shù)就叫做構(gòu)造函數(shù)調(diào)用
var p1 = new Person('小明', 18)
var p2 = new Person('小剛', 15)
p1.sayHello()
p2.sayHello()
console.log(p1.constructor === Car)
console.log(p2.constructor === Person)
// instanceof 操作符也可以用來判斷實例與構(gòu)造函數(shù)的關系
// constructor 和 instanceof 都可以用來判斷實例與構(gòu)造函數(shù)的關系
// 但是更建議使用 instanceof
console.log(p1 instanceof Person)
構(gòu)造函數(shù)和實例對象的關系
每一個實例對象中都有一個 constructor
屬性, 改屬性指向創(chuàng)建該實例的構(gòu)造函數(shù).
console.log(p1.constructor === Person) //true
console.log(p2.constructor === Person) //true
console.log(p1.constructor === p2.constructor) //true
p1 instanceof Peson // true
p2 instanceof Peson // true
- 構(gòu)造函數(shù)是根據(jù)具體事物抽象出來的模板
- 實例對象時根據(jù)抽象出來的構(gòu)造函數(shù)模板得到的具體函數(shù)
- 每一個實例對象都有一個
constructor
屬性, 改屬性指向創(chuàng)建該實例的構(gòu)造函數(shù) - 可以通過實例的
constructor
屬性來判斷實例與構(gòu)造函數(shù)之間的關系
構(gòu)造函數(shù)的問題
function Person(name, age) {
this.name = name
this.age = age
this.type = type
this.sayHello = function () {
console.log('helloi ' + this.name)
}
}
var p1 = new Person('xiaoqiang', 16)
var p2 = new Person('xiaogang', 18)
每實例化一個對象, type
和 sayHello
都是一樣的內(nèi)容, 每生成一個實例, 都會多占用一些內(nèi)存,
p1.sayHello === p2.sayHello // false
對于這種問題, 需要將共享的函數(shù)定義到構(gòu)造函數(shù)外部:
function Person(name, age) {
this.name = name
this.age = age
this.type = type
this.sayHello = sayHello
}
function sayHello = function () {
console.log('helloi ' + this.name)
}
p1.sayHello === p2.sayHello // true
雖然解決了內(nèi)存問題, 但是有多個共享函數(shù)又會造成命名沖突的問題.
解決辦法: 將多個函數(shù)放入一個對象中來避免全局命名沖突:
var fns = {
sayHello: function () { },
showAge: function () { }
}
function Person(name, age) {
this.name = name
this.age = age
this.sayHello = fns.sayHello
this.showAge = fns.showAge
}
var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)
console.log(p1.sayHello === p2.sayHello) // => true
console.log(p1.sayAge === p2.sayAge) // => true
原型
更好的解決辦法 prototype
javascript規(guī)定: 每一個構(gòu)造函數(shù)內(nèi)部都有一個 prototype
屬性, 該屬性指向另一個對象. 這個對象的所有屬性和方法, 都會被構(gòu)造函數(shù)的實例繼承.
我們可以把所有對象實例需要共享的屬和方法直接定義在 prototype
對象上.
function Person (name, age) {
this.name = name
this.age = age
}
console.log(Person.prototype)
Person.prototype.type = 'human'
Person.prototype.sayHello = function(){
console.log('hello ' + this.name)
}
var p1 = new Person(...)
var p2 = new Person(...)
console.log(p1.sayHello === p2.sayHello) // true
所有實例的 type
屬性和 sayHello
方法, 指向的都是同一內(nèi)存地址 -- prototype
對象
構(gòu)造函數(shù)震束、實例怜庸、原型三者之間的關系
每創(chuàng)建一個函數(shù), 系統(tǒng)就會為這個函數(shù)自動分配一個 prototype 指針, 指向他的原型對象. 這個原型對象包含兩個部分( constructor
和 __proto__
) 其中 constructor
指向函數(shù)自身
function Person(){ }
console.log(Person.prototype.constructor === Person) // true
通過構(gòu)造函數(shù)得到的實例對象中只有 __proto__
屬性, 所有的實例都指向自己構(gòu)造函數(shù)的原型.
p.__proto__ === Person.prototype // true
__proto__
屬性里面有構(gòu)造器 constructor
和 __proto__
constructor
指向原型所屬的構(gòu)造函數(shù)
__proto__
指向Object的原型
面向?qū)ο?- 基本特性
- 抽象性
-- 只有在具體的環(huán)境中對象才可以表示具體的事物
-- 而在程序設計中實際只考慮對象的目標數(shù)據(jù) - 封裝性
-- 將具體的操作步驟打包起來
-- 使用時無需關心具體的實現(xiàn)過程, 知道怎樣使用即可 - 繼承性
-- 繼承在OOP
中的表現(xiàn)就是擴展. 在原有的對象的基礎上, 添加一些新的東西得到新的對象, 這個新的對象就繼承自原有的對象. - 多態(tài)性
-- 調(diào)用同一個方法, 根據(jù)傳入的參數(shù)不同, 得到不同的結(jié)果
面向?qū)ο?- Error 對象
異常的概念
在代碼運行的過程中, 得到與預期不同的結(jié)果
處理異常
語法
try {
// 需要判斷的代碼
} catch (e) {
// 異常的處理
}
異常對象的傳遞
代碼出現(xiàn)異常, 那么異常后面的代碼不再執(zhí)行, 將錯誤傳遞給調(diào)用該函數(shù)的函數(shù), 直至傳到最頂層.
如果有 try - catch
那么出現(xiàn)異常后會執(zhí)行 catch
中異常處理的代碼
異常對象
在出現(xiàn)異常的時候, 異常出現(xiàn)的位置以及異常的類型, 內(nèi)容等數(shù)據(jù)都會被封裝起來, 以一個對象的形式傳遞給 catch
語句中的參數(shù) e
,用戶可以使用 throw 異常對象
拋出異常, 或者 new Error(e)
得到異常信息.
try {
// 需要判斷的代碼
} catch (e) {
console.log(new Error(e))
throw e
}
面向?qū)ο?- DOM對象
HTML中所有的節(jié)點都是對象
<body>
你好, 今天<i>天氣很好</i>.
<body>
其中:
"你好, 今天" 是一個對象
i 標簽也是一個對象
"添加很好" 也是一個對象
面向?qū)ο?- 繼承
原型繼承
對象 p
中沒有 sayHello
方法, 因為構(gòu)造函數(shù) Person
中什么都沒有
但是 p
連接到原型中了, 因此 p
就可以調(diào)用 sayHello
方法
這就是原型繼承, p
沒有, 但是 p
繼承自原型對象, 所以 p
有了
-
一般的方法
所有的方法寫在原型中
function Person ( name, age, gender ) { this.name = name this.age = age this.gender = gender } Person.prototype.sayHello = function () { console.log( '你好, 我是 ' + this.name ) }
-
替換原型
function Person ( name, age, gender ) { this.name = name this.age = age this.gender = gender } Person.prototype = { constructor: Person // 最好手動添加 constructor 的指向 sayHello: function () { }, walk:function () { } } var p = new Person() p.sayHello()
-
不推薦的寫法
function Person (name, age, gender) { this.name = name this.age = age this.gender = gender } Person.prototype.sayHello = function () { console.log( '你好, 我是 ' + this.name ) } function Teacher (name, age, gender){ Person.call(this, name, age, gender) } Teacher.prototype = Person.prototype // 不推薦 修改 Teacher.prototype 會修改所有繼承自 Person 的對象的原型方法
-
推薦的寫法
Teacher.prototype = new Person() // TODO: 執(zhí)行修改 Teacher.prototype 操作
-
Object.create()
方法Object.create(proto [, propertiesObject ]) 是ES5中提出的一種新的對象創(chuàng)建方式,第一個參數(shù)是要繼承的原型垢村,如果不是一個子函數(shù)割疾,可以傳一個null,第二個參數(shù)是對象的屬性描述符嘉栓,這個參數(shù)是可選的宏榕。
function Car (desc) { this.desc = desc this.color = "red" } Car.prototype = { getInfo: function() { return 'A ' + this.color + ' ' + this.desc + '.' } } //instantiate object using the constructor function var car = Object.create(Car.prototype) car.color = "blue" alert(car.getInfo()) // A blue undefined.
propertiesObject
參數(shù)的詳細解釋:(默認都為false)-
writable
: 是否可任意寫 -
configurable
:是否能夠刪除驰凛,是否能夠被修改 -
enumerable
:是否能用 for in 枚舉 -
value:值
訪問屬性 -
get()
: 訪問 -
set()
: 設置
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> </head> <body> <script type="text/javascript"> var obj = { a: function () { console.log(100) }, b: function () { console.log(200) }, c: function () { console.log(300) } } var newObj = {} newObj = Object.create(obj, { t1: { value: 'yupeng', writable: true }, bar: { configurable: false, get: function () { return bar }, set: function (value) { bar = value } } }) console.log(newObj.a()) // 100 console.log(newObj.t1) // yupeng newObj.t1 = 'yupeng1' console.log(newObj.t1) // yupeng1 newObj.bar = 201 console.log(newObj.bar) // 201 function Parent() { } var parent = new Parent() var child = Object.create(parent, { dataDescriptor: { value: "This property uses this string as its value.", writable: true, enumerable: true }, accessorDescriptor: { get: function () { return "I am returning: " + accessorDescriptor }, set: function (val) { accessorDescriptor = val }, configurable: true } }) child.accessorDescriptor = 'YUPENG' console.log(child.accessorDescriptor) // I am returning: YUPENG var Car2 = function () { this.name = 'aaaaaa' } //this is an empty object, like {} Car2.prototype = { getInfo: function () { return 'A ' + this.color + ' ' + this.desc + '.' } } var newCar = new Car2() var car2 = Object.create(newCar, { //value properties color: { writable: true, configurable: true, value: 'red' }, //concrete desc value rawDesc: { writable: true, configurable: true, value: 'Porsche boxter' }, // data properties (assigned using getters and setters) desc: { configurable: true, get: function () { return this.rawDesc.toUpperCase() }, set: function (value) { this.rawDesc = value.toLowerCase() } } }) car2.color = 'blue' console.log(car2.getInfo()) // A blue PORSCHE BOXTER. car2.desc = "XXXXXXXX" console.log(car2.getInfo()) // A blue XXXXXXXX. console.log(car2.name) // aaaaaa </script> </body> </html>
-
混合繼承
將原型鏈和借用構(gòu)造函數(shù)混合使用
function SuperType (name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
window.alert(this.name)
}
function SubType (name, age) {
SuperType.call(this, name)
this.age = age
}
// 繼承方法
SubType.prototype = new SuperType()
SubType.prototype.sayAge = function () {
window.alert(this.age)
}
var instance1 = new SubType('Nicholas', 29)
instance1.colors.push('black')
window.alert(instance1.colors) // red,blue,green,black
instance1.sayName() // Nicholas
instance1.sayAge() // 29
var instance2 = new SubType('Greg', 27)
window.alert(instance2.colors) // red,blue,green
instance2.sayName() // Greg
instance2.sayAge() // 27
函數(shù)
函數(shù)也是對象, 所有的函數(shù)都是 Function
的實例.
所有函數(shù)的 __proto__
都指向 Function.prototype
, 包括 Function
, 即:
function fn () {}
fn.__proto__ === Function.prototype // true
Function.__proto__ === Function.prototypr /// true
Function.prototype.__proto__ === Object.prototype
函數(shù)的參數(shù)
length 屬性
在javascript中, 創(chuàng)建了一個函數(shù)就是創(chuàng)建了一個對象. 函數(shù)與一般數(shù)據(jù)一樣使用 -- 賦值, 調(diào)用
函數(shù)作為對象有一個 length 屬性, 改屬性用于描述創(chuàng)建函數(shù)時參數(shù)的個數(shù).
arguments 對象
在調(diào)用函數(shù)的時候, 會給函數(shù)參數(shù), 但在函數(shù)定義的時候, 有時候不確定要傳入多少參數(shù), 所有在調(diào)用時傳入的參數(shù)都會被 arguments 獲取到, 也就是說 -- arguments 中存儲的是參數(shù)的集合
function a () {
console.log(arguments.length)
}
a() // 0
a(1) // 1
a(1,2,3,'4') // 4
如何判斷調(diào)用時的參數(shù)個數(shù)與函數(shù)定義時的參數(shù)個數(shù)一樣?
函數(shù)名.length === arguments.length
將偽數(shù)組轉(zhuǎn)為數(shù)組
-
聲明一個空數(shù)組,通過遍歷偽數(shù)組把它們重新添加到新的數(shù)組中
var list = document.querySelectorAll('li') var res = [] list.forEach(function(item, i){ res.push(item) })
偽數(shù)組
NodeList
沒有forEach
方法, 但是通過document.querySelectorAll()
返回的偽數(shù)組有這個方法 -
使用數(shù)組的slice()方法 它返回的是數(shù)組担扑,使用call或者apply指向偽數(shù)組
var res = Array.prototype.slice.call(list)
// 模擬 slice 的實現(xiàn) Array.prototype.mySlice = function () { // 參數(shù)為 0 個, 則從 0 截到最后 // 參數(shù)為 1 個, 則從 第一個參數(shù)開始的索引 截到最后 // 參數(shù)為 2 個, 則從 第一個參數(shù)開始的索引 截到第二個參數(shù)截止的索引 var start = 0 var end = this.length arguments.length === 1 && (start = arguments[0]) arguments.length === 2 && (start = arguments[0], end = arguments[1]) var tmp = [] for(var i = start; i < end; i++) { tmp.push(this[i]) } return tmp }
-
使用原型繼承
list.__proto__ = Array.prototype
-
ES6中數(shù)組的新方法 from()
var res = Array.from(list)
-
jq的makeArray()恰响,toArray()方法 它們也可以實現(xiàn)偽數(shù)組轉(zhuǎn)數(shù)組的功能,但是實現(xiàn)的原理并不太一樣
// core_deletedIds = [] // core_slice = core_deletedIds.slice // core_push = core_deletedIds.push // makeArray: 使用了數(shù)組的slice方法 toArray: function () { return core_slice.call(this) } // makeArray:使用了push方法 makeArray: function (arr, result) { var ret = result || [] if (arr != null) { if (isArraylike(object(arr))) { jQuery.merge(ret, typeof arr === 'string' ? [arr] : arr ) } else { core_push.call(ret, arr) } } return ret }
callee 屬性
-
callee
返回正在執(zhí)行的函數(shù)本身的引用, 他是 arguments 的一個屬性arguments.callee === fn // true
-
callee
有一個 length 屬性, 可以獲得形參的個數(shù). 因此可以用來比較形參與實參個數(shù)是否一致.arguments.length === arguments.callee.length
-
可以用來遞歸匿名函數(shù)
var sum = function(n){ if (n <= 1) return 1 else return n + arguments.callee(n - 1) }
caller
caller 返回一個函數(shù)的引用, 這個函數(shù)調(diào)用了當前的函數(shù)
使用這個屬性要注意:
- 這個屬性只有在函數(shù)執(zhí)行時才有作用
- 如果在 javascript 程序中, 函數(shù)是頂層調(diào)用的, 返回 null
var a = function() {
alert(a.caller)
}
var b = function() {
a()
}
b()
代碼中, b
調(diào)用了 a
, 所以 a.caller
返回的是 b
的引用, 結(jié)果如下:
var b = function() {
a()
}
如果直接調(diào)用 a()
, 輸出結(jié)果為: null
函數(shù)的預解析
在javascript預解析的時候, 同名的函數(shù)與變量以函數(shù)為準.
已經(jīng)預解析過得函數(shù), 在代碼執(zhí)行過程中會略過
console.log(typeof fn) // function
// 在執(zhí)行階段涌献,這里對 fn 重新賦值為 123 了
var fn = 123
// 函數(shù)聲明最大的特性就是:具有函數(shù)提升
function fn() {
console.log('hello')
}
console.log(typeof fn) // function number
函數(shù)的表達式
函數(shù)的表達式類似于變量賦值, 只有變量提升, 沒有函數(shù)提升, 必須先聲明, 再使用
fn() // 報錯 VM277:1 Uncaught ReferenceError: fn is not defined
console.log(typeof fn) // undefined 函數(shù)表達式只有變量提升
var fn = function () {
console.log('hello')
}
fn() // hello
new Function
執(zhí)行效率低, 很少用.
var add = new Function('x', 'y', 'return x + y')
var ret = add(10, 30)
console.log(ret)
作用域
塊級作用域
所謂的塊, 就是代碼的邏輯機構(gòu), 其中使用 {}
包含的就是語句塊. 例如:
if (true) {
// 語句1
// 語句2
// 語句3
}
這里的 {}
就是語句塊
塊級作用域是指: 從變量定義開始, 到變量所在的語句塊結(jié)束, 在這樣一個范圍內(nèi)可以被使用.
在塊級作用域內(nèi) 本塊級的變量可以訪問父級塊內(nèi)的變量, 反之不行.
如果子塊和父塊變量重名, 那么會在定義該變量時隱藏父塊中的變量.在子塊中定義的變量的改變, 不會影響父塊中的變量, 離開子塊后, 父塊中的變量可以繼續(xù)使用.
但是, 在javascript中沒有塊級作用域, 所有聲明的變量的作用據(jù)就是當前函數(shù)范圍內(nèi), 或者全局.
詞法作用域
詞法作用域, 指的是變量的訪問規(guī)則按照詞法定義的規(guī)則進行使用, 也就是只有函數(shù)才可以限定作用域.
訪問變量從當前作用域開始往上進行查找 -- 不是代碼的書寫順序
問題
var condition = true
if (condition) {
// var foo = 'bar'
function fn() {
console.log('hello')
}
} else {
function fn() {
console.log('world')
}
}
fn()
// 因為在javascript中, 沒有塊級作用域, 所以 {} 內(nèi)聲明的都是全局
// 低版本(IE10以下)瀏覽器會先進性函數(shù)提升 -- 執(zhí)行結(jié)果是 world
// 對于上面的方式胚宦,建議使用函數(shù)表達式來處理就可以了
// 因為函數(shù)表達式?jīng)]有函數(shù)提升
var condition = true
var fn
if (condition) {
// var foo = 'bar'
fn = function () {
console.log('hello')
}
} else {
fn = function () {
console.log('world')
}
}
fn()
變量的訪問規(guī)則
函數(shù)的作用域鏈以定義時所處的作用域為準, 而不是調(diào)用時.
javascript的執(zhí)行原理
作用域鏈
this
this
的指向, 在調(diào)用的時候才能確定.
調(diào)用方式 | 非嚴格模式 | 備注 |
---|---|---|
普通函數(shù)調(diào)用 | window | 嚴格模式下是 undefined |
構(gòu)造函數(shù)調(diào)用 | 實例對象 | 原型方法中 this 也是實例對象 |
對象方法調(diào)用 | 該方法所屬對象 | 緊挨著的對象 |
事件綁定方法 | 綁定事件對象 | |
定時器函數(shù) | window |
普通函數(shù)調(diào)用
function a(){
var user = "追夢子"
console.log(this.user) //undefined
console.log(this) //Window
}
a()
a()
相當于 window.a()
, 在非嚴格下指向 window
可以理解為 window
對象調(diào)用
對象方法調(diào)用
var o = {
user:"追夢子",
fn:function(){
console.log(this.user) //追夢子
}
}
o.fn()
this
的指向是在調(diào)用的時候確定的, 這里的 this
指向?qū)ο?o
var o = {
user:"追夢子",
fn:function(){
console.log(this.user) //追夢子
}
}
window.o.fn()
這里的 this
指向?qū)ο?o
, 因為 o
相當于全局對象 window
的一個屬性.
如果一個函數(shù)中有this,這個函數(shù)中包含多個對象燕垃,盡管這個函數(shù)是被最外層的對象所調(diào)用枢劝,this指向的也只是它上一級的對象, 最終要看是哪個對象"點"出來的
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a) //undefined
console.log(this) //window
}
}
}
var j = o.b.fn
j()
這里的 this
指向并不是 b
對象, 雖然函數(shù) fn
是被對象 b
引用, 但是在將 fn
賦值給 j
的時候并沒有執(zhí)行, 所以在 j
執(zhí)行的時候, this
的最終指向是 window
構(gòu)造函數(shù)調(diào)用
function Fn(){
this.user = "追夢子"
}
var a = new Fn()
console.log(a.user) //追夢子
構(gòu)造函數(shù)內(nèi)的 this
指向構(gòu)造方法的實例 -- a
創(chuàng)建實例時 new
改變了 this
的指向
使用 new 操作符. 這種方式調(diào)用構(gòu)造函數(shù)會經(jīng)歷以下4個階段:
1.創(chuàng)建一個新對象
2.將構(gòu)造函數(shù)的作用域賦給新對象 -- this 指向新對象
3.執(zhí)行構(gòu)造函數(shù)中的代碼
4.返回新對象
當構(gòu)造函數(shù)內(nèi)有 return
時:
return 的值是值類型 不影響 this 指向
-
return 的值是引用類型:
function fn() { this.user = '追夢子' return {} // return 空對象 } var a = new fn console.log(a.user) //undefined
function fn() { this.user = '追夢子' return function(){} // 函數(shù) } var a = new fn console.log(a.user) //undefined
如果返回值是一個對象,那么this指向的就是那個返回的對象卜壕,如果返回值不是一個對象那么this還是指向函數(shù)的實例
事件綁定方法
document.getElementById('btn').onclick = function(){
console.log('btn 被點擊了')
}
事件處理函數(shù)不是用戶來調(diào)用的您旁,而是由系統(tǒng)來調(diào)用
事件處理函數(shù)內(nèi)部的 this 指向 DOM 對象
定時器函數(shù)
setTimeout(function(){
console.log('時間 +1s')
}, 1000)
當時間到達,系統(tǒng)會去幫你調(diào)用這個函數(shù), 所以轴捎,這個定時器處理函數(shù)中的 this 指向的是 window
demo
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)}
// 寫出下面的結(jié)果
// 1 Foo.getName()
// 2 getName()
// 3 Foo().getName()
// 4 new Foo.getName()
// 5 new Foo().getName()
// 6 new new Foo().getName()
點運算符鹤盒、new運算符、函數(shù)執(zhí)行這三者之間的優(yōu)先級:
new A.B(); 的邏輯是這樣的:new A.B ();
點運算符優(yōu)先于new運算符侦副,看起來似乎僅僅如此侦锯。
new A().B(); 的邏輯卻是這樣的:(new A()).B(); 而不是 new (A().B) ();
區(qū)別在于A后面多了一對小括號,這個影響到了優(yōu)先級順序秦驯。
//這兩種寫法是等價的
var d = new A
var d = new A()
//但是下面這兩種是不同的尺碰,不能混淆了:
var d = new A.B(); //new A.B
var d = new A().B(); //new A().B
call & apply
apply
和 call
的方法作用是一模一樣的,都是用來改變方法的 this
關鍵字译隘,并且把方法執(zhí)行亲桥;而且在嚴格模式下和非嚴格模式,對于第一個參數(shù)時null/undefined固耘,這樣的情況下题篷,也是一樣的。
// call在給fn傳遞參數(shù)的時候玻驻,是一個一個傳遞值的 call在給fn傳遞參數(shù)的時候悼凑,是一個一個傳遞值的
fn.call(obj, 100, 200)
// 而apply不是一個個的傳遞,而是把要給fn傳遞的參數(shù)值統(tǒng)一的放在一個數(shù)組中進行操作
// 但是也相當于一個一個的給fn的參數(shù)賦值
fn.apply(obj, [100, 200])
var a = {
user:"追夢子",
fn:function(){
console.log(this.user) //追夢子
}
}
var b = a.fn
b.call(a)
通過在call方法璧瞬,給第一個參數(shù)添加要把b添加到哪個環(huán)境中户辫,簡單來說,this就會指向那個對象嗤锉。
call方法除了第一個參數(shù)以外還可以添加多個參數(shù)渔欢,如下:
var a = {
user:"追夢子",
fn:function(e,ee){
console.log(this.user) //追夢子
console.log(e+ee) //3
}
}
var b = a.fn
b.call(a,1,2)
注意如果call和apply的第一個參數(shù)寫的是null,那么this指向的是window對象
var a = {
user:"追夢子",
fn:function(){
console.log(this) //Window {external: Object, chrome: Object, document: document, a: Object, speechSynthesis: SpeechSynthesis…}
}
}
var b = a.fn
b.apply(null)
var keith = {
rascal: 123
}
var rascal = 456
function a() {
console.log(this.rascal)
}
a() //456
a.call() //456
a.call(null) //456
a.call(undefined) //456
a.call(this) //456
a.call(keith) //123
call
和 apply
可以用來借用其他函數(shù)的方法
var myMath = {
max: function () {
var max = arguments[0]
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] > max) {
max = arguments[i]
}
}
return max
}
}
var arr = [32, 1, 32, 13, 2, 4321, 13, 21, 3]
// 第一個參數(shù)用來指定內(nèi)部 this 的指向
// apply 會把傳遞的數(shù)組或偽數(shù)組展開, 一個一個傳遞到方法內(nèi)部
var max = myMath.max.apply(null, arr)
console.log(max)
bind
call
, apply
, 和 bind
的區(qū)別:
-
call
,apply
, 和bind
都可以改變很熟內(nèi)部的指向 -
call
,函數(shù)apply
在改變this
指向的同時調(diào)用函數(shù)-
call
通過,
作為分隔進行傳參 -
apply
通過傳遞一個數(shù)組或者偽數(shù)組傳遞參數(shù)
-
-
bind
改變函數(shù)內(nèi)部this
的指向, 但是他不調(diào)用, 而是返回了一個指定了this
環(huán)境的新函數(shù)
綁定函數(shù)
bind() 最簡單的用法是創(chuàng)建一個函數(shù), 使這個函數(shù)不論怎么調(diào)用都有同樣的 this
值.
常見的錯誤:
var altwrite = document.write
altwrite("hello")
//1.以上代碼有什么問題
//2.正確操作是怎樣的
//3.bind()方法怎么實現(xiàn)
altwrite
改變了 this
的指向 global
或 window
對象, 導致執(zhí)行時提示非法調(diào)用異常 Uncaught TypeError: Illegal invocation
, 正確的使用方法:
altwrite.bind(document)("hello")
或者
altwrite.call(document, "hello")
常見的錯誤就像上面的例子一樣, 講方法從對象中拿出來, 然后調(diào)用, 并且希望 this 指向原來的對象. 如果不做特殊處理, 一般對象會丟失. 使用 bind() 方法可以很漂亮的解決這個問題.
this.number = 9
var module = {
num: 81,
getNum: function () {
return this.num
}
}
module.getNum() // 81
var getNum = module.getNum
getNum() // 9
var boundGetNum = getNum.bind(module)
boundGetNum() // 81
偏函數(shù)
與 setTimeout 一起使用
一般情況下, setTimeout 的 this 會指向 global 或 window 對象. 當時用類的方法需要 this 指向?qū)嵗? 就可以用 bind() 將 this 綁定到回調(diào)函數(shù)來管理實例.
function Bloomer () {
this.petalCount = Math.ceil(Math.random() * 12) + 1
}
Bloomer.prototype.bloom = function () {
window.setTimeout(this.declare.bind(this), 1000)
}
Bloomer.prototype.declare = function () {
console.log(`我有 ${this.petalCount} 朵花瓣`)
}
綁定函數(shù)作為構(gòu)造函數(shù)
function Point(x, y) {
this.x = x
this.y = y
}
Point.prototype.toString = function() {
return this.x + ',' + this.y
}
var p = new Point(1, 2)
p.toString() // '1,2'
var emptyObj = {}
var YAxisPoint = Point.bind(emptyObj, 0/*x*/)
// 實現(xiàn)中的例子不支持,
// 原生bind支持:
var YAxisPoint = Point.bind(null, 0/*x*/)
var axisPoint = new YAxisPoint(5)
axisPoint.toString() // '0,5'
axisPoint instanceof Point // true
axisPoint instanceof YAxisPoint // true
new Point(17, 42) instanceof YAxisPoint // true
上面例子中Point和YAxisPoint共享原型瘟忱,因此使用instanceof運算符判斷時為true奥额。
捷徑
bind()也可以為需要特定this值的函數(shù)創(chuàng)造捷徑苫幢。
例如要講一個類數(shù)組對象轉(zhuǎn)換為真正的數(shù)組, 可能會寫:
var slice = Array.prototype.slice
// ...
slice.call(arguments)
使用 bind() 的話:
var unboundSlice = Array.prototype.slice
var slice = Function.prototype.call.bind(unboundSlice)
// ...
slice(arguments)
參數(shù)
bind() 可以傳參數(shù), 但是不調(diào)用
bind() 以后得到的新函數(shù)也可以傳參, 但在實際使用的時候, 會把在 bind() 時傳遞的參數(shù)和調(diào)用新函數(shù)傳的參數(shù)進行合并, 然后作為函數(shù)的參數(shù)
高階函數(shù)
高階函數(shù)就是指:
- 函數(shù)可以當做參數(shù)進行傳遞
- 函數(shù)可以當做返回值進行返回
作為參數(shù)傳遞
ajax
// callback為待傳入的回調(diào)函數(shù)
var getUserInfo = function(userId, callback) {
$.ajax("http://xxx.com/getUserInfo?" + userId, function(data) {
if (typeof callback === "function") {
callback(data)
}
})
}
getUserInfo(13157, function(data) {
alert (data.userName)
})
Array.prototype.sort
Array.prototype.sort
接收一個函數(shù)作為參數(shù), 這個函數(shù)封裝了數(shù)組元素的排序規(guī)則.
//從小到大排列
[1, 4, 3].sort(function(a, b) {
return a - b
});
// 輸出: [1, 3, 4]
//從大到小排列
[1, 4, 3].sort(function(a, b) {
return b - a
});
// 輸出: [4, 3, 1]
作為返回值返回
判斷數(shù)據(jù)類型
// 以前的代碼
function isArray (obj) {
return Object.prototype.toString.call(obj) === '[object Array]'
}
function isObject (obj) {
return Object.prototype.toString.call(obj) === '[object Object]'
}
// ...
// 函數(shù)作為返回值的寫法
var isArray = generateCheckTypeFn('[object Array]')
var isObject = generateCheckTypeFn('[object Object]')
var isString = generateCheckTypeFn('[object String]')
var isNumber = generateCheckTypeFn('[object Number]')
function generateCheckTypeFn (type) {
return function (obj) {
return Object.propotype.toString.call(obj) === type
}
}
isArray([]) // true
isNumber(NaN) // true
閉包
閉包就是能夠讀取其它函數(shù)內(nèi)部變量的函數(shù)
由于在 javascript 中, 只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量, incident可以把閉包簡單理解為 "定義在函數(shù)內(nèi)部的函數(shù)"
在本質(zhì)上, 閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁.
如果一個函數(shù)內(nèi)部返回了一個函數(shù)或者多個函數(shù), 而返回的函數(shù)中具有對自己的外層作用域中的成員的讀取或者修改, 那么這個函數(shù)就成為閉包函數(shù).
如何訪問閉包中的數(shù)據(jù):
- 利用返回值
- 利用一個對象返回函數(shù)
- 返回對象
function fn () {
var foo = 'bar'
var getFoo = function () {
return foo
}
var setFoo = function (val) {
foo = val
}
return {
getFoo: getFoo,
setFoo: setFoo
}
}
var obj = fn()
console.log(obj.getFoo())
示例代碼:
var arr = [10, 20, 30]
for(var i = 0; i < arr.length; i++) {
arr[i] = (function (i) {
return function () {
console.log(i)
}
})(i)
}
arr.forEach(function (item, index) {
item()
})
示例代碼:
console.log(111)
for(var i = 0; i < 3; i++) {
// 定時器永遠在普通代碼的最后執(zhí)行
// 哪怕時間是 0
setTimeout((function (i) {
return function () {
console.log(i)
}
})(i),0)
}
console.log(222)
沙箱模式
利用匿名函數(shù)自執(zhí)行保護內(nèi)部成員不被外部修改或者訪問
;(function () {
var age = 3
function F () {
}
F.prototype.getAge = function () {
return age
}
F.prototype.setAge = function (val) {
if (val < 18) {
return console.log('age 不能小于18歲')
}
age = val
}
window.F = F
})()
var f = new F()
console.log(f.getAge())
思考題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()())
遞歸
深拷貝
// 判斷類型
function getType(type) {
return function (obj) {
return Object.prototype.toString.call(obj) === `[object ${type}]`
}
}
function isArray(obj) {
return getType('Array')(obj)
}
function isObject(obj) {
return getType('Object')(obj)
}
// 循環(huán)拷貝
function deepCopy(target, source) {
for (var key in source) {
if (source.hasOwnProperty(key)) {
var element = source[key];
if (isArray(element)) {
target[key] = []
deepCopy(target[key], element)
} else if (isObject(element)) {
target[key] = {}
deepCopy(target[key], element)
} else {
target[key] = element
}
}
}
}
階乘
function factorial(number) {
if (number < 0) {
return
} else if (number < 2) {
return 1
} else {
return number * factorial(number - 1)
}
}
以下代碼會導致錯誤
var fact = factorial
fact = null
fact(5)
解決辦法: 使用 callee
function factorial(number) {
if (number < 0) {
return
} else if (number < 2) {
return 1
} else {
return number * arguments.callee(number - 1)
}
}
var fact = factorial
factorial = null
console.log(fact)
fact(5)
`callee` 返回正在執(zhí)行的函數(shù)本身的引用, `arguments.callee === fn // true`