原型,原型链与继承,类,面向对象

原型链与继承,类,面向对象

原型与原型链

对象与构造函数

构造函数创建实例对象,对象都是由函数创建出来的

构造函数有prototype属性,指向构造函数的原型对象

对象有__proto__属性,指向创建该对象的构造函数的原型对象

原型对象有constructor属性,指向构造函数

// 构造函数
function Person(name){
    this.name = name;
}
// 实例对象
let person = new Person('zsq');
// 二者关系
console.log(person.__proto__ == Person.prototype);   // true
console.log(Person.prototype.constructor == Person); // true

函数也是对象,所以也有__proto__属性,对象都是由函数创建出来的,所以函数对象也有构造函数

函数对象的构造函数是Function(),所以函数对象的__proto__属性指向Funtion的原型对象

console.log(Person.__proto__ == Function.prototype) // true

所以函数既有prototype属性,又有__proto__属性

原型对象也是对象,所以也有__proto__属性,对象都是由函数创建出来的,所以原型对象也有构造函数

原型对象的构造函数是Object(),所以原型对象的__proto__属性指向Object的原型对象

console.log(Person.prototype.__proto__ == Object.prototype) // true

Object()是函数,也是对象,函数对象的构造函数是Function(),函数对象的__proto__属性指向Funtion的原型对象

console.log(Object.__proto__ == Function.prototype) // true

Function()的原型对象也是对象,其构造函数是Object(),所以其__proto__属性指向Object.prototype

console.log(Function.prototype.__proto__ == Object.prototype) // true

Object()的原型对象也是对象,所以也有__proto__属性,指向nullnull是原型链的顶端

console.log(Object.prototype.__proto__ == null) // true

总结

  1. 对象由构造函数创建,有__proto__属性,指向创建该对象的构造函数的原型对象
  2. 函数有prototype属性,指向原型对象,同时函数也是对象,有__proto__属性,函数对象的构造函数是Function()
  3. 原型对象有constructor属性,指向构造函数,同时原型对象也是对象,满足第1点
  4. 一切对象都继承自Object,一切函数对象都继承自Function(Object也是一种构造函数),null是原型链的顶端

在这里插入图片描述

函数与类

函数既可以作为函数,也可以作为对象,既有prototype属性,也有__proto__属性

类实际上就是构造函数,但类构造函数与类不是一回事

可以将类理解为函数,区别是函数在声明是可以提升,函数收到函数作用域限制,类声明时不可提升,类受到块级作用域限制

class Person{}
let p = new Person();
let p_ = new Person.constructor();
console.log(p === p_);  // false

类包含构造函数方法constructor,实例方法,获取函数get(),设置函数set(),静态类方法static

类构造函数与构造函数的区别

调用类构造函数必须使用new操作符否则会报错,而普通构造函数如果不使用new调用,则会以全局的this作为内部对象

类与函数的属性和方法定义的位置

类构造函数定义的属性和方法会创建在实例对象中,在类块中定义的方法会定义在类的原型上,在类块中不能定义数据属性会报错(访问器属性可以),在类块中定义的静态属性(等于=)和方法会定义在类(函数)本身上

静态方法适合作为工厂,处理多个对象

class Person{
    static name = 'qioqio'       // 静态属性,定义在类上
    name_ = 'qio'                // 定义在实例对象上
    constructor(){
         // 定义在实例对象上
    }             
    sayName(){}                  // 定义类原型的方法
    static getName(){}           // 静态方法,定义在类上
}

function Person_(name){
    this.name = name;  // 定义在实例对象上
    name = 'qioqio';   // 静态属性,定义在函数对象上
    getName(){};       // 静态方法,定义在函数对象上
}
类的私有属性
私有属性
  1. class内部不同方法间可以使用,因此this要指向实例化对象
  2. 不能被外部访问,因此实例化对象person.name既不能获得值,也不能设定值,应该返回undefined,甚至应该在实例化之后,并不知道有name这个属性存在,开发者甚至可以自己再person.name = 'new name'动态去创建一个非私有属性
  3. 不能被继承,因此extends后子类不具备该属性
  4. 方便的调用方式,比如类this._name形式
创建

无法完全实现私有属性:闭包,Symbol,WeakMap

Symbol只要不在立即执行函数return _id,就可以实现无法直接访问该值,但如果直接打印实例对象还是能在控制台查看到,并且使用Object.getPropertySymbols()也能看到

WeakMap是将私有属性和私有方法保存到WeakMap中,由于该类型创建在类外,所以不属于实例的属性和方法,由此实现私有,只能通过访问器属性访问

完全实现私有属性:es12新增的**#**

var Person = (function () {
    const _id = Symbol('id');           // 只要不把_id返回到全局作用域,就无法直接访问该值
    const _private = new WeakMap();     // 将属性和方法保存到WeakMap中,不属于实例的属性和方法
    const _privateMethod = new WeakMap();
    class Person {
        #score;                         // 声明私有属性
        constructor(name, age, tel) {
            this.name = name
            this.age = age
            this._phone = tel
            this[_id] = 1
            _private.set(this, 'url:https')
            _privateMethod.set(this, function (newUrl) {
                _private.set(this, newUrl)
            })
            this.#score = 10            // 真正意义的私有属性
        }
        get id() {                      // 利用访问器属性访问函数内部作用域
            return this[_id];
        }
        get private() {
            return _private.get(this);
        }

        get privateMethod() {
            return _privateMethod.get(this);
        }
    }
    return Person;   // 把函数内部作用域的类返回到全局作用域里
}())


var p = new Person('zsq', 21, '15014329752');
console.log(p);                       // Person { name: 'zsq', age: 21, _phone: '15014329752', [Symbol(id)]: 1 }
// console.log('p[_id]', p[_id]);     // ReferenceError: _id is not defined
console.log('p.score', p.score);      // undefined
console.log('id', p.id);
console.log('p.private', p.private);
console.log('p.privateMethod', p.privateMethod);

继承

原型链继承

属性写在构造函数上,通过new创建实例对象

方法写在原型上,如:Person.Prototype.doSomething(),但会造成代码冗余

封装原型减少代码冗余

function Person(){}
Person.prototype.doSomething = function(){}
Person.prototype.doAnything = function(){}

// 封装,但重写了原型,导致Person原型对象上的constructor属性丢失
Person.prototype = {
    doSomething(){},
    doAnything(){}
}

// 定义constructor
// Object.defineProperty(对象,属性名,描述对象)
Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false,       // 不可遍历
    value: Person
})

// 对象上有数据属性:configurable(属性是否可以删除),enumerate(属性是否可以遍历),writable(属性是否可以被修改),value(属性值)
问题
  1. 所有的实例对象默认取得相同的值——解决:在实例对象添加同名属性遮蔽掉
  2. 包含引用类型的属性,实例间会共享属性,在一个实例上修改属性会导致所有实例的属性都被修改
盗用构造函数继承

在子类构造函数中使用call()或apply()调用父类构造函数

function parent(){}
function son(){
    // 继承父类构造函数的属性
    parent.call(this)
}

问题:函数不能重用,不能访问父类原型上定义的方法,因为不是通过new创建子类的

组合继承:原型链+盗用构造函数

使用原型链继承原型上的属性和方法,通过构造函数继承实例属性,既可以让每个方法定义在原型上实现复用,又可以让每个实例都有自己的方法

function parent(){}
parent.prototype.doSomething = function(){}
function son(){
    parent.call(this);               // 第一次调用父类构造函数
}
son.prototype = new parent();        // 第二次调用父类构造函数
son.prototype.constructor = son;

问题:父类构造函数被调用两次

原型式继承

类似于Object.create(原型对象,属性描述对象)

不需要单独创建构造函数就可以实现继承,父类构造函数只被调用一次

问题:包含引用类型的属性,实例间会共享属性(与原型链继承一样)

寄生式组合继承:组合继承+原型式继承
// 封装继承
function inherit(parent, son){
    son.prototype = Object.create(parent.prototype);
    son.prototype.constructor = son;
}
function parent(){}
parent.prototype.doSomething = function(){}
function son(){
    parent.call(this);               
}
inherit(parent, son);
类继承

使用extend实现类继承,使用super()调用父类构造函数

面向对象

三大特性:封装、继承、多态

多态比较不好理解:可以理解为多种状态,同一个方法不同的对象会有不同的状态输出

  • 30
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值