关注[前端小讴] ,阅读更多原创技术文章
创建对象
创建单个对象:Object 构造函数 和 对象字面量 缺点:使用一个接口创建很多对象,产生大量重复代码
相关代码 →
工厂模式
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)
工厂模式解决了创建多个相似对象的问题,但没有解决对象识别问题 (怎样知道一个对象的类型)
构造函数模式
除了 Object 和 Array 等原生构造函数,还可以创建自定义的构造函数 构造函数模式 vs 工厂模式
不显式的创建对象 直接将属性和方法赋给 this 对象 没有 return
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 一个对象后:
创建了一个新对象(实例) 新对象内部的[[Prototype]]
特性被赋值为构造函数的prototype
属性(共同指向原型) 将构造函数的作用域(即 this)赋给新对象 执行构造函数中的代码(即:为这个对象添加新属性) 返回新对象或非空对象 创建的对象(实例)既是 Object 的实例 ,又是构造函数的实例 ,其 constructor
属性指向构造函数 可以确保自定义构造函数的实例被标识为特定的类型 ,是构造函数模式胜过工厂模式的地方
console. log ( person1. constructor === Person)
console. log ( person2. constructor === Person)
console. log ( person1 instanceof Object )
console. log ( person1 instanceof Person )
console. log ( person2 instanceof Object )
console. log ( person2 instanceof 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 ( )
Person ( 'Greg' , 27 , 'Doctor' )
global. sayName ( )
var o = new Object ( )
var p = new Object ( )
Person. call ( o, 'Kristen' , 25 , 'Nurse' )
Person. apply ( p, [ 'Kristen' , 25 , 'Nurse' ] )
o. sayName ( )
p. sayName ( )
构造函数的问题
定义的方法会在每个实例上都创建一遍,每定义一个函数,就实例化一个对象 ,创建 2 个完成同样任务的 Function
实例没有必要
function Person2 ( name, age, job) {
this . name = name
this . age = age
this . job = job
this . sayName = new Function ( console. log ( this . name) )
}
console. log ( person1. sayName === person2. sayName)
将对象的方法移到构造函数外部,避免多次创建 Function 实例
function Person3 ( name, age, job) {
this . name = name
this . age = age
this . job = job
this . sayName = sayName
}
function sayName ( ) {
console. log ( this . name)
}
var person4 = new Person ( 'Nicholas' , 29 , 'Software Engineer' )
构造函数仍未解决 的问题:① 创建的全局函数实际上只需要被某个对象中调用;② 若对象有多个方法,需创建很多全局方法
原型模式
每个函数都有 prototype
属性,该属性是一个指针 ,指向函数(通过调用构造函数 而创建的那个对象实例的)原型对象 使用原型对象的好处是,其所有对象实例共享其所包含的属性和方法
function PersonPrototype ( ) { }
PersonPrototype. prototype. name = 'Nicholas'
PersonPrototype. prototype. age = 29
PersonPrototype. prototype. job = 'Software Engineer'
PersonPrototype. prototype. sayName = function ( ) {
console. log ( this . name)
}
var person5 = new PersonPrototype ( )
var person6 = new PersonPrototype ( )
person5. sayName ( )
person6. sayName ( )
console. log ( person5. sayName === person6. sayName)
理解原型对象
只要创建一个函数 ,就会为函数创建prototype
属性指向原型对象 ,(默认情况下)原型对象自动获得constructor
属性,指回与之关联的构造函数
console. log ( PersonPrototype. prototype. constructor)
console. log ( PersonPrototype === PersonPrototype. prototype. constructor)
实例内部包含[[Prototype]]指针,指向 实例的构造函数的原型对象 ,但没有标准的方式访问[[Prototype]] 在浏览器中,可用 __proto__
属性实现[[Prototype]]的功能
console. log ( person5. __proto__)
console. log ( person5. __proto__ === PersonPrototype. prototype)
console. log ( person5. __proto__. constructor)
console. log ( person5. __proto__ === person6. __proto__)
instanceof
检查实例的原型链 中,是否包含指定构造函数的原型
console. log ( person5 instanceof PersonPrototype )
console. log ( person5 instanceof Object )
console. log ( PersonPrototype. prototype instanceof Object )
原型对象的 isPrototypeOf()
方法,检测实例 中是否有指向原型对象 的指针
console. log ( PersonPrototype. prototype. isPrototypeOf ( person5) )
console. log ( PersonPrototype. prototype. isPrototypeOf ( person1) )
Object.getPrototypeOf()
方法(参数一般为实例),返回参数的[[Prototype]]
的值(一般为原型对象)
console. log ( Object. getPrototypeOf ( person5) )
console. log ( Object. getPrototypeOf ( person5) === person5. __proto__)
console. log ( Object. getPrototypeOf ( person5) === PersonPrototype. prototype)
console. log ( Object. getPrototypeOf ( person5) . name)
console. log ( Object. getPrototypeOf ( person5) . constructor)
Object.setPrototypeOf()
方法,向实例(参数一)的[[Prototype]]
写入一个新值(参数二),从而重写 一个对象的原型继承关系
var biped = {
numLegs: 2 ,
}
var person = {
name: 'Matt' ,
}
Object. setPrototypeOf ( person, biped)
console. log ( person. name)
console. log ( person. numLegs)
console. log ( person. __proto__)
为避免Object.setPrototypeOf()
可能严重影响代码性能 ,可使用Object.create()
创建一个新对象 ,同时为其指定原型 (参数)
var biped2 = {
numLegs: 3 ,
}
var person = Object. create ( biped2)
console. log ( person. numLegs)
console. log ( person. __proto__)
原型层级
var person7 = new PersonPrototype ( )
person7. name = 'Greg'
console. log ( person7. name)
console. log ( person5. name)
delete person7. name
console. log ( person7. name)
使用 hasOwnProperty()
方法,检测属性是否存在于实例 中(存在返回 true),参数为要检测的属性
var person8 = new PersonPrototype ( )
var person9 = new PersonPrototype ( )
console. log ( person8. hasOwnProperty ( 'name' ) )
person8. name = 'Simon'
console. log ( person8. name)
console. log ( person8. hasOwnProperty ( 'name' ) )
console. log ( person9. name)
console. log ( person9. hasOwnProperty ( 'name' ) )
delete person8. name
console. log ( person8. name)
console. log ( person8. hasOwnProperty ( 'name' ) )
可在原型对象上调用 Object.getOwnPropertyDescriptor()
,获取原型属性的描述符
console. log ( Object. getOwnPropertyDescriptor ( person8, 'name' ) )
console. log ( Object. getOwnPropertyDescriptor ( person8. __proto__, 'name' ) )
原型和 in 操作符
单独使用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' ) )
console. log ( 'name' in person9)
person9. name = 'Greg'
console. log ( person9. name) ;
console. log ( person9. hasOwnProperty ( 'name' ) )
console. log ( 'name' in person9)
console. log ( person10. name) ;
console. log ( person10. hasOwnProperty ( 'name' ) )
console. log ( 'name' in person10)
delete person9 'name'
console. log ( person9. name) ;
console. log ( person9. hasOwnProperty ( 'name' ) )
console. log ( 'name' in person9)
同时使用hasOwnProperty()
和in
操作符,判断属性存在于实例还是原型
function hasPrototypeProperty ( object, name) {
return ! object. hasOwnProperty ( name) && name in object
}
var person11 = new PersonIn ( )
console. log ( hasPrototypeProperty ( person11, 'name' ) )
person11. name = 'Greg'
console. log ( hasPrototypeProperty ( person11, 'name' ) )
for-in
循环:返回对象所有能够访问的 、可枚举的 属性(无论来自实例还是原型),屏蔽 了不可枚举 ([[Enumerable]]为 false)的属性(如:原型的 constructor、构造函数的 prototype)
for ( var attr in person11) {
console. log ( ` ${ attr} : ${ person11[ attr] } ` )
}
Object.keys()
方法返回对象(自身 )可枚举的 属性的数组,参数为该对象
var keys = Object. keys ( PersonIn. prototype)
console. log ( keys)
var person12 = new PersonIn ( )
person12. name = 'Bob'
person12. age = 31
var p12keys = Object. keys ( person12)
console. log ( p12keys)
Object.getOwnPropertyNames()
返回对象(自身 )所有属性 (无论是否可枚举)的数组,参数为该对象
var keys = Object. getOwnPropertyNames ( PersonIn. prototype)
console. log ( keys)
var p12keys = Object. getOwnPropertyNames ( person12)
console. log ( p12keys)
console. log ( Object. getOwnPropertyNames ( PersonIn) )
ES6 新增Object.getOwnPropertySymbols()
方法,返回对象(自身 )所有符号键属性 (无论是否可枚举)的数组,参数为该对象
var k1 = Symbol ( 'k1' )
var k2 = Symbol ( 'k2' )
var o = {
[ k1] : 'k1' ,
[ k2] : 'k2' ,
}
console. log ( Object. getOwnPropertySymbols ( o) )
属性枚举顺序
for-in
循环和Object.keys()
在属性枚举顺序 是不确定 的,取决于浏览器的 JS 引擎Object.getOwnPropertyNames()
、Object.getOwnPropertySymbols()
和Object.assign()
在属性枚举顺序 是确定 的:
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) )
console. log ( Object. getOwnPropertySymbols ( o) )
对象迭代
ES7 新增Object.values()
和Object.entries()
方法,接收参数对象,分别返回对象值 和对象键/值对 数组
var o = {
foo: 'bar' ,
baz: 1 ,
qux: { } ,
}
console. log ( Object. values ( o) )
console. log ( Object. entries ( o) )
var o = {
qux: { } ,
}
console. log ( Object. values ( o) )
console. log ( Object. entries ( o) )
console. log ( Object. values ( o) [ 0 ] === o. qux)
console. log ( Object. entries ( o) [ 0 ] [ 1 ] === o. qux)
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 )
console. log ( friend instanceof PersonLiteral )
console. log ( friend. constructor === PersonLiteral)
console. log ( friend. constructor === Object)
可以在对象字面量里设置constructor
属性,让其指向原构造函数 这样设置constructor
属性属于直接在对象上定义的属性 ,会导致constructor
属性的[[Enumerable]]为 true,可以被枚举出来
function PersonLiteral2 ( ) { }
PersonLiteral2. prototype = {
constructor: PersonLiteral2,
name: 'Nicholas' ,
age: 29 ,
job: 'Software Engineer' ,
sayName: function ( ) {
console. log ( this . name)
} ,
}
var friend2 = new PersonLiteral2 ( )
console. log ( friend2. constructor === PersonLiteral2)
console. log ( friend2. constructor === Object)
console. log ( Object. keys ( PersonLiteral2. prototype) )
不在对象字面量内设置constructor
属性,而用Object.defineProperty()
修改对象字面量中constructor
属性的特性,以兼容 JavaScript 引擎
Object. defineProperty ( PersonLiteral2. prototype, 'constructor' , {
enumerable: false ,
value: PersonLiteral2,
} )
console. log ( Object. keys ( PersonLiteral2. prototype) )
原型的动态性
对原型对象 所做的任何修改 都立即从实例上反映 出来,即使先创建实例后修改原型
function Person4 ( ) { }
var friend3 = new Person4 ( )
Person4. prototype. sayHi = function ( ) {
console. log ( 'Hi' )
}
friend3. sayHi ( )
重写整个原型 ,会切断构造函数与最初原型之间的联系,(重写原型前创建的)实例 的[[Prototype]]指针指向最初的原型 (重写原型后创建的实例指向新原型)
Person4. prototype = {
constructor: Person4,
name: 'Nicholas' ,
age: 29 ,
job: 'Software Engineer' ,
sayName: function ( ) {
console. log ( this . name)
} ,
}
console. log ( friend3. __proto__)
console. log ( friend3. __proto__ === Person4. prototype)
friend3. sayName ( )
原生对象原型
所有原生的引用类型 (Array、Object、String…)都是用原型模式 创建的,在其构造函数的原型上定义了方法
console. log ( Array. prototype)
console. log ( String. prototype)
可以像修改自定义对象的原型一样,修改原生对象的原型,添加或删除方法 不推荐 修改原生对象的原型,可能会引起冲突或重写原生方法
String. prototype. startsWith = function ( text) {
return this . indexOf ( text) === 0
}
var msg = 'Hello World'
console. log ( msg. startsWith ( 'Hello' ) )
console. log ( msg. startsWith ( 'World' ) )
delete String. prototype. startsWith
console. log ( msg. startsWith ( 'Hello' ) )
原型的问题
原型模式最大的问题是由其共享 的本性导致的,尤其对于包含引用类型 的属性,对实例的数组、对象等引用类型的属性进行增删改 而非重新定义时,会对原型的引用类型属性造成影响
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)
console. log ( person14. name)
console. log ( person13. friends)
console. log ( person14. friends)
console. log ( person13. friends === person14. friends)
var person15 = new PersonProblem ( )
person15. friends = [ ]
console. log ( person15. friends)
console. log ( person13. friends)
console. log ( person14. friends)
总结 & 问点
创建对象 过程 缺点 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() 枚举顺序确定:先以升序枚举数值键,后以插入顺序枚举字符串和符号键
创建单个对象有哪些方法?这些方法有什么缺点? 工厂模式做出了怎样的优化?该模式有什么缺点? 相比工厂模式,构造函数模式有哪些区别和优势?其在 new 的过程中都发生了什么? 构造函数创建出的对象,其 construtor 属性指向哪里?这样的对象是哪些构造函数的实例? 相比普通函数,构造函数有什么相同点和区别? 构造函数模式有什么缺点?用全局函数代替构造函数内部对象的方法,仍有什么缺点? 函数的 prototype 属性是什么?使用原型对象的好处是什么?如何理解原型对象的 constructor 属性? 构造函数、实例、原型对象之间,分别可以用什么方式相互获取?用什么方法检测实例是否含有指向原型对象的指针? Object.getPrototypeOf()、Object.setPrototypeOf()、Object.create()分别的含义和用法是什么? 代码读取对象属性时,经历了怎样的搜索过程?是否可以通过实例访问和修改原型中的属性值? 在实例中添加与原型的同名属性会怎样?再删除这个实例中新增的属性呢? 单独使用 in 操作符的含义是什么?其和 hasOwnProperty()方法的区别是什么? 请写一段代码,判断某个属性存在于实例还是原型 for-in 的用法是什么?其返回哪些属性屏蔽哪些属性? Object.keys()、Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()、Object.values()、Object.entries()的用法分别是什么? for-in、Object.keys()、Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()在属性枚举时的顺序有什么区别? 用一个对象字面量的新对象重写整个原型对象时,原型对象的 constructor 指向发生了怎样的改变? 写一段代码,用对象字面量重写构造函数的原型对象,且原型对象的 constructor 仍指向原构造函数,并保留 construtor 属性“不可被枚举”的特性 创建实例后再修改原型的属性,实例会受到影响么?为什么? 重写整个原型对象后,构造函数的 prototype 指向哪里?重写前创建的实例的[[Prototype]]属性指向哪里?为什么? 原生引用类型的方法是如何创建的?为什么不推荐修改原生引用类型的原型? 原型模式的“共享”本性,在修改包含引用类型的属性时,会产生怎样的问题?