一. 创建对象
创建对象的四个步骤
- 创建一个新的对象
- 绑定作用域(将构造函数的作用域绑定到对象上)
- 初始化对象(执行构造函数的代码)
- 返回新对象
1. 工厂模式
function createPerson(name) {
let obj = new Object()
obj.name = name
obj.sayName = function () {
console.log(this.name)
}
reutrn obj
}
let p = createPerson('张三')
2. 构造函数模式
function Person (name, age) {
this.name = name
this.sayName= function () {
console.log(this.name)
}
}
let p = new Person('张三')
3. 原型模式
function Person () {
}
Person.prototype.name = '张三'
Person.prototype.sayName = function () {
console.log(this.name)
}
4. 组合模式
构造函数用于定义实例属性,原型用于定义方法和共享属性
function Person (name) {
this.name = name
}
Person.prototype = {
constructor: Person,
sayName () {
console.log(this.name)
}
}
5. 动态原型模式
把所有信息都封装在构造函数中,在构造函数中初始化原型
function Person (name) {
this.name = name
if (typeof this.sayName !== 'function') {
Person.prototype.sayName = function () {
console.log(this.name)
}
}
}
6. 寄生构造函数模式
创建一个函数,该函数封装创建对象的代码,然后返回新创建的对象,除了使用new操作符外,这个模式跟工厂模式一模一样
function Person(name) {
let obj = new Object()
obj.name = name
obj.sayName = function () {
console.log(this.name)
}
return obj
}
let p = new Person('张三')
7. 稳妥构造函数模式
没有公共属性,其方法也不引用this对象,不使用new操作符调用构造函数
function Person (name) {
let obj = new Object()
obj.sayName = function () {
console.log(name)
}
return obj
}
let p = Person('张三')
二. 数据属性与访问器属性
1.数据属性
数据属性有4个特性,configurable,表示能否通过delete删除,能否修改其特性。enumerable,表示能否通过for-in遍历。writable,表示能否修改属性的值。value,表示属性的值
直接定义在对象上的属性为数据属性,它们的configurable、enumerable及writable都为true,通过Object.defineProperty添加的数据属性,以上三个值都为false
2.访问器属性
访问器属性有4个特性,前两个与数据属性相同。get,读取该属性时调用的函数,默认为undefined。set,写入该属性时调用的函数,默认为undefined
属性的set/get方法通过Object.defineProperty()方法来设置
该方法接收三个参数,第一个参数为需要设置的对象,第二个参数为对象的属性,第三个参数为设置的内容
function Product () {
Object.defineProperty(this, 'price', {
get: function () {return price * 0.9},
set: function (value) {
if (value <= 0) {
console.log('金额必须大于0')
} else {
price = value
}
}
})
}
3.getOwnPropertyDescriptor
该方法可以获取给定属性的描述,返回的是一个对象,如果是访问器属性,这个对象的属性有configurable、enumerable、get和set。如果是数据属性,这个对象的属性有configurable、enumerable、writable及value
三. 公有/私有属性
公有属性:使用对象的人可以访问的对象属性
私有属性:使用对象的人无法访问的对象属性
function Person () {
this.name = name // 公有属性
var age = 10 // 私有属性
//公有方法
this.showName = function () {
console.log(‘我叫' + this.name)
}
//私有方法
var that = this
var showInfo = function () {
console.log('我是' + that.name) // 私有方法中使用this无法获取到当前对象,所以用that替代
}
}
四. 类型判断
有如下变量
var a = 1
var b = 'abc'
var c = []
var d = {}
1.typeof
typeof弊端:无论引用的是什么类型的对象,都返回object
typeof a // number
typeof b // string
typeof c // object
typeof d // object
//new Number(1)、new String('abc') 与 new Boolean(false) 返回的也是 object
2.toString.call()
通过调用toString方法来判断类型
toString.call(a) // [object Number],与 new Number(1)结果一样
toString.call(b) // [object String],与 new String('ab')结果一样
toString.call(c) // [object Array]
toString.call(d) // [object Object]
3.instanceof
instanceof 后面只能跟对象类型
a instanceof Number // false,new Number(1) 为 true
b instanceof String // false,new String('abc') 为 true
c instanceof Array // true
d instanceof Object // ture
4.constructor
通过该属性可以判断是通过那个构造函数创建的
a.constructor === Number // new Number(1)一样
b.constructor === String // new String('abc')一样
c.constructor === Array
d.constructor === Object
五. 原型
1.双对象法则
其实所谓的对象包含两个独立的对象:构造函数对象,原型对象(prototype)
原型对象只分配一次内存
实例化的过程其实就是拷贝构造函数属性的过程
实例的某些属性,值不一样需要单独的空间,但是某些方法可能完全一样,如果每个实例都有一份则会浪费内存空间,所以使用原型来定义方法,所有实例对象共享一份。
当我们new 一个实例后,系统自动做了如下事情
- 创建一个空对象 var p = {}
- 拷贝构造函数中的方法属性到空对象中
- 自动生成一个__proto__属性指向原型对象
p.__proto__ = xxx.prototype
2.属性搜索机制
访问对象的某个属性时,会先在对象自身的属性列表中寻找,如果找不到,则通过隐藏的proto属性(该属性保存原型对象的内存地址)找到原型对象,然后从原型对象的属性列表中寻找,直到找到该属性或proto为null为止
var fun = function () {}
fun.prototype = {
info: {
name: 'peter',
age: 25
},
height: 170
}
Object.defineProperty(fun.prototype, 'weight', {
get () { return weight },
set (value) { weight = value }
})
var a = new fun()
var b = new fun()
a.info.name = 'jack'
b.info.name = 'tom'
a.height = 175
b.height = 180
a.weight = 60
b.weight = 70
//info为原型对象属性,所有实例共享一份,所以修改b的值后,a的值也会发生变化
a.info.name // tom
b.info.name // tom
//height虽然也是原型对象属性,但是为值类型,所以不会共享,每个实例都有一份
a.height // 175
b.height // 180
//weight虽然是值类型的原型对象属性,但是通过defineProperty方式添加的,所以会共享
a.weight // 70
b.weight // 70
3.属性屏蔽理论
构造函数中的属性(方法)会屏蔽掉原型中的同名属性
function Person () {
this.name = '张三'
this.show = function () {
console.log('我是构造函数的方法')
}
}
Person.prototype = {
name: '李四',
show: function () {
console.log('我是原型的方法')
}
}
var p = new Person()
console.log(p.name) //张三
p.show() // 我是构造函数的方法
如果需要访问原型中的同名属性,可以通过delete清除构造函数中的属性
delete p.name
delete p.show
console.log(p.name) // 李四
p.show() // 我是原型的方法
也可以直接通过 类名.prototype.属性名 来访问
console.log(Person.prototype.name) // 李四
Person.prototype.show() // 我是原型的方法
六. call、apply及bind方法
1.call方法
会将借用对象方法中的this改为自身
var Person = function () {
this.show = function () {
console.log('这是Person的方法,年龄' + this.age)
}
}
var Student = function () {
this.show = function () {
console.log('这是Student的方法,年龄' + this.age)
}
}
var s = new Student()
s.age = 20
s.show() // 这是Student的方法,年龄20
var p = new Person()
p.show.call(s) // 这是Person的方法,年龄20
借用方法时也可以传递参数
var add = function (num1, num2) {
console.log(num1 + num2)
}
var show = function () {
console.log('哈哈哈哈哈')
}
add.call(show, 1, 2) // 3
2.apply方法
apply与call的用法几乎一样
call的传参是离散的,而apply是把所有参数放在一个数组传递
apply虽然传递的是数组,但是使用时是把数组拆开的
var arr1 = [1, 2, 3, 4]
Array.prototype.push.apply(arr1, [5, 6, 7, 8])
====>
Array.prototype.push.call(arr1, 5, 6, 7, 8)
====>
arr1.push(5, 6, 7, 8)
3.bind方法
bind方法与apply和call很相似,也是可以改变函数体内this的指向
bind方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以第一个参数作为this,第二个及后面的参数加上本身的参数作为原函数的参数来调用原函数
var bar = function () {
console.log(this.x)
}
var foo = { x: 3 }
bar() // undefined
bar.bind(foo)() // 3
七. callee/caller属性
1.callee属性
callee 是 arguments 的一个属性成员,它表示对函数对象本身的引用
function test(a, b) {
console.log(arguments.length) // 3,实参的个数
console.log(test.length) // 2,形参的个数
console.log(arguments.callee) // arguments.callee === test
}
test(1, 2, 3)
callee可以用来递归函数
function sum(n) {
if (n > 0) return n + arguments.callee(n - 1)
// 等价于 if (n > 0) return n + sum(n - 1)
return 0
}
console.log(sum(5)) // 15
2.伪数组
伪数组就是一个包含length属性的json对象,key都是0, 1, 2, 3......
伪数组每次都要自己计算length个数,arguments就是一个伪数组
我们可以通过下面的方式将伪数组转换成数组
var arr1 = {0: 'a', 1: 'b', length: 2}
Array.prototype.slice.call(arr1) // ["a", "b"]
var arr2 = {length: 2}
Array.prototype.slice.call(arr2) // [undefined, undefined]
3.caller属性
caller属性表示当前函数被哪个函数调用
function show () {
console.log(show.caller.toString())
}
function test () {
show()
}
test() // f test () { show() }
show() // null
八. 原型链
所谓原型,就是prototype、proto 和 constructor 的三角关系
function Foo() {}
var f1 = new Foo
1.prototype
构造函数有一个prototype属性,指向它的原型对象
实例对象没有prototype属性
2.constructor
构造函数:用来初始化新创建的对象的函数,在例子中,Foo就是构造函数
通过同一个构造函数实例化的多个对象具有相同的原型对象
原型对象有一个constructor属性,指向该原型对象对应的构造函数
Foo.prototype.constructor === Foo
重新定义原型对象后,其constructor属性会指向Object()
Foo.prototype = {} // Foo.prototype.constructor === Object
如果重新定义原型对象不希望改变其constructor,可设置为之前的constructor
Foo.prototype = {
constructor: Foo
... // 其他属性
}
实例本身没有constructor属性,但由于实例可以继承原型对象的属性,所以也拥有了constructor属性,同样指向构造函数
f1.constructor === Foo.prototype.constructor === Foo
function Foo () {}
====> 等价于
var Foo = new Function()
//所以可以看做Foo是Function的实例对象,Foo本身没有constructor,而是继承自Function.prototype,所以
Foo.constructor === Function.prototype.constructor === Function
3.proto
实例对象有一个proto属性,指向构造函数的原型对象
f1.__proto__ === Foo.prototype // Foo是创建f1的构造函数
任何函数都可以看成是Function的实例,Function也可以看成是调用自身的new操作实例化的,所以
Foo.__proto__ === Function.prototype
Function.__proto__ === Function.prototype
Foo.prototype作为原型对象,同时也是实例对象。任何对象都可以看做是通过Object()构造函数实例化的对象
Foo.prototype.__proto__ === Object.prototype
Function.prototype.__proto__ === Object.prototype
Object()也是函数,它的构造函数就是Function
Object.__proto__ === Function.prototype
九. 继承
1.原型链继承
function Super () {
this.name = '张三'
}
function Sub (age) {
this.age = age
}
Sub.prototype = new Super()
let sub = new Sub(20)
console.log(sub.name) // 张三
2.构造函数继承
function Super (name) {
this.name = name
}
function Sub (name, age) {
Super.call(this, name)
this.age = age
}
let sub = new Sub('张三', 20)
3.组合继承
function Super (name) {
this.name = name
}
Super.prototype.job = 'teacher'
function Sub (name) {
Super.call(this, name)
}
Sub.prototype = new Super()
let sub = new Sub('张三')
console.log(sub.job) // 'teacher'
4.原型式继承
function object (obj) {
function F() {}
F.prototype = obj
reutrn new F()
}
let person = {
name: '张三'
age: 20
}
let p = object(person)
console.log(p.name) // 张三
5.寄生式继承
function object (obj) {
function F() {}
F.prototype = obj
return new F()
}
function createAnother (obj) {
let another = object(obj)
another.sayName = function () {
console.log(this.name)
}
return another
}
let person = { name: '张三' }
let p = createAnother(person)
p.sayName() // 张三
6.寄生组合式继承
function object (obj) {
function F() {}
F.prototype = obj
return new F()
}
function inheritPrototype(sub, super) {
let prototype = object(super.prototype)
prototype.constructor = sub
sub.prototype = prototype
}
function Super(name) {
this.name = name
}
Super.prototype.sayName = function () {
console.log(this.name)
}
function Sub(name, age) {
Super.call(this, name)
this.age = age
}
inheritPrototype(Sub, Super)
let sub = new Sub('张三', 20)
sub.sayName() // 张三