一、面向对象(OOP:Object Oriented Programming)
1. 课堂主题&课程目标
课程主题:
- 对象的创建
- 工厂模式
- new运算符
- 构造函数
- 原型prototype
- 构造函数继承
- 原型的继承
- 原型链
- 包装对象
- 面相对象和面相过程编程
- 类和对象概念
课程目标:
- 理解面相对象思想
- 会使用工厂模式
- 会使用new运算符
- 会使用构造函数
- 理解原型
- 理解原型链
- 会使用继承
- 理解类和对象
- 包装对象
2.面向对象编程思想(面向过程 & 面向对象)
面相过程: 注重解决问题的步骤,分析问题需要的每一步,实现函数依次调用;
面相对象:
- 是一种程序设计思想--将数据和处理数据的程序封装到对象中;
- 特性: 抽象、 继承、封装、多态;
- 优点:提高代码的复用性及可维护性;
对象:
- Javascript 是一种基于对象的语言,几乎所有东西都是对象;
- 对象创建方法:
-
- 字面量创建;
-
- new Object()创建;
-
- Object.create()创建:创建对象的原型;
3-0.工厂模式-类
工厂模式解决了代码复用的问题;
3-1.对象和类
对象:具体的某个事物(如:小明、叮当猫)
类:一类事物的抽象(如:人类、猫类)
new运算符的特点
- 1.new执行函数
- 2.自动创建空对象;
- 3.this绑定到空对象;
- 4.隐式返还this;
- 5.通过new来改造工厂模式
构造函数
- 构造函数要通过new来调用 this指向实例化对象
- 约定俗成构造函数首字母大写
- 静态属性及方法
- 静态方法里的this;
构造函数特点:
-
1.首字母大写;
-
2.this指向实例化对象; 【new的过程叫实例化( Person类里面每个对象)】
-
类属性:Person.num
-
实例属性:this.num
构造函数性能
- 公共空间存放公共方法
构造函数原型
let zhangsan = new Person(‘张三’) console.log(zhangsan); //执行实例化
console.log(zhangsan.__proto__ === Person.prototype) // true
Person.prototype = {
constructor:Person,
hobby:function(){
console.log("hobby");
}
}
//原型的固有属性:(系统自带)——原型上的constructor,指向我们的构造函数Person
console.log( Person.prototype.constructor===Person); //true
let zhangsan = new Person("张三");
console.log(zhangsan.constructor===Person);//true
console.log(zhangsan.__proto__===Person.prototype);//true
let lisi = new Person("李四");
3-2.prototype原型
- 通过new实例化出来的对象其属性和行为来自两个部分,一部分来自构造函数,另一部分来自原型。
- 当声明一个函数的时候,同时也申明了一个原型 。
- 原型本身是一个对象。
- 对象属性方法查找规则;
3-3.原型 & 构造函数 & 对象关系
3-4.工厂模式对比构造函数
1.但是却没有解决对象识别的问题。 即创建的所有实例都是Object类型。(不清楚是哪个对象的实例)
2.没有原型,占用内存。
3-5.原型链
对象之间的继承关系,在JavaScript中是通过prototype对象指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,称之为原型链;
1.当访问一个对象的属性或方法时,会先在对象自身上查找属性或方法是否存在, 如果存在就使用对象自身的属性或方法。 如果不存在就去创建对象的构造函数的原型对象中查找 ,依此类推,直到找到为止。 如果到顶层对象中还找不到,则返回 undefined。
2.原型链最顶层为 Object 构造函数的 prototype 原型对象,给 Object.prototype 添加属性或方法可以被除 null 和 undefined 之外的所有数据类型对象使用。
3-6.构造函数继承
call、apply、bind
function foo(*name*,*age*) {
console.log(*this*,"姓名是"+*name*+"年龄是"+*age*);
}
// foo(); 打印的this是:windows
let obj = {
name:"张三"
}
// foo.call(obj,"张三",20); 改变this指向为obj
// foo.apply(obj,["张三",20]); 改变this指向为obj
// foo.bind(obj)("张三",20);
注意:foo.bind(obj)返回的是一个函数,想要看到结果还需要执行一遍直接()即可
- 继承:子类 继承 父类所有属性和行为,父类不受影响。
- 目的:找到类之间的共性精简代码
function Person(name){
this.name = name;
this.eyes = "两只";
this.legs = "两条";
}
function Student(name){
Person.call(this,name)
this.className = "二班";
}
let newPerson = new Student("张三");
console.log(newPerson.className);
简单原型继承,出现影响父类的情况:
function Person(name){
this.name = name;
this.eyes = "两只";
this.legs = "两条";
}
function Student(name){
Person.call(this,name)
this.className = "二班";
}
Student.prototype = Person.prototype //直接赋值
3-7.原型
-
传值和传址问题
- 基本数据类型:Number、String、Boolean、Null、Undefined
- 复杂数据类型/引用数据类型:Array、Date、Math、RegExp、Object、Function等
-
JOSN序列化的不足
如果拷贝对象包含函数,或者undefined等值,此方法就会出现问题
-
浅拷贝和深拷贝
//递归深拷贝
function deepCopy(obj){
let newObj = Array.isArray(obj)?[]:{};
for(let key in obj){
if(obj.hasOwnProperty(key)){
if(typeof obj[key] == "object"){
newObj[key] = deepCopy(obj[key]);
}else{
newObj[key] = obj[key];
}
}
}
return newObj;
}
原型的继承
- 深拷贝继承
- 组合继承
function Dad(){
this.name = "张三";
}
Dad.prototype.hobby = function(){
console.log("喜欢篮球");
}
function Son(){
Dad.call(this);
}
let F = function(){}
F.prototype = Dad.prototype;
Son.prototype = new F();
Son.prototype.constructor = Son;
let newSon = new Son();
newSon.hobby();
传值和传址
原型-深拷贝&组合继承
深拷贝继承:
组合继承:
包装对象
- 除过null,undefined,基本类型都有自己对应的包装对象:String Number Boolean
- 包装对象把所有的属性和方法给了基本类型,然后包装对象消失
总结
- 1)面向对象编程
- 2)工厂模式
- 3)new运算符
- 3)构造函数
- 4)原型
- 5)面相对象和面相过程编程
- 6)类和对象
二、前端类及ESM模块化应用
1. 课堂目标&知识点
课堂目标:
- 理解并能使用ES6中的类
- 学会使用ES6中继承
- 理解静态属性及方法
- 会使用ES6中模块化
知识点:
- ES6中类的使用
- ES6中继承extends、super
- ES6静态方法和属性
- ES6中模块化import、export
- 王者荣耀英雄选择案例
2.ES6中的类
类的写法(ES5、ES6)
// ES5 —构造函数模拟’类‘的概念(方法写在原型上)
function Person(name){
this.name = name;
this.height = "178cm";
this.age = 20;
}
// 方法
Person.prototype.fn = function(){
console.log("fn");
}
let zhangsan = new Person("张三"); //new 实例化
class Person{
height="178cm";
constructor(name,age){
//属性
this.name = name;
this.age = age;
}
//方法
getName(){
console.log("姓名是:"+this.name);
}
}
let student = new Person("张三",20);
student.getName();
------------------------------------------------------
//ES6—class语法糖,区别于ES5的写法(方法写在constructor内部)
class Person {
//构造函数相当于上面ES5的Person
constructor(name) {
this.name = name;
this.height = "178cm";
this.age = 20;
}
fn(){
console.log("fn");
}
}
let zhangsan = new Person("张三");
let lisi = new Person("李四");
console.log(typeof Person); //function还是个函数
console.log(zhangsan); //打印结果如下图,__proto__内也有class Person
静态方法和属性:实例不会继承的属性和方法
class Person{
//静态方法
static hobby(){
console.log("喜欢篮球");
}
}
Person.height = "178cm"; //静态属性
Person.hobby(); //通过类来调用
console.log(Person.height);
私有成员 “#”符号
// ES5
function Person(name){
let _weight = "130kg"; //私有
this.name = name;
this.height = "178cm";
this.age = 20;
}
//ES6+,ES2020 “#”修饰符,来修饰私有属性
class Person{
#weight = "130kg"; //私有属性
height = "178cm"; //前面的public可以不写神略
constructor(name) {
this.name = name;
// this.height = "178cm";
this.age = 20;
}
getWeight(){ //通过公有方法,获取私有属性
return this.#weight;
}
}
console.log(zhangsan)//能看到Person的#weight,但不能打印数据 如下截图报错;
console.log(zhangsan.#weight) //打印报错,不能通过此种方法获取到;
// 公有方法获取私有属性
console.log( zhangsan.getWeight());
class Person{
#height = "178cm";
constructor(name){
this.name = name;
}
}
静态成员 static
//ES5
function Person(name) {
this.name = name;
}
//机器—类,棉花糖-对象
Person.num = 10;//静态属性和类Person有关系,和zhangsan对象无关系;
let zhangsan = new Person("张三");
console.log( zhangsan.num ); //错误的 undefined
// ES6 static关键字
class Person{
constructor(name){
this.name = name;
}
static num = 10;
static fn(){
console.log("fn");
}
}
console.log( Person.fn );
Person.fn();
静态方法和属性:实例不会继承属性和方法
// 要求一个类只能有一个实例。静态成员法
//全局变量多了,会造成变量污染;使用static方式改写 放入类内部
let instance;
class Person{
static instance;
constructor(name){
if(!Person.instance){ //instance
Person.instance = this; //instance
}else{
return Person.instance; //instance
}
this.name = name;
}
}
let zhangsan = new Person("张三");
let lisi = new Person("李四");
console.log(zhangsan,lisi)
访问器属性get、set
// 四、访问器属性
class Person{
constructor(){
// this.name = "张三";
//换一种方式定义属性,如下方式get set
}
get name(){
console.log("get");
}
set name(newValue){
console.log("set" , newValue);
}
}
let zhangsan = **new** Person();
console.log( zhangsan.name );
zhangsan.name = "李四"
类的继承
class Dad{
name = "张三";
age = 40;
constructor(height){
this.height = height;
}
hobby(){
console.log("喜欢篮球");
}
}
class Son extends Dad{
constructor(height){
//表示父类的构造函数
super(height);
}
}
- super特点
- super只能在子类中使用,可以在constructor 及 函数或静态方法中使用;
- 不能单独使用super;
- super调用类似函数调用可以根据父类构造函数传参数;
- 如果子类中没有constructor,子类会自动调取super()且传入参数到父类;
- 子类中需要在调取super之后调用this;
// ES5写法
function Dad(){
this.name = "张三";
this.age = 20;
}
function Son(){
Dad.call(this);
}
let zhanger = new Son();
console.log(zhanger);
// ES6 extends
class Dad{
constructor(name){
this.name = name;
}
fn(){console.log("fn");}
}
class Son extends Dad{
constructor(name){
//调用父类的constructor,super只能存在于子类里
super(name);
this.age = 20;
// console.log(super) //不能单独使用,打印报错
}
test(){
console.log("test");
super.fn(); //调用父类里的函数
}
}
抽象基类(‘类’->抽象为->‘基类’)
类可以被实例化为对象,让类继承—>基类,所以不允许直接被实例化(new)
基类:不允许直接被实例化
class AbstractPerson{
constructor(){
// console.log(new.target);
// new.target会指向AbstractPerson
if(new.target===AbstractPerson){
throw new Error("AbstractPerson 类不能被实例化");
}
this.age = 20;
}
}
class Person extends AbstractPerson{
constructor(name){
super();
this.name = name;
}
}
let p = new Person("张三");
console.log(p);
面试题相关
小贤是一条可爱的小狗(Dog),它的叫声很好听(wow),每次看到主人的时候就会乖乖叫 一声(yelp)。从这段描述可以得到以下对象:
class Dog{
wow(){ alert("wow"); }
yelp(){ this.wow(); }
}
小芒和小贤一样,原来也是一条可爱的小狗,可是突然有一天疯了(MadDog),一看到 人就会每隔半秒叫一声(wow)地不停叫唤(yelp)。请根据描述,按示例的形式用代码来实现。
- 如何实现链式调用?
__proto__和 prototype 之前有什么关系?
// 1.继承 、原型 、setInterval
class MadDog extends Dog{
yelp(){
setInterval(() => {
this.wow();
}, 500);
}
}
let xiaomang = new MadDog();
xiaomang.yelp();
//2. 如何实现链式调用?
let p = new Promise();
// p.then().then().then()..... //返还this;返还实例化对象
class Person{
fn(){
console.log("fn");
//return this;
//返还this(指向zhangsan),还会去实例化
return new Person();
//返还新的实例化对象(就不是zhangsan了)
}
}
let zhangsan = **new** Person();
zhangsan.fn().fn().fn();
// 3. `__proto__` 和 prototype 之前有什么关系?
//实例化对象有`__proto__` ,构造函数有prototype
function Person(name)
this.name = name;
}
// Person.prototype
// console.log( Person.__proto__);
console.dir(Person);
let obj = {};
//不规范,不推荐(私有属性)
obj.__proto__.name = "张三";
Object.setPrototypeOf(obj, {name:"张三"} );
console.log(obj);
3. ESM(es6模块化)
导入导出方式
<script type="module">//严格模式
function test(){
console.log(*this*);
}
test(); //严格模式下,打印的this是undefined
import './a.js'; //导出
import myfn,{*c* as e,b} from './a.js';
//以e的别名导出 (as别名导出)
import * as obj from './a.js';
//导出的全部东西 “*” 放入obj里
console.log(obj);
console.log(myfn);
// 按需加载
document.onclick = function(){
// import myfn,{c as e,b} from './a.js';
import("./a.js").then(res => {
console.log(res);
})
}
4. 王者荣耀选择英雄案例
- 抽象玩家类
- 抽象英雄类
- 抽象技能类
- 抽象游戏管理类
- 模块化分各类
- 扩展英雄及技能
- 亚瑟
- 契约之盾
- 回旋打击
- 圣剑裁决
- 鲁班
- 河豚手雷 S11210
- 无敌鲨嘴炮S11220
- 空中支援 S11230
课程总结
- 面向对象及面向过程编程
- ES6中类的使用
- ES6中继承extends、super
- ES6静态方法和属性
- ES6中模块化import、export
下节预告
前端常用设计模式