JavaScript 原型、对象

原型与原型链

原型:构造函数的prototype 属性的值。
原型链:由相互关联的原型组成的链状结构就是原型链。通过一个对象的__proto__可以找到它的原型对象,原型对象也是一个对象,,就可以通过原型对象的__proto__,一直往上找,直至Object.prototype(null)。
原型的作用:实现资源共享。(所有实例共享的属性和方法。)

原型

function Person(name) {
    this.name = name;
}

let p = new Person();
console.log(p.__proto__ === Person.prototype); //true
console.log(Person.prototype.constructor === Person); //true

原型.jpg

constructor

function Person(name) {
    this.name = name;
}

let p = new Person();
console.log(p.constructor === Person); //true
console.log(Person.prototype.constructor === Person); //true

当获取 p.constructor 时,其实 p 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性,所以:

p.constructor === Person.prototype.constructor

重写原型

function Person(){}
let p = new Person();
Person.prototype = { //重新改写了原型。
    name: 'tt',
    age: 18
}
Person.prototype.constructor === Person // false
p.name // undefined
//注意区分与上面的区别。
function Person() {
}

let person2 = new Person();
Person.prototype.name = 'tom';
console.log(person2.name); //tom

重写原型.jpg

原型链

原型链.png

原型链的问题

function Person(){}
Person.prototype.arr = [1, 2, 3, 4];

var person1 = new Person();
var person2 = new Person();

person1.arr.push(5) 
person2.arr // [1, 2, 3, 4, 5]

我们通过其中某一个实例对原型属性的更改,结果会反映在所有实例上面,这也是原型共享属性造成的最大问题。

对象

创建对象

1.字面量

//方式1:
let o1 = {name: 'o1'};
//方式2:
let o2 = new Object({name: 'o2'});
console.log(o2); //{ name: 'o2' }

2.构造函数

let Person = function (name) { 
    this.name = name;
    this.showInfo = function(){
        return this.name;
    }
};
let p = new Person('o3');
console.log(p); //Person { name: 'o3' }

3.Object.create()

Object.create(proto[, propertiesObject])

proto:新创建对象的原型对象。

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。返回一个新对象,带着指定的原型对象和属性。

let p = {name: 'p'};
let o4 = Object.create(p);
console.log(o4); //{}

即o4没有自身的属性方法,都是通过原型继承自P的。

对象的属性

获取和设置

通过点(.)或方括号([])运算符来获取和设置属性的值。

. 运算符不能用的时候,就需要用[]代替。
1、在属性名可变的情况下用[]
2、属性名有空格或者连字符等时用[]

删除属性

delete
let Person = function (name) {
    this.name = name;
    foo = function () {
        console.log(this.name);
    }
};
let p = new Person('o3');
console.log(p.name);//o3
delete p.name;
console.log(p.name);//undefined

检测属性

  • in 运算符
let Person = function (name) {
    this.name = name;
    this.showInfo = function () {
        return this.name;
    }
};
Person.prototype.sayHi = function () {
    console.log("hello");
};
let p = new Person('o3');
for (let a in p) {
    console.log(a);
}
//name
//showInfo
//sayHi
  • hasOwnProperty()
检测给定的名字是否是对象的自有属性
let Person = function (name) {
    this.name = name;
    this.showInfo = function () {
        return this.name;
    }
};
Person.prototype.sayHi = function () {
    console.log("hello");
};
let p = new Person('o3');
console.log(p.hasOwnProperty("showInfo")); // true
console.log(p.hasOwnProperty("sayHi")); //false
  • propertyIsEnumerable()
只有检测到是自身属性(不包括继承的属性)且这个属性的可枚举性为true时它才返回true。

对象的方法

object 静态方法

直接定义在构造函数上的方法和属性是静态的

Object.assign()
用于将所有可枚举属性的值从一个或多个源对象复制(浅拷贝)到目标对象。它将返回目标对象。
语法

Object.assign(target,...sources)

基本用法

  • 合并对象
const target = { a: 1 }
const source1 = { b: 2 }
const source2 = { c: 3 }
Object.assign(target, source1, source2)
console.log(target)
// {a: 1, b: 2, c: 3}

注意:如果目标对象与源对象的属性具有相同的键,或者多个源对象的属性具有相同的键,则后面对象的属性会覆盖前面对象的属性。

const target = { a: 1, b: 1 }
const source1 = { b: 2, c: 2 }
const source2 = { c: 3 }
Object.assign(target, source1, source2)
console.log(target)
// {a: 1, b: 2, c: 3}

如果只传入了一个参数,则该方法会直接返回该参数。

const target = { a: 1 }
Object.assign(target)
console.log(target)
// {a: 1}
console.log(Object.assign(target) === target)
// true

如果传入的参数不是对象,原始类型会被包装为对象。

const target = Object.assign(1)
console.log(target)
// Number {1}
typeof target
// "object"

null和undefined无法被转为对象,所以如果把它们两个作为目标对象则会报错。

const target = Object.assign(null)
const tar = Object.assign(undefined)
// Cannot convert undefined or null to object

如果null和undefined作为源对象,则不会报错,因为基本数据类型被包装,null和undefined会被忽略。

const target = Object.assign({a:1}, null)
const tar = Object.assign({a:1}, undefined)
// {a:1}
const target1 = Object.assign(1, null)
// Number {1}

如果null和undefined作为源对象中的属性值,则它们不会被忽略

const target = Object.assign({ a: 1 }, { b: null }, { c: undefined })
console.log(target)
// {a: 1, b: null, c: undefined}
复制代码
  • 拷贝

复制一个对象

const target = Object.assign({}, { a: 1 })
console.log(target)
// {a: 1}

拷贝symbol类型的属性

const target = Object.assign({}, { a: 1 }, { [Symbol('foo')]: 2 })
console.log(target)
// {a: 1, Symbol(foo): 2}

拷贝的属性是有限制的,继承属性和不可枚举属性无法被拷贝。

const obj = Object.defineProperty({}, 'a', {
  enumerable: false,
  value: 1
})
console.log(obj)
// {a: 1}
const target = Object.assign({b: 2}, obj)
console.log(target)
// {b: 2}

现在把a属性变成可枚举的属性。

const obj = Object.defineProperty({}, 'a', {
  enumerable: true,
  value: 1
})
console.log(obj)
// {a: 1}
const target = Object.assign({b: 2}, obj)
console.log(target)
// {b: 2, a: 1}

接下来再看看基本数据类型的可枚举性。

注意:首先基本数据类型会被包装成对象,null和undefined会被忽略。其次只有字符串的包装对象才可能有自身可枚举属性。

const v1 = "abc"
const v2 = true
const v3 = 10
const v4 = Symbol("foo")
const target = Object.assign({}, v1, null, v2, undefined, v3, v4)
console.log(target)
// {0: "a", 1: "b", 2: "c"}

拷贝一个数组。该方法会把数组视为对象,同时在拷贝的时候通过位置来进行覆盖。

const target = Object.assign([1,2,3],[4,5])
console.log(target)
// [4, 5, 3]
  • 深浅拷贝

Object.assgin()实现的是浅拷贝。如果源对象中的某个属性的值也是对象,那么目标对象拷贝得到的是这个对象的引用,一旦这个对象发生改变,那么拷贝后的目标对象也做相应的改变。

let obj1 = { a: 0 , b: { c: 0}}
let obj2 = Object.assign({}, obj1)
console.log(JSON.stringify(obj2))
// {"a":0,"b":{"c":0}}
obj1.a = 1
console.log(JSON.stringify(obj1))
// {"a":1,"b":{"c":0}}
console.log(JSON.stringify(obj2))
// {"a":0,"b":{"c":0}}
obj2.a = 2
console.log(JSON.stringify(obj1))
// {"a":1,"b":{"c":0}}
console.log(JSON.stringify(obj2))
// {"a":2,"b":{"c":0}}
obj1.b.c = 3
console.log(JSON.stringify(obj1))
// {"a":1,"b":{"c":3}}
console.log(JSON.stringify(obj2))
// {"a":0,"b":{"c":3}}

至于深浅拷贝的区别以及如何实现的问题,会在之后的文章中详细说明。

常见用途

  • 为对象添加属性
class Person {
  constructor(x, y) {
    Object.assign(this, {x, y})
  }
}
  • 为对象添加方法
Object.assign(someClass.prototype, {
  foo(x, y){
    ....
  }
})
  • 合并多个对象
Object.assign(target, ...sources)
  • 复制一个对象
const target = Object.assign({}, { a: 1 })
console.log(target)
// {a: 1}
  • 为属性指定默认值
const DEFAULT_VALUE = {
  name: 'Joe',
  age: '27'
}
function foo(options) {
  return Object.assign({}, DEFAULT_VALUE, options)
}

参考链接:https://juejin.im/post/5c96f5...

Object.create()
Object.defineProperty()
Object.defineProperties()
Object.entries()
Object.preventExtensions()
Object.isExtensible()
Object.seal()
Object.isSealed()
Object.freeze()
Object.isFrozen()
Object.keys()
Object.values()
Object.getPrototypeOf()
Object.getOwnPropertyNames()
Object.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptors()

Object实例方法

实例方法就是定义在 构造函数原型(prototype)上的方法

Object.prototype.hasOwnProperty()
Object.prototype.isPrototypeOf()
Object.prototype.propertyIsEnumerable()
Object.prototype.toString()
Object.prototype.valueOf()

1. ES5

类的声明

let Person = function (name) {
    this.name = name;
    this.sayHi = function () {
        console.log(`姓名 ${this.name}`);
    }
};

类的继承

1.借助构造函数实现继承

原理:将父级元素的this,指向子构造函数的实例上。
缺点:父类原型对象上的东西是无法被子类继承的。
let Person = function (name) {
    this.name = name;
    this.sayHi = function () {
        console.log(`姓名 ${this.name}`);
    }
};
Person.prototype.sayName = function () {

};
let Child = function (name, grade) {
    Person.call(this, name);
    this.grade = grade;
};
let ch1 = new Child("tom", 3);
console.log(ch1);

2.借助原型链实现继承

原理:将实例对象的引用指向同一个原型对象
缺点:修改一个实例的属性,会反映到原型对象上。
function Person() {
    this.arr = [1, 2, 3];
}

function Child() {
    this.type = 'child';
}

Child.prototype = new Person();

let c1 = new Child();
let c2 = new Child();
console.log(c1.arr, c2.arr); //[ 1, 2, 3 ] [ 1, 2, 3 ]
c1.arr.push(4);
console.log(c1.arr, c2.arr); //[ 1, 2, 3, 4 ] [ 1, 2, 3, 4 ]

3.组合继承

原理:将构造函数继承与原型链继承结合
缺点:每实例化一个对象,父类构造函数执行两次。
function Person() {
    this.arr = [1, 2, 3];
}

function Child() {
    Parent.call(this);
}

Child.prototype = new Person();
let s3 = new Child();
let s4 = new Child();
s3.arr.push(4);
console.log(s3.arr, s4.arr);

3.组合继承优化2
缺点:实例的原型对象是父类的prototype

function Person() {
    this.arr = [1, 2, 3];
}

function Child() {
    Person.call(this);
}

Child.prototype = Person.prototype;
let c1 = new Child();
console.log(c1 instanceof Child, c1 instanceof Person); //true true
console.log(c1.__proto__); //Person {}

4.组合继承优化2

function Person() {
    this.arr = [1, 2, 3];
}

function Child() {
    Person.call(this);
}

Child.prototype = Object.create(Person.prototype);
let c1 = new Child();
console.log(c1 instanceof Child, c1 instanceof Person); //true true
console.log(c1.__proto__); //Person {}

2. ES6

类的声明

使用 class 定义类,使用 constructor 定义构造函数。通过 new 生成新实例的时候,会自动调用构造函数。ES6 中实例属性只能通过构造函数中的 this.xxx 来定义。在ES6中没有静态属性(ES7中有),只有静态方法。

class Person {
    constructor(name) {
        this.name = "person";//实例属性name
    }

    sayHi() {
        console.log(`姓名 ${this.name}`);
    }
}
let p1 = new Person("Tom");
console.log(p1.sayHi());

类的继承

使用 extends 关键字实现继承,子类中使用 super 关键字来调用父类的构造函数和方法。子类构造函数中的this必须在super()之后调用。~

new运算符
1.创建一个空对象{}。
2.构造函数执行。传入相应的参数,this被指定为当前实例。
3.如果构造函数返回了一个“对象”,那么这个对象会取代整个new出来的结果。如果构造函数没有返回对象,则返回**this**

实例方法和静态方法
静态方法的定义需要使用static关键字来标识,通过类名来的调用。​实例方法通过实例对象来调用。

// 父类
class People {
    constructor(name) {
        this.name = name
    }
    eat() {
        console.log(`${this.name} eat something`)
    }
}

// 子类
class Student extends People {
    constructor(name, number) {
        super(name)
        this.number = number
    }
    sayHi() {
        console.log(`姓名 ${this.name} 学号 ${this.number}`)
    }
}

// 子类
class Teacher extends People {
    constructor(name, major) {
        super(name)
        this.major = major
    }
    teach() {
        console.log(`${this.name} 教授 ${this.major}`)
    }
}

// 实例
const xialuo = new Student('夏洛', 100)

存取器
使用 gettersetter 可以改变属性的赋值和读取行为:

class People {
    constructor(name) {
        this.name = name;
    }
    get name(){
        return  'Jack';
    }
    set name(value){
        console.log('setter: '  + value);
    }
}

let a =  new  Animal('Kitty');  // setter: Kitty
a.name =  'Tom';  // setter: Tom
console.log(a.name);  // Jack

3. ES7

实例属性

ES6 中实例的属性只能通过构造函数中的 this.xxx 来定义,ES7 提案中可以直接在类里面定义:

class Animal {
    name = 'Jack';

    constructor() {
        // ...
    }
}

let a = new Animal();
console.log(a.name); // Jack

静态属性

ES7 提案中,可以使用 static 定义一个静态属性:

class Animal {
    static num = 42;

    constructor() {
        // ...
    }
}

console.log(Animal.num); // 42
阅读 325

推荐阅读