关注前端小讴,阅读更多原创技术文章
相关代码 →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() | 枚举顺序肯定:先以升序枚举数值键,后以插入顺序枚举字符串和符号键 |