關注前端小謳,閱讀更多原創技術文章
相關代碼 →javascript
function createPerson(name, age, job) { var o = new Object() o.name = name o.age = age o.job = job o.sayName = function () { console.log(this.name) } return o } var person1 = createPerson('Nicholas', 29, 'Engineer') var person2 = createPerson('Greg', 27, 'Doctor') console.log(person1) console.log(person2)
構造函數模式 vs 工廠模式前端
function Person(name, age, job) { this.name = name this.age = age this.job = job this.sayName = function () { console.log(this.name) } } var person1 = new Person('Nicholas', 29, 'Software Engineer') var person2 = new Person('Greg', 27, 'Doctor')
new
操做符構造函數 new 一個對象後:java
[[Prototype]]
特性被賦值爲構造函數的prototype
屬性(共同指向原型)constructor
屬性指向構造函數console.log(person1.constructor === Person) // true,constructor 屬性指向構造函數 console.log(person2.constructor === Person) // true,constructor 屬性指向構造函數 console.log(person1 instanceof Object) // true,person1是Object的實例 console.log(person1 instanceof Person) // true,person1是Person的實例 console.log(person2 instanceof Object) // true,person2是Object的實例 console.log(person2 instanceof Person) // true,person2是Person的實例
var PersonExpression = function () { // 構造函數的函數表達式 this.name = 'Jake' this.sayName = function () { console.log(this.name) } } var personNoBrackets = new PersonExpression() // 實例化不傳參數,可不加括號
new
操做符調用的就是構造函數,不使用的是普通函數this
指向 Global
對象(瀏覽器中指向 window
對象)var person3 = new Person('Nicholas', 29, 'Software Engineer') // 用構造函數建立對象 person3.sayName() // 'Nicholas' Person('Greg', 27, 'Doctor') // 'Greg',不使用new操做符,直接調用 global.sayName() // 直接調用函數,this指向Global對象(瀏覽器中指向window對象) var o = new Object() // 新對象o var p = new Object() // 新對象p Person.call(o, 'Kristen', 25, 'Nurse') // 將對象o指定爲Person()內部的this值,call()分別傳入每一個參數 Person.apply(p, ['Kristen', 25, 'Nurse']) // 將對象o指定爲Person()內部的this值,apply()傳入參數數組 o.sayName() // 'Kristen' p.sayName() // 'Kristen'
Function
實例沒有必要function Person2(name, age, job) { this.name = name this.age = age this.job = job this.sayName = new Function(console.log(this.name)) // 與聲明函數邏輯等價,每建立一個對象就要建立一個Function實例 } console.log(person1.sayName === person2.sayName) // false,新對象的2個方法的做用域鏈和標識符解析不一樣
function Person3(name, age, job) { this.name = name this.age = age this.job = job this.sayName = sayName } function sayName() { console.log(this.name) // 將sayName設置成全局函數 } var person4 = new Person('Nicholas', 29, 'Software Engineer')
prototype
屬性,該屬性是一個指針,指向函數(經過調用構造函數而建立的那個對象實例的)原型對象function PersonPrototype() {} PersonPrototype.prototype.name = 'Nicholas' // 爲PersonPrototype的原型對象添加屬性 PersonPrototype.prototype.age = 29 // 爲PersonPrototype的原型對象添加屬性 PersonPrototype.prototype.job = 'Software Engineer' // 爲PersonPrototype的原型對象添加屬性 PersonPrototype.prototype.sayName = function () { // 爲PersonPrototype的原型對象添加方法 console.log(this.name) } var person5 = new PersonPrototype() var person6 = new PersonPrototype() person5.sayName() // 'Nicholas' person6.sayName() // 'Nicholas' console.log(person5.sayName === person6.sayName) // true,原型對象上建立的屬性和方法,由全部實例共享
prototype
屬性指向原型對象,(默認狀況下)原型對象自動得到constructor
屬性,指回與之關聯的構造函數console.log(PersonPrototype.prototype.constructor) // PersonPrototype構造函數,原型對象的constructor屬性指向與之關聯的構造函數 console.log(PersonPrototype === PersonPrototype.prototype.constructor) // true,都指向構造函數
__proto__
屬性實現[[Prototype]]的功能console.log(person5.__proto__) // 原型對象,PersonPrototype {name: 'Nicholas',age: 29,job: 'Software Engineer',sayName: [Function] } console.log(person5.__proto__ === PersonPrototype.prototype) // true,都指向原型對象 console.log(person5.__proto__.constructor) // Function: PersonPrototype構造函數 console.log(person5.__proto__ === person6.__proto__) // true,共享同一個原型對象
instanceof
檢查實例的原型鏈中,是否包含指定構造函數的原型console.log(person5 instanceof PersonPrototype) // true,person5是PersonPrototype的實例 console.log(person5 instanceof Object) // true,person5是Object的實例 console.log(PersonPrototype.prototype instanceof Object) // true,全部實例對象和原型對象都是Object的實例
isPrototypeOf()
方法,檢測實例中是否有指向原型對象的指針console.log(PersonPrototype.prototype.isPrototypeOf(person5)) // true,person5包含指向PersonPrototype的原型對象的指針 console.log(PersonPrototype.prototype.isPrototypeOf(person1)) // false,person1不包含指向PersonPrototype的原型對象的指針
Object.getPrototypeOf()
方法(參數通常爲實例),返回參數的[[Prototype]]
的值(通常爲原型對象)console.log(Object.getPrototypeOf(person5)) // 原型對象 console.log(Object.getPrototypeOf(person5) === person5.__proto__) // true,都指向原型對象 console.log(Object.getPrototypeOf(person5) === PersonPrototype.prototype) // true,都指向原型對象 console.log(Object.getPrototypeOf(person5).name) // 'Nicholas' console.log(Object.getPrototypeOf(person5).constructor) // Function: PersonPrototype構造函數
Object.setPrototypeOf()
方法,向實例(參數一)的[[Prototype]]
寫入一個新值(參數二),從而重寫一個對象的原型繼承關係var biped = { numLegs: 2, } var person = { name: 'Matt', } Object.setPrototypeOf(person, biped) console.log(person.name) // 'Matt' console.log(person.numLegs) // 2 console.log(person.__proto__) // { numLegs: 2 },person的[[Prototype]]指針指向biped
Object.setPrototypeOf()
可能嚴重影響代碼性能,可以使用Object.create()
建立一個新對象,同時爲其指定原型(參數)var biped2 = { numLegs: 3, } var person = Object.create(biped2) console.log(person.numLegs) // 3 console.log(person.__proto__) // { numLegs: 3 },person的[[Prototype]]指針指向biped2
代碼讀取對象屬性的搜索過程:git
var person7 = new PersonPrototype() person7.name = 'Greg' console.log(person7.name) // 'Greg',來自實例 console.log(person5.name) // 'Nicholas',來自原型
delete person7.name console.log(person7.name) // 'Nicholas',來自原型
hasOwnProperty()
方法,檢測屬性是否存在於實例中(存在返回 true),參數爲要檢測的屬性var person8 = new PersonPrototype() var person9 = new PersonPrototype() console.log(person8.hasOwnProperty('name')) // false,name不存在在person8的實例中 person8.name = 'Simon' console.log(person8.name) // 'Simon',來自實例 console.log(person8.hasOwnProperty('name')) // true,name存在在person8的實例中 console.log(person9.name) // 'Nicholas',來自原型 console.log(person9.hasOwnProperty('name')) // false,name不存在在person8的實例中 delete person8.name console.log(person8.name) // 'Nicholas',來自原型 console.log(person8.hasOwnProperty('name')) // false,person8實例的name屬性已被刪除
Object.getOwnPropertyDescriptor()
,獲取原型屬性的描述符console.log(Object.getOwnPropertyDescriptor(person8, 'name')) // undefined,person8實例上沒有name屬性 console.log(Object.getOwnPropertyDescriptor(person8.__proto__, 'name')) // {value: 'Nicholas',writable: true,enumerable: true,configurable: true},原型對象的name屬性描述符
in
操做符:對象可以訪問指定屬性則返回 true,不管屬性在實例中仍是原型中function PersonIn() {} PersonIn.prototype.name = 'Nicholas' PersonIn.prototype.age = 29 PersonIn.prototype.job = 'Software Engineer' PersonIn.prototype.sayName = function () { console.log(this.name) } var person9 = new PersonIn() var person10 = new PersonIn() console.log(person9.hasOwnProperty('name')) // false,實例person9中不含name屬性 console.log('name' in person9) // true,經過person9能夠訪問到name屬性 person9.name = 'Greg' console.log(person9.name); // 'Greg',來自實例 console.log(person9.hasOwnProperty('name')) // true,實例person9中包含name屬性 console.log('name' in person9) // true,經過person9能夠訪問到name屬性 console.log(person10.name); // 'Nicholas',來自原型 console.log(person10.hasOwnProperty('name')) // false,實例person10中不含name屬性 console.log('name' in person10) // true,經過person10能夠訪問到name屬性 delete person9 'name' console.log(person9.name); // 'Nicholas',來自原型 console.log(person9.hasOwnProperty('name')) // false,實例person9中不含name屬性 console.log('name' in person9) // true,經過person9能夠訪問到name屬性
hasOwnProperty()
和in
操做符,判斷屬性存在於實例仍是原型function hasPrototypeProperty(object, name) { return !object.hasOwnProperty(name) && name in object // 不存在於實例 && 能訪問到 → 存在於原型 } var person11 = new PersonIn() console.log(hasPrototypeProperty(person11, 'name')) // true,!false && true person11.name = 'Greg' console.log(hasPrototypeProperty(person11, 'name')) // false,!true && true
for-in
循環:返回對象全部可以訪問的、可枚舉的屬性(不管來自實例仍是原型),屏蔽了不可枚舉([[Enumerable]]爲 false)的屬性(如:原型的 constructor、構造函數的 prototype)for (var attr in person11) { console.log(`${attr}:${person11[attr]}`) /* name:Greg age:29 job:Software Engineer sayName:function () { console.log(this.name) } */ }
Object.keys()
方法返回對象(自身)可枚舉的屬性的數組,參數爲該對象var keys = Object.keys(PersonIn.prototype) // 原型對象的全部可枚舉屬性 console.log(keys) // [ 'name', 'age', 'job', 'sayName' ] var person12 = new PersonIn() person12.name = 'Bob' person12.age = 31 var p12keys = Object.keys(person12) // person12的全部可枚舉屬性 console.log(p12keys) // [ 'name', 'age' ]
Object.getOwnPropertyNames()
返回對象(自身)全部屬性(不管是否可枚舉)的數組,參數爲該對象var keys = Object.getOwnPropertyNames(PersonIn.prototype) // 原型對象的全部屬性,包含不可枚舉 console.log(keys) // [ 'constructor', 'name', 'age', 'job', 'sayName' ],原型對象都包含constructor屬性,指向構造函數 var p12keys = Object.getOwnPropertyNames(person12) // person12的全部屬性,包含不可枚舉 console.log(p12keys) // [ 'name', 'age' ] console.log(Object.getOwnPropertyNames(PersonIn)) // [ 'length', 'name', 'arguments', 'caller', 'prototype' ]
Object.getOwnPropertySymbols()
方法,返回對象(自身)全部符號鍵屬性(不管是否可枚舉)的數組,參數爲該對象var k1 = Symbol('k1') var k2 = Symbol('k2') var o = { [k1]: 'k1', // 符號做爲屬性,需使用「計算屬性」語法,即[屬性名] [k2]: 'k2', } console.log(Object.getOwnPropertySymbols(o)) // [ Symbol(k1), Symbol(k2) ]
for-in
循環和Object.keys()
在屬性枚舉順序是不肯定的,取決於瀏覽器的 JS 引擎Object.getOwnPropertyNames()
、Object.getOwnPropertySymbols()
和Object.assign()
在屬性枚舉順序是肯定的:github
var k1 = Symbol('k1') var k2 = Symbol('k2') var o = { 1: 1, first: 'first', [k2]: 'sym2', third: 'third', 0: 0, } o[k1] = 'sym1' o[3] = 3 o.second = 'second' o[2] = 2 console.log(Object.getOwnPropertyNames(o)) // [ '0', '1', '2', '3', 'first', 'third', 'second' ] console.log(Object.getOwnPropertySymbols(o)) // [ Symbol(k2), Symbol(k1) ]
Object.values()
和Object.entries()
方法,接收參數對象,分別返回對象值和對象鍵/值對數組var o = { foo: 'bar', baz: 1, qux: {}, } console.log(Object.values(o)) // [ 'bar', 1, {} ],迭代值 console.log(Object.entries(o)) // [ [ 'foo', 'bar' ], [ 'baz', 1 ], [ 'qux', {} ] ],迭代鍵值對
var o = { qux: {}, } console.log(Object.values(o)) // [ {} ] console.log(Object.entries(o)) // [ [ 'qux', {} ] ] console.log(Object.values(o)[0] === o.qux) // true,淺複製,複製對象的引用 console.log(Object.entries(o)[0][1] === o.qux) // true,淺複製,複製對象的引用
var sym = Symbol() var o = { [sym]: 'foo', // 符號屬性 } console.log(Object.values(o)) // [],符號屬性被忽略 console.log(Object.entries(o)) // [],符號屬性被忽略
function PersonLiteral() {} PersonLiteral.prototype = { name: 'Nicholas', age: 29, job: 'Software Engineer', sayName: function () { console.log(this.name) }, }
prototype
屬性設置爲一個以對象字面量形式建立新對象,其constructor
屬性再也不指向原構造函數,而是新對象的constructor
屬性,即Object 構造函數var friend = new PersonLiteral() console.log(friend instanceof Object) // true,friend是Object的實例 console.log(friend instanceof PersonLiteral) // true,friend是PersonLiteral的實例 console.log(friend.constructor === PersonLiteral) // false,constructor屬性變成了新對象——即對象字面量的constructor console.log(friend.constructor === Object) // true,新對象的constructor指向Object構造函數
constructor
屬性,讓其指向原構造函數constructor
屬性屬於直接在對象上定義的屬性,會致使constructor
屬性的[[Enumerable]]爲 true,能夠被枚舉出來function PersonLiteral2() {} PersonLiteral2.prototype = { constructor: PersonLiteral2, // 直接在對象上定義constructor,指向原構造函數 name: 'Nicholas', age: 29, job: 'Software Engineer', sayName: function () { console.log(this.name) }, } var friend2 = new PersonLiteral2() console.log(friend2.constructor === PersonLiteral2) // true,constructor再次指向原構造函數 console.log(friend2.constructor === Object) // false console.log(Object.keys(PersonLiteral2.prototype)) // [ 'constructor', 'name', 'age', 'job', 'sayName' ],由於constructor是「直接在對象上定義的屬性」,可被枚舉出來
constructor
屬性,而用Object.defineProperty()
修改對象字面量中constructor
屬性的特性,以兼容 JavaScript 引擎Object.defineProperty(PersonLiteral2.prototype, 'constructor', { enumerable: false, value: PersonLiteral2, }) console.log(Object.keys(PersonLiteral2.prototype)) // [ 'name', 'age', 'job', 'sayName' ],constructor的enumerable已被設置爲false
function Person4() {} var friend3 = new Person4() // 先建立實例 Person4.prototype.sayHi = function () { // 後修改原型對象 console.log('Hi') } friend3.sayHi() // 'Hi',實例受影響,實例指向原型
Person4.prototype = { // 重寫原型 constructor: Person4, name: 'Nicholas', age: 29, job: 'Software Engineer', sayName: function () { console.log(this.name) }, } console.log(friend3.__proto__) // Person4 { sayHi: [Function] },friend3在重寫原型前建立,[[Prototype]]指向最初的原型對象 console.log(friend3.__proto__ === Person4.prototype) // false,重寫整個原型切斷了構造函數與最初原型之間的聯繫 friend3.sayName() // error:friend3.sayName is not a function
console.log(Array.prototype) // 在瀏覽器中查看Array的原型對象,包含sort()等方法 console.log(String.prototype) // 在瀏覽器中查看Array的原型對象,包含substring()等方法
String.prototype.startsWith = function (text) { // 給String的原型對象添加startsWith方法 return this.indexOf(text) === 0 } var msg = 'Hello World' console.log(msg.startsWith('Hello')) // true console.log(msg.startsWith('World')) // false delete String.prototype.startsWith console.log(msg.startsWith('Hello')) // error
function PersonProblem() {} PersonProblem.prototype = { constructor: PersonProblem, name: 'Nicholas', age: 29, job: 'Software Engineer', friends: ['Shelby', 'Court'], sayName: function () { console.log(this.name) }, } var person13 = new PersonProblem() var person14 = new PersonProblem() person13.name = 'Greg' // 從新定義,在實例中屏蔽原型的屬性 person13.friends.push('Van') // 非從新定義,而是向原型的數組中添加一個字符串 console.log(person13.name) // 'Greg',從實例得到 console.log(person14.name) // 'Nicholas',從原型中得到 console.log(person13.friends) // [ 'Shelby', 'Court', 'Van' ],從原型中得到 console.log(person14.friends) // [ 'Shelby', 'Court', 'Van' ],從原型中得到 console.log(person13.friends === person14.friends) // true var person15 = new PersonProblem() person15.friends = [] // 從新定義,在實例中屏蔽原型的屬性 console.log(person15.friends) // [],從實例得到 console.log(person13.friends) // [ 'Shelby', 'Court', 'Van' ],從原型中得到 console.log(person14.friends) // [ 'Shelby', 'Court', 'Van' ],從原型中得到
建立對象 | 過程 | 缺點 |
---|---|---|
Object 構造函數 | 1.建立 Objecty 實例 2.添加屬性和方法 | 同一接口建立多個對象,大量重複代碼 |
對象字面量 | 直接建立包含屬性和方法的對象 | 同一接口建立多個對象,大量重複代碼 |
工廠模式 | 1.用函數封裝建立 Object 實例的過程(添加屬性和方法、返回該實例對象) 2.調用該函數 | 沒有解決對象識別問題,即怎樣知道一個對象的類型 |
構造函數模式 | 1.構造函數封裝(不顯示的建立對象、屬性和方法賦給 this 對象、無 return) 2.new 調用構造函數 | 每一個實例從新建立方法,機制相同的 Function 對象被屢次實例化 |
原型模式 | 1.構造函數封裝(空的,無屬性和方法) 2.原型對象上添加屬性和方法 3.new 調用構造函數 | 對實例來自原型的引用類型屬性修改而非從新定義時,會對原型形成影響 |
對象 | 屬性 | 默認指向 | 用法 |
---|---|---|---|
任何函數 | prototype | 原型對象 | Person.prototype → 構造函數的原型對象 |
實例、原型 | constructor | 構造函數 | person1.constructor === Person.prototype.constructor === Person |
實例 | [[Prototype]] |
原型對象 | person1.__proto__ === Person.prototype (沒有標準方式訪問[[Prototype]] ,但可用 __proto__ ) |
操做符 | 含義 | 用法 |
---|---|---|
new | 建立構造函數的實例(四個步驟) | var person = new Person() |
delete | 刪除實例屬性 | delete person.name |
in | 可否經過對象訪問到屬性(不管屬性在實例仍是原型中) | console.log('name' in person) |
for-in | 返回全部能經過對象訪問到的、可枚舉的屬性(不管屬性在實例仍是原型中) | for(var attr in person){console.log(attr)} |
方法 | 含義 | 參數 | 返回值 |
---|---|---|---|
isPrototypeOf() | 實例是否有指向原型對象的指針 | 實例 | true/false |
Object.getPrototypeOf() | 獲取實例[[Prototype]] 的值 |
實例 | 原型對象 |
Object.setPrototypeOf() | 向實例的[[Prototype]] 寫入新值(指定原型) |
① 實例 ② 指定原型 | |
Object.create() | 建立一個新對象,同時爲其指定原型 | 指定原型 | |
hasOwnProperty() | 屬性是否存在於實例中(非原型中) | 屬性 | true/false |
Object.keys() | 獲取對象(自身)全部可枚舉的屬性 | 對象 | 屬性的字符串數組 |
Object.getOwnPropertyNames() | 獲取對象(自身)全部屬性(不管是否可枚舉) | 對象 | 屬性的字符串數組(原型對象包含 constructor 屬性) |
Object.getOwnPropertySymbols() | 獲取對象(自身)全部符號鍵屬性(不管是否可枚舉) | 對象 | 屬性的符號鍵數組(原型對象包含 constructor 屬性) |
方法/操做符 | 枚舉順序 |
---|---|
for-in | 枚舉順序不肯定,取決於瀏覽器的 JS 引擎 |
Object.keys() | 枚舉順序不肯定,取決於瀏覽器的 JS 引擎 |
Object.getOwnPropertyNames() | 枚舉順序肯定:先以升序枚舉數值鍵,後以插入順序枚舉字符串和符號鍵 |
Object.getOwnPropertySymbols() | 枚舉順序肯定:先以升序枚舉數值鍵,後以插入順序枚舉字符串和符號鍵 |