夯实基础(二)-对象

925 阅读8分钟

1.数据属性

对象的数据属性包含四个:1.Configurable 2.Enumerable 3.Writable 5.Value

1.1 Configurable

解释:是否可配置该属性,默认为true,当设置为false的时候将不可更改,不可删除,也无法重新设置为true。设置新值会被忽略。严格模式下会报错,在node环境和浏览器环境运行可能有差异.例如:

var person = {}

Object.defineProperty(person, 'name', {
    configurable: false,
    value: "Nicholas"
})
console.log(person.name) // Nicholas
Object.defineProperty(person, 'hah', {
    configurable: false,
    value: "222"
})
person.name = '尼古拉斯.赵四'

console.log(person.name) // Nicholas

delete person.hah
console.log(person.hah) // 222

console.log(person) // node中为控对象,浏览器中为{name: 'Nicholas', 'hah': 222}

1.2 Enumerable

解释:控制属性是否能够被枚举,默认为true,当设置为false的时候将不会被for...in...出

var person = {}
Object.defineProperty(person, 'text', {
    enumerable: false,
    value: "222"
})

for (let key in person) {
    console.log(key) // 打印不出text
}

person.text = '111'  // 修改也是无效的

1.3 writable

解释:控制属性值是否能够被修改

1.4 value

解释:属性值

2.访问器属性

2.1 get

解释:每当访问一个对象属性的时候都会出触发get

2.2 set

解释:每当设置一个对象属性的时候都会出触发set

var person = {}
Object.defineProperty(person, 'txt', {
    get: function () {
        console.log('我拿了txt')
    },
    set: function () {
        console.log('我设置了')
    }
})

console.log(person.txt)
person.txt = 1

Vue2 版本也是利用了访问器属性来实现的,后面我们会简单的介绍一下。

3.创建对象

3.1.对象字面量

var a = {}

3.2.new 方法


var b = new Object()

3.3.工厂函数

由于ES6之前没有class类的概念,所以就有了使用一个函数来模拟一个类,经过这个类创建的对象具备属性的一致性:

function createObj (name, age) {
    var o = new Object()
    o.name= name
    o.age = age
    o.sayName = function () {
        console.log(this.name)
    }

    return o
}

var a = createObj('xx', 12)
var b = createObj('xx', 13)

优点:可以自定义,
缺点:无法复用方法,并且无法溯源(无法找到具体的构造函数即 instanceof 无法检测)

3.4.构造函数创建对象

function CreateObj (name, age) {
    this.name= name
    this.age = age
    this.sayName = function () {
        console.log(this.name)
    }
}

var a = new CreateObj('xx', 12)
var b = new CreateObj('xx', 13)

a.constructor === b.constructor === CreateObj // true

缺点:方法为私有的,同样的方法每个对象需要新建一个,浪费内存
优点:自定义,灵活传参

new的过程中做了四件事情:

(1) 创建一个新对象

(2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)

(3) 执行构造函数中的代码(为这个新对象添加属性)

(4) 返回新对象

3.5.原型方式创建

通过原型链去创建

function CreateObj () {
}
CreateObj.prototype.name = 'zhangsan'

var a = new CreateObj()

优点:共享方法,节约内存
缺点:无法传参

3.6.组合使用构造函数

总结上面的问题不难发现缺点就是不够灵活,或者不能共享方法;了解了缺点我们就可以利用采用原型和构造函数的相互结合的方式去解决这些缺点;

function CreateObj (name, age) {
    this.name = name
    this.age = age
}
CreateObj.prototype = {
    constructor: CreateObj,
    sayName: function () {
        console.log(this.name)
    }
}

4.原型链

很多学习了很多年的老司机还是说不清原型链,其实总结一些就几句话。 1.构造函数都有个原型对象 =》obj.prototype

2.实例都有个指向构造函数原型对象的指针__proto__, instance.proto === obj.prototype

3.原型对象都有一直指向构造函数的指针constructor

4.让一个原型指向一个构造函数的实例,这样子的原型就产生了一个指向父类原型的指针 例子:


function Create() {

}

function a() {}
var obj = new Create()

// 实例都有个指向构造函数原型对象的指__proto__, instance.__proto__ === obj.prototype
obj.__proto__ === Create.prototype

// 原型对象都有一直指向构造函数的指针constructor
Create.prototype.constructor === Create
// 让一个原型指向一个构造函数的实例,这样子的原型就产生了一个指向父类原型的指针

a.protoType = new Create()

a.protoType.__proto__ === Create.prototype

Create.prototype.__proto__ === Object.prototype

Object.prototype.__proto__ === null

a => Create => Object => null

通过原型形成了一条链

5.继承

5.1.原型继承

参考上一节原型链 缺点:无法解决引用类型的问题,引用类型共享

5.2.构造函数继承

因为原型链的继承的缺陷问题,有人采用了构造函数继承。 构造函数继承的思想,就是在子类中通过call或者apply的方式,来改变this指向。从而继承构造函数的方法。

function father (name) {
    this.name = name
    this.methods = ['get', 'post']
}

function son () {
    father.call(this)
}

var xiaoming = new son('xiaoming')

优点:解决了传参和引用类型的问题 缺点:降低了函数复用

5.3.组合继承

思路也很简单,就是实现用使用构造函数,完成私有属性的定制,使用原型去共享需要复用的属性。

function father (name) {
    this.name = name
    this.methods = ['get', 'post']
}

function son (name) {
    father.call(this, name)
}
son.prototype.sayName = function () {
    console.log(this.name)
}
var xiaoming = new son('xiaoming')
var xiaoming2 = new son('xiaoming2')

xiaoming.sayName === xiaoming2.sayName // true

优点:结合了第一种和第二种的优点

5.4.原型式继承

原型式继承的,基础在于需要一个基础对象传入,使用Object.create去创建新的对象。例如:

var person = {
    name: 'xiaoming',
    age: 18
}

var xiaoming = Object.create(person)
var xiaoming2 = Object.create(person)

本质实际上是原型链继承:

var person = {
    name: 'xiaoming',
    age: 18
}
function CreateObj (o) {
    function F() {}
    F.prototype = o
    return new F()
}

var xiaoming = CreateObj(person)
var xiaoming2 = CreateObj(person)

5.5.寄生式继承

寄生式继承式在原型式基础上增强的,原理也很简单。 1.有个基础对象 2.以基础对象为基础,创建一个新的对象 3.新对象添加方法

var person = {
    name: 'xiaoming',
    age: 18
}
function Clone (person) {
    var a = Object.create(person)
    a.sayName = function () {
        console.log(this.name)
    }
    return a
}

var xiaoming = new Clone(person)
var xiaoming2 = new Clone(person)

5.6.寄生式组合继承

看完前面的几种方式,觉得组合继承是最好,能够共享该共享也能区别对待。但是如果我们仔细分析也是会有问题的,请看下面的例子🌰:


function sub(name) {
    this.name = name
}

function sup(name, age) {
    sub.call(this, name) // 第二次
    this.age = age
}

sup.prototype = new sub() // 第一次
sup.prototype.constructor = sup
sup.prototype.sayName = function () {
    console.log(this.age)
}
var instance = new sup('xm', 23)

通过上面的🌰,能够看的出来,sub这个函数被调用了2次,所以变量name也会同时存在在prototype和实例上

寄生式组合继承就恰好的解决了这个问题,所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

function sub(name) {
    this.name = name
}

function sup(name, age) {
    sub.call(this, name)
    this.age = age
}
function inherit(sup, sub) {
    var o = Object.create(sup.prototype)
    sub.prototype = o
    o.constructor = sub
}

var instance = new sup('xm', 23)

这时候再去看sup的prototype上已经没有name的属性了,完美。

5.7.class继承

es新增的语法糖

function A(name) {
    this.name = name
}

class B extends A {
    constructor(name, age) {
        super(name)
        this.age = age
    }
}

6.对象的一些API

6.1 Object.assign

将单个或者多个对象合并到目标对象。是浅拷贝。

Object.assign(target, obj1, obj2....)

当多个对象的属性名称重合的时候。后面的会覆盖前面的。

Object.keys/values

获取一个对象所有key和value值

Object.entries/fromEntries

entries:返回一个数组,数组元素是一个包含对象key和value,一一对应的数组 fromEntries:entries的逆操作

Object.freeze

冻结对象。不允许修改其中属性。可以用isFrozen去检测。 在做Vue项目的时候,可以对不会修改的对象进行freeze,这样子。就不会再监听该对象了,提高性能。

Object.defineProperty

可以用来定义对象的属性,也是vue2实现数据监听的本质所在

属性太多就不一一介绍了,以上是常用的

7.简单vue的实现

7.1初始化

vue的第一步通过new初始化,所以vue是一个构造函数

    // 构造虚拟dom的容器
    function nodeContainer(node, vm, flag) {
        console.log(node)
        var flag = flag || document.createDocumentFragment()
        var child
        // 遍历节点
        while(child = node.firstChild) {
            compile(child, vm)
            flag.appendChild(child)
            if (flag.firstChild) {
                nodeContainer(child, vm, flag)
            }
        }
        return flag
    }
    // 构造函数
    function Vue(options) {
        this.data = options.data
        observe(this.data, this)
        var id = options.el
        console.log(id, document.getElementById(id))
        var dom = nodeContainer(document.getElementById(id), this);
        document.getElementById(id).appendChild(dom)
    }
    // data中的属性设置get和set
    function define(obj, key, value) {
        var dep = new Dep()
        Object.defineProperty(obj, key, {
            get: function() {
                if(Dep.global){//这里是第一次new对象Watcher的时候,初始化数据的时候,往订阅者对象里面添加对象。第二次后,就不需要再添加了
                    dep.add(Dep.global);
                }
                return value
            },
            set: function(newValue) {
                if(newValue === value){
                    return;//如果值没变化,不用触发新值改变
                }
                value = newValue;//改变了值
                dep.notify();
                console.log("set了最新值"+value);
            }
        })
    }
    // 给data中属性设置get和set
    function observe (obj,vm){
        Object.keys(obj).forEach(function(key){
        define(vm,key,obj[key]);
        })
    }

    // 编译v-model和模板语法
    function compile(node, vm) {
        var res = /\{\{(.*)\}\}/g
        // node为元素
        if (node.nodeType === 1) {
            var attr = node.attributes
            for (var i = 0; i < attr.length; i++) {
                if (attr[i].nodeName === 'v-model') {
                    var name = attr[i].nodeValue
                    new Watcher(vm,node,name)
                    node.addEventListener('input',function(e){
                        vm[name] = e.target.value
                    })
                    node.value = vm.data[name]
                }
            }
        }
        // node为文本
        if (node.nodeType === 3) {
            if (res.test(node.nodeValue)) {
                var name = RegExp.$1
                name = name.trim()
                new Watcher(vm,node,name)
                node.nodeValue = vm.data[name];
            }
        }
    }

    // 发布者模式
    function Dep() {
        this.subs = []
    }
    Dep.prototype = {
        add: function(sub) {
            this.subs.push(sub)
        },
        notify: function() {
            this.subs.forEach(function(sub) {
                sub.update()
            })
        }
    }
    // 订阅者
    function Watcher(vm, node, name) {
        Dep.global = this
        this.name = name
        this.node = node
        this.vm = vm
        this.update()
        Dep.global = null
    }
    Watcher.prototype.update = function() {
        this.get()
        switch (this.node.nodeType) {
            case 1:
                this.node.value = this.value;
                break;
            case 3:
                this.node.nodeValue = this.value
                break
            default: break;
        }
    }
    Watcher.prototype.get = function() {
        this.value = this.vm[this.name]
    }
    new Vue({
        el: 'mvvm',
        data: {
            text: 10000
        }
    })

本文使用 mdnice 排版