原型与原型链
原型:构造函数的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
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
原型链
原型链的问题
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)
存取器
使用 getter
和 setter
可以改变属性的赋值和读取行为:
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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。