前端笔记 — JS面向对象

一. 创建对象

创建对象的四个步骤

  • 创建一个新的对象
  • 绑定作用域(将构造函数的作用域绑定到对象上)
  • 初始化对象(执行构造函数的代码)
  • 返回新对象
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 一个实例后,系统自动做了如下事情

  1. 创建一个空对象 var p = {}
  2. 拷贝构造函数中的方法属性到空对象中
  3. 自动生成一个__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() // 张三
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342