一,面向对象
- 面向过程: 就是分析出解决问题所需要的步骤,然后用变量,函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
- 面向对象编程 => 思想上提升, 就是将你的需求抽象成一个对象,然后针对这个对象分析其特征(属性)与行为(方法)--这个对象就称之为类(面向对象)
- 最简单的面向对象(字面量对象): 将面向过程中的变量和方法整合到对象中,作为对象的属性和方法
- 函数封装(简易工厂模式) => 可以根据传入的参数,快速得到想要的对象
- 构造函数:构造函数也是一个函数, 配合new关键词创建实例化对象 获取元素,返回获取的元素组成的集合(伪数组 -> 实例化对象)
普通函数封装的缺点:
(1) 通过函数cat创建的对象,和函数本身并无任何关系 (Cat不是构造函数)
var arr = new Array(1, 2, 3);
console.log(arr instanceof Array);
var obj = new Object({ a: 1, b: 2 });
console.log(obj instanceof Object);
(2) 公共的属性和方法重复创建 =>占内存
分析:
(1) 如果想让创建的对象和函数有关联, 一般用构造函数 (创建实例化对象)
(2) 对比发现,构造函数也是一个函数, 配合new关键词创建实例化对象
构造函数:
(1) 官方提供的构造函数: Array() Object() Date() Promise() Error() Set() Map()
(2) 用户自定义构造函数: new Xxx();
普通函数
function fn() {
console.log("函数fn执行了", this); window
}
var result = fn();
console.log("result", result); undefined
二,new 关键词做了哪些操作
(1) 创建一个纯净的实例化对象 {}
(2) 给创建的实例化对象绑定原型属性([[Prototype]])指向构造函数的原型对象(构造函数.prototype),所有的(该构造函数创建的)实例化对象都可以通过此属性访问构造函数的原型对象
(3) this -> {} 强制将构造函数内的this指向本次构造函数执行过程中创建的实例化对象 (通过this给实例化对象绑定数据和方法)
(4) return this 返回被创建的实例化对象
原型对象(prototype)
构造函数上存在原型对象(prototype),官方提供,致力于将实例化对象中公共的属性和方法提取出来,单独存储,允许当前构造函数创建的所有实例化对象访问
Cat.prototype = {constructor}
console.dir(Cat); 以键值对形式打印对象(函数也是一个特殊的实例化对象)的属性和方法
三,constructor 构造器属性
(1) 构造函数创建时默认配置, 存储在构造函数的原型对象上,指向构造函数本身
Cat.prototype = {constructor:Cat}
(2) 所有的实例化对象都可以通过原型属性[[Prototype]],访问构造函数的原型对象(prototype) => 所有的实例化对象均可访问构造器属性, 返回创建它的构造函数
小结: constructor默认存储在构造函数的原型对象上, 允许所有的实例化对象均可访问构造器属性, 返回创建它的构造函数
注意:
(1) 为了区分普通函数和构造函数,建议构造函数首字符大写
(2) 每次调用构造函数都会重新执行构造函数的上下文,返回被创建的实例化对象
(3) 构造函数中的this, 指向本次构造函数执行过程中创建的实例化对象
证明: 实例化对象都存在原型属性([[Prototype]]),指向创建该实例化对象的构造函数的原型对象(构造函数.prototype)
[[Prototype]] 原型属性(私有属性),只能通过官方给的方法获取和设置
如何访问构造函数原型对象(prototype)上的公共属性和方法?
(1) 实例化对象自己有,先访问自己的
(2) 实例化对象自己没有,则访问(创建该实例化对象的)构造函数的原型对象(prototype)
老版本: 如何获取原型属性?
_ _proto_ _ 原型属性(未来会被弃用)
console.log(Tom.__proto__ === Cat.prototype);
console.log(Kitty.__proto__ === Cat.prototype);
console.log(Kitty.__proto__ === Tom.__proto__);
新版本: 如何获取原型属性?
Object.getPrototypeOf(target) 传入一个实例化对象,返回该实例化对象原型属性的指向([[Prototype]])
Object.setPrototypeOf(target, proto) 传入一个实例化对象和原型对象(构造函数,prototype),设置该实例化对象原型属性的指向([[Prototype]])
结论:实例化对象都存在原型属性([[Prototype]]),指向创建该实例化对象的构造函数的原型对象(构造函数.prototype)
构造函数
function Fn() {
// {}
// this -> {}
console.log("函数Fn执行了", this); //this -> {}
//给实例化对象绑定属性和方法(用户自己决定)
this.a = 1;
this.b = 2;
this.sum = function () { // 函数sum中的this(实例化对象)
console.log(this.a + this.b);
}
return this; // 返回被创建的实例化对象
}
var result = new Fn(); //调用构造函数,执行构造函数上下文,返回被创建的实例化对象 => 赋值给变量result
console.log("result", result); {}
var result = new Fn(); //构造函数调用
console.log("result", result); {}
四,原型: [[Prototype]] 原型对象 | 隐式原型 | 原型
实例化对象上存在原型属性( [[Prototype]]),指向创建该实例化对象的构造函数的原型对象
实例化对象[[Prototype]] => 构造函数.prototype
实例化对象访问属性和方法的顺序?
(1) 自己有就访问自己的 => 实例化对象自己的属性
(2) 自己没有,就顺着原型[[Prototype]],访问创建该实例化对象的构造函数的原型对象
(3) 如果构造函数的原型对象上找不到(Cat.prototype),继续顺着原型属性找构造函数Object.prototype!
(4) 如果Object.prototype上找不到, 就默认返回undefined,并不在向后查找
Tom -> Tom.__proto__(Cat.prototype) => Cat.prototype.__proto__(Object.prototype) => Object.prototype.__proto__(null)
Tom -> Cat.prototype -> Object.prototype -> null
(1) 自己有就访问自己的 => 实例化对象自己的属性
console.log(Tom.name, Tom.color);
(2) 自己没有,就顺着原型[[Prototype]],访问创建该实例化对象的构造函数的原型对象(Cat.prototype)
Tom.__proto__ => Cat.prototype
console.log(Tom.species);
console.log(Tom.skill);
console.log(Tom.hall);
console.log(Tom.say);
Tom.say();
(3)
如果构造函数的原型对象上找不到(Cat.prototype),怎么办?
分析: 构造函数的原型对象本质也是一个对象, 它是由上级构造函数Object创建
证明:
console.log(Cat.prototype);
看构造函数的原型对象 的 原型属性指向 (老版本的写法)
console.log(Cat.prototype.__proto__ === Object.prototype);
console.log(Object.getPrototypeOf(Cat.prototype) === Object.prototype);
结论:如果 构造函数的原型对象上找不到(Cat.prototype),继续顺着原型属性找构造函数Object.prototype!
Cat.prototype.__proto__ => Object.prototype
console.log(Tom.a);
console.log(Tom.b);
(4)
如果Object.prototype上也找不到想要的属性和方法,怎么办?
类推法: Object.prototype本质也是一个对象, 它是由上级构造函数Object创建
=> Object.prototype上找不到, 继续顺着原型属性找构造函数Object.prototype!
矛盾: 如果Object.prototype上找不到,会一直沿着Object.prototype查找 => 死查找
结论: 如果Object.prototype上找不到, 就默认返回undefined,并不在向后查找
console.log(Object.prototype.__proto__ === null);
五,Object.prototype上的方法:
valueOf() 返回实例化对象本身
var num = 100;
var str = "hello"
var bool = true;
var arr = [1, 2, 3];
var obj = { a: 1, b: 2 };
console.log(num.valueOf()); //100
console.log(str.valueOf()); //hello
console.log(bool.valueOf()); //true
console.log(arr.valueOf()); //[1,2,3]
console.log(obj.valueOf()); //{a: 1, b: 2}
console.log(Tom.valueOf()); //Tom is not defined
isPrototypeOf 判断 某个构造函数的原型对象 是否存在于 某个实例化对象的原型链上
自实例化对象Tom起的原型链: Tom -> Cat.prototype -> Object.prototype -> null
console.log(Cat.prototype.isPrototypeOf(Tom)); true
console.log(Object.prototype.isPrototypeOf(Tom)); true
console.log(Array.prototype.isPrototypeOf(Tom)); false
拓展
instanceof 判断 某个实例化对象 的原型链上,是否存在 某个构造函数 的原型对象
console.log(Tom instanceof Cat); true
console.log(Tom instanceof Object); true
console.log(Tom instanceof Array); false
hasOwnProperty() 判断某个实例化对象上,是否存在某个属性 (只查找实例化对象本身,不查找原型链)
Object.hasOwn() 判断某个实例化对象上,是否存在某个属性 (只查找实例化对象本身,不查找原型链)
//所有实例化对象均可访问 Object.prototype的方法
console.log(Tom.hasOwnProperty("name")); true
console.log(Tom.hasOwnProperty("color")); true
console.log(Tom.hasOwnProperty("species")); false
console.log(Tom.hasOwnProperty("a")); false
console.log(Tom.hasOwnProperty("b")); false
构造函数Object的静态方法
console.log(Object.hasOwn(Tom, "name")); true
console.log(Object.hasOwn(Tom, "color")); true
console.log(Object.hasOwn(Tom, "species")); false
console.log(Object.hasOwn(Tom, "a")); false
console.log(Object.hasOwn(Tom, "b")); false
in 语法 判断 某个属性 是否存在于 某个实例化对象 的原型链上
for (var key in Tom) {
console.log(key);
}
console.log("name" in Tom); true
console.log("color" in Tom); true
console.log("species" in Tom); true
console.log("a" in Tom); true
console.log("b" in Tom); true
console.log("hello" in Tom); false
propertyIsEnumerable() 判断某个实例化对象上的某个属性,是否可以被枚举(遍历) => 只查找实例化对象本身
console.log(Tom.propertyIsEnumerable("name")); true
console.log(Tom.propertyIsEnumerable("color")); true
console.log(Tom.propertyIsEnumerable("species")); false
console.log(Tom.propertyIsEnumerable("a")); false
console.log(Tom.propertyIsEnumerable("b")); false
六,面向对象继承:
继承
让 一个类(构造函数)继承 另一个类(构造函数) 的 属性和方法(实例化对象的属性和方法 和 原型对象prototype上的属性和方法) 或者重新定义、追加自己属性和方法等。
继承可以使子类具有父类的属性和方法或者重新定义、追加属性和方法等
被继承类(构造函数) 父类
继承的类 子类
原型继承: 让子类继承父类原型对象上的属性和方法
a. 拷贝继承 删除继承 中间件继承 Object.create()继承
b.直接用父类.prototype给子类.prototype赋值 (缺点:使用同一片内存地址 子类修改Prototype上的属性父类也会被修改 不建议使用)
结果: 子类创建的实例化对象可以访问父类的原型对象上的方法
缺点: 原型继承只能子类继承父类原型对象上的属性和方法, 不能继承父类构造函数中实例化对象的属性和方法
组合继承: 构造函数继承 + 原型继承
既能继承父类构造函数中实例化对象的属性和方法,也能让子类继承父类原型对象上的属性和方法
方法:
(1) 拷贝继承 (浅拷贝) => 遍历父类的原型对象,对应属性名将其拷贝子类的原型对象
缺点: 将父类的原型对象上的方法拷贝到子类, 重复创建 => 占内存
(2) 用父类的原型对象,覆盖子类的原型对象
yp -> YellowPerson.prototype(Person.prototype) -> Object.prototype -> null
缺点: 父类和子类的原型对象引用同一片内存地址,给子类添加方法,父类也会收到影响
(3) 用父类的实例化对象,覆盖子类的原型对象 => 拓宽了原型链
yp -> YellowPerson.prototype(父类的实例化对象 p) -> Person.prototype -> Object.prototype -> null
缺点: 用父类的实例化对象,覆盖子类的原型对象 => 本来只需要父类实例化对象的的[[Prototype]],多余了name age
(4) 删除继承(了解) => 删除父类实例化对象多余属性
(5) 寄生式继承(中间件继承)
(6) Object.create() 手搓版 (新建实例化对象,将其原型属性指向父类的原型对象)
// 父类
function Person(name, age) {
// 父类构造函数中this,指向父类在执行过程中创建的实例化对象
this.name = name;
this.age = age;
}
Person.prototype.species = "human";
Person.prototype.skill = function () {
console.log("thinking");
};
Person.prototype.intro = function () {
console.log(this.name, this.age);
};
// 子类
function YellowPerson(name, age, hobby) {
// 子类构造函数中this,指向子类在执行过程中创建的实例化对象
// {}
// {}[[Prototype]] = YellowPerson.prototype
// this->{}
// 想让子类继承父类构造函数中定义的, 实例化对象相关属性和方法?
// 调用父类构造函数,在函数执行过程中强制将this指向子类在执行过程中创建的实例化对象
Person.call(this, name, age); // 继承父类的
this.hobby = hobby; // 追加自己的
}
// 为什么子类创建的实例化对象不能访问父类原型对象上的属性和方法?
// 子类的原型链上不存在父类的原型对象(Person.prototype)
// p的原型链:
// p -> Person.prototype -> Object.prototype -> null
// yp的原型链:
// yp -> YellowPerson.prototype -> Object.prototype -> null
// (2) 用父类的原型对象,覆盖子类的原型对象
// yp -> YellowPerson.prototype(Person.prototype) -> Object.prototype -> null
// 缺点: 父类和子类的原型对象引用同一片内存地址,给子类添加方法,父类也会收到影响
// YellowPerson.prototype = Person.prototype; // (对象的赋值属于浅复制 => YellowPerson.prototype Person.prototype 引用同一片内存地址)
// YellowPerson.prototype.speak = function () {
// console.log("chinese");
// };
// (3) 用父类的实例化对象,覆盖子类的原型对象 => 拓宽了原型链
// yp -> YellowPerson.prototype(父类的实例化对象 p) -> Person.prototype -> Object.prototype -> null
// 缺点: 用父类的实例化对象,覆盖子类的原型对象 => 本来只需要父类实例化对象的的[[Prototype]],多余了name age
// YellowPerson.prototype = new Person("李四", 24);
/*
YellowPerson.prototype = {
name:"李四",
age:24,
[[Prototype]]: Person.prototype
}
*/
// 原理: 只需要父类实例化对象的的[[Prototype]],不需要name和age => 得到一个纯净的对象,其原型[[Prototype]]=> Person.prototype
// (4) 删除继承(了解) => 删除父类实例化对象多余属性
// var p1 = new Person("李四", 24);
// delete p1.name;
// delete p1.age;
// YellowPerson.prototype = p1;
// (5) 寄生式继承(中间件继承)
/* function Fn() {
// {}
// {}[[Prototype]] = Fn.prototype
// this -> {}
// return this;
}
Fn.prototype = Person.prototype;
// var p1 = new Fn(); // {[[Prototype]]:Fn.prototype(Person.prototype)}
// console.log(p1);
YellowPerson.prototype = new Fn(); */
// (6) Object.create() 手搓版 (新建实例化对象,将其原型属性指向父类的原型对象)
/* var p = new Object(); // {}
Object.setPrototypeOf(p, Person.prototype)
console.log("p", p);
YellowPerson.prototype = p; */
YellowPerson.prototype = Object.create(Person.prototype);
YellowPerson.prototype.speak = function () {
console.log("chinese");
};
console.dir(YellowPerson);
var yp = new YellowPerson("张三", 18, "唱歌");
console.log(yp);
// var p = new Person("张三", 18);
// console.log(p);
// console.log(p.speak);
面向对象之多态
多态: 通过不同类创建的实例化对象,在访问同一个方法,会返回不同的结果!
JS 无态,天生就支持多态