JavaScript高级知识-ES5~6

ES5

原型

获取对象的原型:

        const obj = {
            name: '张三',
            age: 18
        }
        // 浏览提共的获取对象原型的方法
        console.log(obj.__proto__);
        // 官方提供的获取对象原型的方法
        console.log(Object.getPrototypeOf(obj));
    

在这里插入图片描述

作用

获取某个对象的属性的查找方式如下:

  1. 优先从当前对象查找,找到即返回,否则执行步骤2
  2. 沿着原型链查找,找到即返回,直到返回null

示例:我们在Object原型上设置了sex属性,对于一个新创建的对象obj上是没有这个sex属性的,但是当我们查找obj.sex的时候却返回了value“男”,这就是原型的作用之一。

        Object.prototype.sex = '男'
        const obj = {
            name: '张三',
            age: 18
        }
        // 浏览提共的获取对象原型的方法
        console.log(obj.__proto__);
        // 官方提供的获取对象原型的方法
        console.log(Object.getPrototypeOf(obj));
        console.log(obj.sex)
        console.log(obj.id)

在这里插入图片描述

函数的原型prototype

        var obj = {};
        function test() {}
        // 1.将函数看成一个普通的对象时,它是具有__proto__(隐式原型)
        console.log('obj.__proto__:', obj.__proto__)
        console.log('test.__proto__:',test.__proto__)
        
        // 2.将函数看成一个函数时,它是具备prototype
        // 作用:用来构建对象时,给对象设置隐式原型的
        console.log('test.prototype:', test.prototype)
        // 对象是没有prototype的
        // console.log('obj.prototype:', obj.prototype)

在这里插入图片描述

作用

       function Student (name, age) {
         this.name = name;
         this.age = age;
         this.running = function(){
            console.log(`${this.name}在跑步`)
         }
       }
       Student.prototype.singing = function() {
       		console.log(`${this.name}在唱歌`);
       }
       var s1 = new Student('张三', 18);
       var s2 = new Student('李四', 18);

       console.log('他们的running是不是同一个?', s1.running === s2.running);
       console.log('他们的singing是不是同一个?', s1.singing === s2.singing);
    

在这里插入图片描述
问题:我们可以从上得出,创建多个student对象就会创建多个running(),但很明显,running()是可以共用的。
解决办法:通过显示原型prototype进行设置,不管创建多少个student对象,他们的singing()都指向同一个,这就是prototype的作用之一。
函数的
函数的prototype是一个对象。

constructor

constructor是原型对象上的属性,指向当前的函数对象。
举例:
对于下面的Student函数。
Student的原型:Student.prototype
constructor: Student.prototype.contructor
关系:Student.prototype.contructor 指向Student

       function Student (name, age) {
         this.name = name;
         this.age = age;
       }
       var studentPrototype = Student.prototype;
       console.log('studentPrototype:', studentPrototype)
       console.log('studentPrototype.constructor:', studentPrototype.constructor);
       console.log(studentPrototype.constructor === Student)

在这里插入图片描述

面向对象的特性

面向对象有三大特性:封装、继承、多态。

  • 封装:将属性和方法封装到一个类中,称之为封装;
  • 继承:继承是面向对象中非常重要的,不仅仅可以减少重复代码的数量,也是多态前提(纯面向对象中);
  • 多态:不同的对象在执行时表现出不同的姿态。

原型链继承

目前student的原型是p对象,而p对象的原型是Person默认的原型。
注意:步骤4和5是不可以调整顺序的,否则会有问题。(在下面的示例中,会报错studying is not a function)

       // 1.定义父类构造函数 
       function Person (name) {
            this.name = name;
       }
       // 2.父类原型上添加内容
       Person.prototype.running = function () {
        console.log(`${this.name} is running`);
       }
       // 3.定义子类构造函数
       function Student (age) {
            this.age = age;
       }
       // 4.创建一个父类的实例对象(new Person()),用这个实例对象作为子类的原型对象
       var p = new Person('张三');
       Student.prototype = p;
       // 5.在子类原型上添加内容
       Student.prototype.studying = function() {
        console.log(`${this.name} is studying`)
       }
       var s = new Student(18);
       console.log(s.name);
       console.log(s.running());
       console.log(s.studying());

在这里插入图片描述
在这里插入图片描述

原型链继承的弊端

某些属性其实是保存在p对象上的

  • 第一,我们通过打印对象是看不到这个属性的;
  • 第二,这个属性会被多个对象共享。如果这个对象是一个引用类型,那么就会造成问题;
  • 第三,不能给Person传递参数(让每个stu有自己的属性)。因为这个对象是一次性创建的(没办法定制化)。

组合继承

组合继承是借用构造函数来实现继承的目的。
Person.call(this, name)这里调用Person()构造函数,此时函数里面的this指向的是Student的示例对象。
在这里插入图片描述
在这里插入图片描述

       // 1.定义父类构造函数 
       function Person (name) {
            this.name = name;
       }
       // 2.父类原型上添加内容
       Person.prototype.running = function () {
        console.log(`${this.name} is running`);
       }
       // 3.定义子类构造函数
       function Student (name, age) {
            // 借用构造函数继承
            Person.call(this, name)
            this.age = age;
       }
       var p = new Person('李四', 12);
       Student.prototype = p;
       // 4.在子类原型上添加内容
       Student.prototype.studying = function() {
        console.log(`${this.name} is studying`)
       }
       var s = new Student('张三',18);
       console.log(s.name);
       console.log(s.running()); 
       console.log(s.studying());

在这里插入图片描述

存在的问题

组合继承最大的问题是无论在什么情况下,都会调用两次父类构造函数。

  • 一次在创建子类原型的时候;
  • 另一次在子类构造函数内部(每次创建子类实例的时候)

所有子类实例事实上会拥有两份父类的属性:

  • 一份在当前的实例里面(person本身),另一份在子类对应的原型对象中(person.__proto__里面);但是不会出现问题,默认一定是访问实例本身这部分。

寄生组合继承

// 创建对象的过程
function createObject (obj) {
    function F() {};
    F.prototype = obj;
    return new F();
}

// 将SubType和SuperType联系在一起
// 寄生式函数
function inherit (SubType, SuperType) {
    SubType.prototype = createObject(SuperType);
    Object.defineProperty(SubType.prototype, 'constructor', {
        enumerable: false,
        configurable: true,
        writable: true,
        value: SubType
    })
}

ES6

原型继承关系

在这里插入图片描述

class类的定义

定义方法:类声明式和类表达式

// 第一种
class XXX {

}
// 第二种
var yyy = class XXX {

}

示例:

 // ES5类的声明
    function Student (name, age) {
        this.name = name;
        this.age = age;
    }
    Student.prototype.running = function () {
        console.log(`${this.name} is running`);
    }
    // ES6类的声明
    class Person {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
        running() {
            console.log(`${this.name} is running`);
        }
    }
    var p = new Person('张三', 18);
    var p1 = new Person('张三ds', 18);
    console.log(p.name)
    p.running();
    console.log(p.__proto__ === Person.prototype) // true
    console.log(p.running == p1.running) // true

类的访问器编写方式:

    class Person {
        constructor(name, age) {
            this._name = name;
            this._age = age;
        }
        set name(value) {
            console.log('设置新的name')
            this._name = value;
        }
        get name() {
            console.log('返回name')
            return this._name;
        }
        running() {
            console.log(`${this._name} is running`);
        }
    }
    var p = new Person('张三', 18);
    // console.log(p.age) undefined
    // console.log(p._age) 18 不建议
    console.log(p.name) 
    p.name = '李四';

在这里插入图片描述

new一个对象时发生的事情

  1. 在内存中创建一个新的对象(空对象);
  2. 这个对象内部的[[prototype]]属性会被赋值为该类的prototype属性;
  3. 构造函数内部的this,会指向创建出来的新对象;
  4. 执行构造函数的内部代码(函数体代码);
  5. 如果构造函数没有返回非空对象,则返回创建出来的新对象。

类的静态方法

静态方法通常用于定义直接使用类来执行的方法,不需要有类的实例,使用static关键字来定义:

class Person {
        constructor(name, age) {
            this._name = name;
            this._age = age;
        }
        static sayHello() {
            console.log('这是静态方法');
        }
        static sVal = '静态属性';
    }
    console.log(Person.sayHello());
    console.log(Person.sVal);

extends继承

super关键字

  • 执行super.methodName(…)来调用父类方法
  • 执行super(…)来调用父类constructor(只能在子类的constructor中使用)
    class Person {
        constructor(name) {
            this.name = name;         
        }
        running () {
            console.log(`${this.name} is running`);
        }
    }
    class Student extends Person{
        constructor(name, age) {
            super(name);
            this.age = age;
        }
    }
    const stu = new Student('张三', 18);
    console.log(stu.name); // 张三
    stu.running(); // 张三 is running

Proxy

我们先看一下Object.defineProperty如何监听对象的属性:

var obj = {
    name: '中',
    age: 1
}
// 监听所有的属性:遍历所有的属性,对每一个属性使用defineProperty
const keys = Object.keys(obj);
for (const key of keys) {
    let val = obj[key];
    Object.defineProperty(obj, key, {
        set: function(newVal) {
            console.log(`${key}设置了新的值${newVal}`);
            value = newVal;
        },
        get: function () {
            console.log(`返回${key}的值${value}`);
            return value;
        },
    })
}

obj.name = '李四';
console.log(obj.name);

在这里插入图片描述

问题:

Object.defineProperty对于新增属性、删除属性等操作是无能为力的。

解决方法:

如果我们希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(proxy);之后对该对象的所有操作,都可以通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作。
MDN文档

const p = new Proxy(target, handler)

参数

  • target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
  • handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
var obj = {
    name: '中',
    age: 1
}

const p = new Proxy(obj, {
    get: function (target, property, receiver) {
        // target:目标对象
        // property: 被获取的属性名
        // receiver: Proxy 或者继承 Proxy 的对象
        console.log(`监听${target}对象的${property}`)
        return target[property];
    },
    set: function (target, property, value, receiver) {
        // value: 新属性值
        console.log(`监听${target}对象设置的${property}属性的值${value}`)
        target[property] = value;
    }
})

p.name;
p.name = '张三'

在这里插入图片描述

Reflect

Reflect文档

作用:

主要提供了很多操作JavaScript对象的方法,有点像Object中操作对象的方法;比如:Reflect.getPrototypeOf(target)类似于Object.getPrototypeOf();、Reflect.defineProperty(target, propertyKey, attributes)类似于Object.defineProperty()。

为什么需要reflect?

早期的ECMA规范中没有考虑到这种对对象本身的操作如何设计会更加规范,所以将这些API放到了Object上面。但是Object作为一个构造函数,这些操作实际上放到它身上并不合适。因此,ES6新增了Reflect,就把这些操作集中到了Reflect对象上了。最后,在使用Proxy时,可以做到不操作原对象。
Reflect和Object的比较

var obj = {
    name: '中',
    age: 1
}

const p = new Proxy(obj, {
    get: function (target, property, receiver) {
        // target:目标对象
        // property: 被获取的属性名
        // receiver: Proxy 或者继承 Proxy 的对象
        console.log(`监听${target}对象的${property}`)
        return target[property];
    },
    set: function (target, property, value, receiver) {
        // target[property] = value;
        // 1.代理对象的目的:不再直接操作原对象
        // 2.Reflect.set方法有返回Boolean值,可以判断set是否成功
        // 3.receiver就是外层的Proxy对象
        // 4.Reflect.set/get最后一个参数,可以决定对象访问器setter/getter的this指向
        const isSuc = Reflect.set(target, property, value);
        if (!isSuc) {
            console.log('赋值失败')
        }
    }
})

p.name;
p.name = '张三'
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值