目录
JS对象
1.通过new Object()创建
2.工厂模式
3.构造函数模式
4.原型模式
5.组合使用构造函数与原型对象
6.动态原型模式
7. 寄生构造函数模式
8.稳妥构造函数模式
原型对象
原型链
继承
1.原型链继承
2.借用构造函数(经典继承 | 伪造函数)
3.组合继承
4.原型式继承
5.寄生式继承
class(ES6)
constructor 方法
prototype属性
取值函数(getter)与存值函数(setter)
class的其他定义方式
注意点
继承(ES6)
JS对象
js对象拥有属性和方法
1.通过new Object()创建
person=new Object();
person.firstname="John";
person.lastname="Doe";
person.age=50;
console.log(person)
// { firstname: 'John', lastname: 'Doe', age: 50 }
2.工厂模式
这种模式抽象了创建具体对象的过程,通过一个接口来创建对象 ;不能体现是谁创造的实例
function Person(name, age){
var o = new Object();
o.name = name;
o.age = age;
return o;
}
var person1 = Person('lhf', 25);
console.log(person1.name);
//不能判断实例的类型,实例继承了谁
console.log(person1 instanceof Person);
3.构造函数模式
与普通函数的区别,调用它们的方式不同;任何函数,只要通过new操作符来调用,那么就可以成为构造函数。
function Person(name, age){
this.name = name;
this.age = age;
this.getName = function(){
console.log(this.name)
};
}
var person1 = new Person('lhf', 25);
console.log(person1.name) // { name: 'lhf', age: 25 }
console.log(person1 instanceof Person); //true,可以体现实例来自哪里
person1对象有一个构造属性,该属性指Person
console.log(person1.constructor === Person); // true
与工厂模式的不同是:
没有在函数中创建对象,通过new操作符创建;
没有return语句,直接将属性和方法赋值给this;
工厂模式不能体现是谁创造的实例,构造函数可以(优点)
使用new操作符,经历的步骤:
创建一个新对象;
将构造函数的作用域赋值给新对象,即将this指向新创建的对象obj;
执行构造函数中的代码,为这个新对象添加属性;
返回新对象;
缺点:
每个方法都要在每个实例上重新创建一遍
当我们把方法移到外面时,属性getName是一个指向函数的指针,因此实例就共享了全局作用域中的函数。
问题好像是解决了,但又产生了新的问题:如果要定义很多方法,那么就要定义很多全局函数,这样就无法封装。
function Person(name, age){
this.name = name;
this.age = age;
this.getName = getName; // 存放的是一个指向函数的指针
}
function getName(){
console.log(this.name)
};
4.原型模式
原型模式指我们创建的每个函数都有一个原型(prototype)属性,这个属性是一个指针,指向一个对象,而这个对象包含所有实例共享的方法和实例。
使用原型的好处:让所有对象共享它所包含的原型和方法
原型对象的缺点:
- 它省略了构造函数传参这一环节,导致所有实例在默认情况下的属性值相同;
- 对于共享的引用类型(函数),当一个实例改变引用类型的值时,其他实例也会改变;
function Person() {
}
Person.prototype.name = "lhf";
Person.prototype.age = 24;
Person.prototype.getName = function(){
console.log(this.name);
}
// 通过new创建一个实例
var person1 = new Person();
console.log(person1.name+mine.age); // 输出 lhf24
person1.getName(); //lhf
建议此时先看(原型对象)
5.组合使用构造函数与原型对象
优点:每个实例可以拥有属于自己的属性值,同时也可以通过原型属性,拥有公共的属性值
function Person(name, age) {
this.age=age;
this.name=name;
}
Person.prototype.location = '中国';
var person1 = new Person('lhf', 24);
var person2 = new Person('zuzu', 24);
console.log(person1.name+' '+person1.location);
console.log(person2.name+' '+person2.location);
// lhf 中国
// zuzu 中国
6.动态原型模式
把构造函数模式与原型模式合二为一,location只在初次调用构造函数时执行一次
function Person(name, age) {
this.age=age;
this.name=name;
console.log(this.location);
if(this.location == undefined){
Person.prototype.location = '中国';
}
}
var person1 = new Person('lhf', 24); //undefined
var person2 = new Person('zuzu', 24); //中国
7. 寄生构造函数模式
这种模式的思想是创建一个函数,仅仅用来封装创建对象的代码。该模式返回的对象与构造函数没有关系。
与工厂模式的不同点,使用new操作符,并且把包装的函数叫做构造函数
function Person(name, age){
var o = new Object();
o.name = name;
o.age = age;
return o;
}
var person1 = new Person('lhf', 25);
console.log(person1.name);
8.稳妥构造函数模式
稳妥推向,指的是没有公共属性,而且其方法也不引用this的对象,可以防止数据被其他应用程序改动。与寄生构造函数模式类似,但是有两点不同:1.创建对象的实例方法不引用this 2.不使用new操作符调用构造函数。
function Person(name, age){
var o = new Object();
o.getName = function(){
return name;
}
return o;
}
var person1 = new Person('lhf', 25);
console.log(person1.getName()); //lhf
原型对象
- 无论什么时候,只要创建了一个新函数,就会同时创建一个prototype属性(指针),指向函数的原型对象;
- 默认情况下,所有原型对象都会获取一个constructor(构造函数)属性(指针),指向prototype属性所在函数;通过constructor,我们可以知道该原型对象引用于哪个构造函数;
- 实例都包含一个内部指针__proto__,指向原型对象
function Person() {
}
//添加原型属性及方法
Person.prototype.name = "lhf";
Person.prototype.age = 24;
Person.prototype.getName = function(){
console.log(this.name);
}
var person1 = new Person();
// 1、3
console.log(Person.prototype); //{ name: 'lhf', age: 24, getName: [Function (anonymous)] }
console.log(person1.__proto__); //{ name: 'lhf', age: 24, getName: [Function (anonymous)] }
console.log(Object.getOwnPropertyNames(Person.prototype)); //[ 'constructor', 'name', 'age', 'getName' ]
// 2 通过constructor,我们可以知道该原型对象引用于哪个构造函数
console.log(Person.prototype.constructor === Person); // true
// 重写原型,无constructor
// 表示把Person函数的Prototype指针指向了另一个地址,原来的地址只被Person1引用
Person.prototype = {
name: "zuzu",
age: 12,
getName: function () {
console.log(this.name);
}
};
console.log(Person.prototype.constructor === Person); // false
console.log(Object.getOwnPropertyNames(Person.prototype)); //[ 'name', 'age', 'getName' ]
console.log(Person.prototype); //{ name: 'zuzu', age: 12, getName: [Function: getName] }
console.log(person1.__proto__); //{ name: 'lhf', age: 24, getName: [Function (anonymous)] }
使用Object.getPrototypeOf() 获取这个实例的原型;
var person1 = new Person();
console.log(Person.prototype) //{ name: 'lhf', age: 24, getName: [Function (anonymous)] }
console.log(Object.getPrototypeOf(person1) === Person.prototype); //true
当读取到某个实例的属性时,先从对象本身开始查找,如果实例中找不到,则继续搜索指针指向的原型对象;使用delete操作符可以完全删除实例属性。
var person1 = new Person();
console.log(person1.name); //lhf ,在原型上搜索的结果
person1.name = 'zuzu';
console.log(person1.name); // zuzu,在实例上已经搜索到属性的值,不会往原型上搜索
delete person1.name;
console.log(person1.name); //lhf
hasOwnProperty()可以检测一个实例属性是否存在某个实例中,而非原型; property :属性
var person1 = new Person();
console.log(person1.hasOwnProperty("name")); //false, 此时访问的是原型属性
person1.name = 'zuzu';
console.log(person1.hasOwnProperty("name")); //true
in操作符、for-in可以同时访问实例属性和原型属性
var person1 = new Person();
console.log('name' in person1); //true
person1.name = 'zuzu';
console.log("name" in person1); //true
ES6, 类的内部所有定义的方法,都是不可枚举的(enumerable = false);
Object.getOwnPropertyNames()
- Person.prototype:适用于ES5、ES6的类 (Person.prototype也可以看最一个对象,object);获取原型的所有属性;无论是否可枚举,但不适用于私有属性;
- object: 对于普通的实例对象,只能获取实例属性
Object.keys()
- Person.prototype:获取ES5原型属性key( ES5的原型是可以枚举的,ES6类的内部所有定义的方法,都是不可枚举的);
- object:获取实例可枚举的自身属性
function Person(name) {
this.name = name;
}
Person.prototype.age = 24;
Person.prototype.getName = function(){
console.log(this.name);
}
//输出: [ 'age', 'getName']
console.log(Object.keys(Person.prototype));
//输出: [ 'constructor', 'age', 'getName' ]
console.log(Object.getOwnPropertyNames(Person.prototype));
//ES6
class Point {
constructor(x, y) {
}
toString() {
}
}
// [],ES6的方法都在原型上,是不可枚举的
console.log(Object.keys(Point.prototype));
//[ 'constructor', 'toString' ]
console.log(Object.getOwnPropertyNames(Point.prototype));
var my_obj = Object.create({}, {
getFoo: {
value: function() { return this.foo; },
enumerable: false
}
});
my_obj.foo = 1;
console.log(Object.getOwnPropertyNames(my_obj).sort()); // ["foo", "getFoo"]
console.log(Object.keys(my_obj)); //[ 'foo' ]
for (let i in my_obj){ //foo
console.log(i);
}
ES5,下面是原型的另一种赋值方法(Person函数同上),唯一不同的是,constructor不再指向Person了,因为下面写法本质上是重写了prototype,constructor已经无法确定对象的类型了,指向的是Object
Person.prototype = {
age:24,
getName:function(){
console.log(this.name);
}
}
//[ 'age', 'getName' ]
console.log(Object.getOwnPropertyNames(Person.prototype));
let person1 = new Person();
console.log(person1.constructor == Person); //false
console.log(person1.constructor == Object); //true
原生对象(Object、String、Array等)的都在其构造函数的原型上定义了方法,通过原生对象的原型修改或添加一些方法
console.log(typeof Array.prototype.sort); //function
console.log(typeof String.prototype.substring); //function
原型链
JS 原型链,原型链的顶端是什么?
Object.prototype
Object 的原型是什么?
console.log(Object.getOwnPropertyNames(Object.prototype));
/*
[
'constructor',
'hasOwnProperty',
'toLocaleString',
'isPrototypeOf',
'propertyIsEnumerable',
'toString',
'valueOf',
'__lookupGetter__',
'__lookupSetter__',
'__defineGetter__',
'__defineSetter__',
'__proto__'
]
*/
继承
1.原型链继承
原型链是作为实现继承的主要方法
- 下面代码实现了XiaoMing继承Person,通过创建Person实例,并将该实例赋值给XiaoMing.prototype实现的,实现的本质是重写原型对象(所以constructor指针消失)。
- XiaoMing原型指向了Person原型,并且也拥有自己的原型方法(getName),实现了对Peron属性与方法的继承。
- 关于age属性为什么会出现在XiaoMing原型中,因为age是Person的实例属性,而XiaoMing.prototype又指向Person的实例(包含age),所以age存在于XiaoMing的原型中。
function Person(){
this.age=[0, 1, 2];
}
Person.prototype.getAge = function(){
return this.age;
}
//Person原型 [ 'constructor', 'getAge' ]
console.log(Object.getOwnPropertyNames(Person.prototype));
function XiaoMing(){
this.name='xiaoming';
}
//继承了Person,构造函数指针指向了Person的原型对象
XiaoMing.prototype = new Person();
XiaoMing.prototype.getName = function(){
return this.name;
}
//XiaoMing原型 [ 'age', 'getName' ]
console.log(Object.getOwnPropertyNames(XiaoMing.prototype));
//XiaoMing.prototype.constructor也指向Person
console.log(XiaoMing.prototype.constructor); //Function: Person
var a = new XiaoMing();
console.log(a.getAge());
默认的继承
所有的引用类型都默认继承了Object,而这个继承也是通过原型链实现的,所有函数的默认原型都是Object的实例。因此默认原型搜会包含一个内部指针,指向Object.prototype.
确定原型与实例的关系
instanceof,A instanceof B == true,说明A是B的实例,B可能是直接对象或继承对象
isPrototypeof, B.prototype.isPrototypeof(A) == true, 同上
原型链的问题
- 从前面介绍,我们知道实例会共享原型的引用类型的值(可以参考原型模式),当一个函数继承了另一个函数,如上面的例子,age实例属性,相当于它是XiaoMing原型的引用类型, 它的值会被XiaoMing的所有实例共享。
- 在创建子类型实例时,无法向超类型的构造函数传递参数
2.借用构造函数(经典继承 | 伪造函数)
主要思想:在子类型的构造函数内部,调用超类型构造函数,一般使用call()和apply()方法。
优点:子类可向超类传递参数
缺点:子类不能继承超类的原型
function Parent() {
this.age = [0, 1, 2, 10];
}
Parent.prototype.name = "hha";
function Son() {
Parent.call(this);
}
var kk = new Son();
for (let i in kk) { //只输出 age,说明超类中的原型未被继承
console.log(i);
}
console.log(Object.getPrototypeOf(kk)); //{}
3.组合继承
主要思想:使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承
两种方法的继承!!!
function Parent() {
this.age = [0, 1, 2, 10];
}
Parent.prototype.name = "hha";
function Son() {
//继承Parent的实例属性
Parent.call(this);
}
//继承Person的原型属性及方法
Son.prototype = new Parent();
var kk = new Son();
for (let i in kk) { //输出 age、name
console.log(i);
}
console.log(Object.getPrototypeOf(kk)); //Person { age: [ 0, 1, 2, 10 ] }
这样就达到了,让两个实例,既有自己的实例属性(age属性),又有公共的原型属性
var kk = new Man();
var uu = new Man();
kk.age.push(100);
console.log(kk.age); //[ 0, 1, 2, 10, 100 ]
console.log(uu.age); //[ 0, 1, 2, 10 ]
原型链的继承思想:
每个实例对象都有(指向)自己的原型,而每个实例对象可以作为子类的原型对象,这样就实现了继承。当创建子类的实例对象时,实例对象就有了自己的实例属性(age属性),又有公共的原型属性
实例对象可以作为子类的原型对象
Son.prototype = new Parent();
相当于重写了子类的原型,所以子类的原型对象的constructor指向的是Parent
console.log(Son.prototype.__proto__.constructor == Parent ) //true
console.log(Son.prototype.constructor == Parent )
应用:vue的全局挂载
4.原型式继承
主要思想:借助原型,可以基于已有的对象创建新对象。
在object函数内部,先创建一个临时的构造函数,将传入的对象作为这个构造函数的原型,返回这个临时构造函数的实例。
function object(o){
function F(){}
F.prototype = o;
return new F();
}
var person = {
name: 'haha',
age: [0, 1, 5]
}
var person1 = object(person);
person1.age.push(11);
var person2 = object(person);
console.log(person2.age); //[ 0, 1, 5, 11 ]
ES5用Object.create()规范了原型式继承 ,Object.create() = object() ,第一个参数是传入的对象,第二个参数的每个属性都是通过自己的描述定义的
var person = {
name: 'haha',
age: [0, 1, 5]
}
var person1 = Object.create(person, {
name: {
value: "zuzu"
}
});
console.log(person1.name); //zuzu
5.寄生式继承
与寄生构造函数模式类似,即创建一个仅用于封装继承过程的函数
class(ES6)
创建实例与es5完全一样,通过new创建
class Point {
// constructor 构造方法
constructor(x, y) {
this.x = x;
this.y = y;
}
// 添加方法,省略了function
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
obj = new Point(1, 2);
console.log(obj.toString()); // (1, 2)
constructor 方法
是类的默认方法,通过new命令创建实例时,自动添加该方法,constructor方法默认返回该实例对象(this),构造方法里的属性都是实例属性,即是每个实例所特有的,不共享。
下面的代码表明,类的数据类型就是函数,类本身就指向构造函数(constructor)
class Point {
// constructor 构造方法
constructor(x, y) {
this.x = x;
this.y = y;
}
}
console.log(typeof(Point)); //function
console.log(Point === Point.prototype.constructor); //true
prototype
属性
在 ES6 的“类”上面继续存在,事实上,类的所有外部方法都定义在prototype原型上,如下面的toString,且都是不可枚举的
class Point {
constructor() {
}
toString() {
}
}
// 等同于
Point.prototype = {
constructor() {},
toString() {}
};
Object.assign
可以方便的一次性添加多个类函数
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
//一次性添加多个函数
Object.assign(Point.prototype, {
add(){
return this.x+this.y;
},
sub(){
return this.x-this.y
}
});
P = new Point(1, 2);
console.log(P.add()); //3
console.log(P.sub()); //-1
取值函数(getter)与存值函数(setter)
与 ES5 一样,在“类”的内部可以使用get
和set
关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为
class MyClass {
constructor() {
// ...
}
get prop() {
return "getter";
}
set prop(value) {
console.log("setter: " + value);
}
}
let inst = new MyClass();
inst.prop = 123;
// setter: 123
console.log(inst.prop);
// 'getter
class的其他定义方式
Point类名只在类的内部可用,如果内部没用到,可以省略类名Point;创建对象还需要用MyPoint
const MyPoint = class Point{
constructor(name){
this.name = name;
}
getName(){
console.log(Point.name) // Point
return this.name
}
}
var obj = new MyPoint('lhf');
console.log(obj.getName()); //lhf
可以写出立即执行的 Class
注意点
类需要先定义,再调用,子类需要写在父类的后面
类中this的指向
方法中的this,默认指向类的实例
静态方法
类的内部函数加上static就是静态方法,代表着不会被实例继承,只有通过类名调用;
如果静态方法中包含this,这个this指向的是类而不是实例;
子类可以继承父类的静态方法,可直接被子类调用
类的实例属性
1.定义在constructor函数的this上面; 1.直接定义在类的最上面,不用添加this
类的私有方法
方法一:_+函数名,声明为私有方法仅供内部使用,但是在类的外部还是可以调用的
方法二:把方法放到类的外部,在类的内部调用函数,达到私有方法的目的
方法三:利用Symbol
值的唯一性
//方法一
class Widget {
foo (name) {
this._setName(name);
}
_setName(name) {
return this.name = name;
}
}
//方法二
class Widget {
foo (name) {
setName.call(this, name);
}
}
function setName(name) {
return this.name = name;
}
let obj = new Widget();
obj.foo('haha');
console.log(obj.name); //haha
类的私有属性
#+属性名,即是私有属性; #+方法名,即是私有方法;外部调用会出错
class Counter{
#count = 0;
increment(){
this.#count++;
}
print(){
console.log(this.#count);
}
#log(){
console.log(this.#count);
}
}
const counter = new Counter();
counter.increment();
counter.print(); //1
counter.log(); //报错
判断实力是否存在某个私有属性,可以用in运算符在类的内部判断
new.target属性
new是构造函数生成实例的命令,ES6为new添加了一个属性new.target, 返回类名;子类继承父类时,new.target
会返回子类,可以利用这个特点,判断是否为继承后的类。
继承(ES6)
1.extent继承
ES5继承的实质(构造函数方式):先创建子类的实例对象this,再将父方法添加到this上面 Person.call(this);
ES6继承的实质:先创建父类的实例对象this(调用super()),再在子类的构造函数中修改this
有调用super
之后,才可以使用this
关键字
class Person{
constructor(){
this.age = [1, 10];
}
}
class Man extends Person{
constructor(name){
super(); //调用父类
this.name = name;
}
getName(){
return this.name;
}
}
let kk = new Man('kk');
console.log(kk.getName());
2.Object.getPrototypeOf()
获取子类的父类
3.super()
子类继承父类时使用super(), 代表调用父类的构造函数,但是返回的是子类B
的实例,即super
内部的this
指的是B
的实例
class A {
constructor() {
console.log(new.target.name);
}
}
class B extends A {
constructor() {
super();
}
}
new A() // A
new B() // B
super作为对象时,在普通函数中,指向父类的原型。所以super.p()就相当于A.prototype.p(),所以super对象不能调用父类实例上的方法或属性。在静态方法中,指向父类
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
ES6 规定,在子类普通方法中通过super
调用父类的方法时,方法内部的this
指向当前的子类实例;
在子类的静态方法中通过super
调用父类的方法时,方法内部的this
指向当前的子类,而不是子类的实例
class A {
constructor() {
this.x = 1;
}
print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
m() {
super.print(); //this指向当前子类实例
}
}
let b = new B();
b.m() // 2
class的两条继承链