面向对象
目录
4.1.1 Object Array Function String Number Boolean. 10
1.面向对象
1.1面向对象
我们之前写的代码都是使用面向过程的方式
面向过程 | 面向对象 |
var name = '老王’; | var person = { |
var age = 18; | name:’老王’, |
Var sex=’男’; | age: 20, |
var length = 180; | sex:"男", |
length: 180 | |
} |
也就是说,面向对象是代码书写的另一种方式
1.2构造函数(new)
构造函数与普通函数在定义方式上没有不同,只不过构造函数的首字母要大写,此要求非语法要求
构造函数必须使用new关键字进行调用
目的:
普通函数:实现某一种功能; 构造函数:为了创建对象
调用方式:
普通函数:直接调用; 构造函数:使用new 进行调用
//定义构造函数,与普通函数一样,通常首字母大写
//人的构造函数
function Person() { }
//狗
function Dog() { }
//通过构造函数创建对象
var p = new Person();
var d = new Dog();
console.log(p, d);//Person {} Dog {}
//类:通过构造函数可以创建一类对象,该构造函数就被称之为 类
//创建的对象,称之为 该类的实例化对
构造函数执行时候的四个步骤
1开辟一个新的内存空间
2改变this指向
3执行函数中的代码,为this赋值
4返回this
function Person(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
}
//四.1 直接为原型对象添加属性,让实例共享
Person.prototype.getName = function () {
console.log('姓名:' + this.name);
}
Person.prototype.getSex = function () {
console.log('性别:' + this.sex);
}
Person.prototype.getAge = function () {
console.log('年龄:' + this.age);
}
//创建人
var p1 = new Person('小白', '男', 20);
var p2 = new Person('小红', '女', 18);
p1.getName();//姓名:小白
p1.getSex();//男
p2.getAge();//年龄:18
console.log(p1, p2);
//👇 皆为true,t,t
console.log(p1.getSex() === p2.getSex(), p1.getName() === p2.getName());
2.原型
原型是每一个函数天生可以调用的属性,它的值是一个对象,对象中有constructor属性执行构造函数本身
作用:实例共享属性和方法
2.1特点:
每一个实例化对象天生可以访问类原型中的所有内容
拓展
instanceof
该关键字用于判定某一个对象是否是某一个构造函数的实例
会查询整个原型链。
hasOwnProperty
该方法检测某个对象身上是否包含了指定的属性
| |
|
|
用来检测 | |
类:包含构造函数与原型 |
<script>
function Person(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
//1.定义同名方法
this.getName = function () {
console.log(this.name);//小白
}
}
// 直接为原型对象添加属性,让实例共享
Person.prototype.getName = function () {
console.log('姓名:' + this.name);
}
Person.prototype.getSex = function () {
console.log('性别:' + this.sex);
}
Person.prototype.getAge = function () {
console.log('年龄:' + this.age);
}
//原型是允许 存储属性数据
Person.prototype.msg = 'hello';
//创建人
var p1 = new Person('小白', '男', 20);
// var p2 = new Person('小红', '女', 18);
// //2.constructor属性,代表构造函数
// console.log(p1);
// console.log(p1.constructor);
// //使用方法
// p1.getName();
//for in 循环案例详解
// function Eg() {
// this.name = "张三";
// this.age = "30";
// this.port = "显老的程序员";
// }
// var eg = new Eg(); //实例化
// for (var k in eg) { //定义一个k变量 在eg对象中遍历
// console.log(k); // name age port k表示对象中的属性名
// }
// for (var k in eg) {
// console.log(eg[k]); // 张三 30 显老的程序员
// }
//3.for in 循环,可以将原型数据遍历出来
for (var key in p1) {
// console.log(key, p1[key]);
//hasOwnProperty检测某个对象身上是否包含了指定的属性
//如果是自身的返回true ,如果是原型的返回false
// console.log(p1.hasOwnProperty(key), key);//ttttfff
//查看自身的属性(查看实例属性)
if (p1.hasOwnProperty(key)) {
console.log(key, p1[key]);
}
}
//判断p1是不是Person实例
console.log(p1 instanceof Person);//true
console.log(p1 instanceof Object);//true//Object:对象的原生类
console.log(p1 instanceof Array);//false//Array:数组的原生类
/**
* 原型链:
* 原型对象也是对象,对象有原型对象,所以原型对象也有原型对象;
* 数据在查找的时候,会在当前实例对象上查找,当前对象没有则去原型对象查找,
* 原型对象没有则,又去原型对象的原型对象查找 ...
* 直到查找到原型对象的终点:null
* 如果此时还没有找到,就说明该数据不存在
* 这个查找过程就是 原型链 ,与作用域查找数据类似
* **/
//查看原型对象
//双下划线一般都是内置的隐藏的属性(不建议访问)
//函数也是对象,因此它的原型对象就是函数
console.log(Person.__proto__);//ƒ () { [native code] }
// p1也是对象,原型对象就是Person构造函数的原型对象
console.log(p1.__proto__);//{msg: "hello", getName: ƒ, getSex: ƒ, getAge: ƒ, constructor: ƒ}
console.log(p1.__proto__.__proto__);
console.log(p1.__proto__.__proto__.__proto__);//null
// console.log(p1.__proto__.__proto__.__proto__.__proto__);//报错
console.log(p1.__proto__.hasOwnProperty('hasOwnProperty'));//,false
console.log(p1.__proto__.__proto__.hasOwnProperty('hasOwnProperty'));//,true
// console.log(p1.__proto__.__proto__.__proto__.hasOwnProperty('hasOwnProperty'));// 报错
3.安全类
无论外部如何调用该类,都会返回一个类的实例化对象
在构造函数中,判断this的指向,从而决定代码如何执行
如果this是该类的实例,正常创建对象
否则,说明用户将构造函数当作普通函数来执行,我们要在内部重新实例化并返回结果
<script>
//一切数据都可以看作对象,是对象都有与之对应的 类
//例如:对象(对应的类):Object、数组:Array
//创建数组的三种方式
var arr = [1, 2, 3, 4];//字面量 //[1, 2, 3, 4]
var arr2 = new Array(3, 4, 5, 6);//构造函数 //[3, 4, 5, 6]
var arr3 = Array(7, 8, 9, 0)//工厂方法 //[7, 8, 9, 0]
console.log(arr, arr2, arr3);
//小问题:(构造函数、工厂)对参数进行了重载,
//传递的参数个数、参数类型的不同,得到的结果不同(内部逻辑不同)
console.log(new Array(3), new Array('3'), new Array(2, 3));
// [empty × 3]👉表示数组长度 , ["3"]👉表示成员 , [2, 3]👉多个内容,表示成员
console.log(Array(3), Array('3'), Array(2, 3));
// [empty × 3]👉表示数组长度 , ["3"]👉表示成员 , [2, 3]👉多个内容,表示成员
// //创建对象的三种方式
// //字面量
// var obj1 = { color: 'red' };//{color: "red"}
// //构造函数
// var obj2 = new Object({ color: 'green' })//{color: "green"}
// ///工厂方法
// var obj3 = Object({ color: 'pink' })//{color: "pink"}
// console.log(obj1, obj2, obj3);
// //创建类
// function People(name, age) {
// console.log(this);//指向People
// this.name = name;
// this.age = age;
// }
// //创建对象
// var p1 = new People('张三', 156)//{name: "张三", age: 156}
// //👇没有创建出对象,把People(构造函数)当作普通函数使用了
// //👇普通函数this指向window
// //👇this赋值被添加到全局了,污染全局作用域//在控制台直接输入name可输出结果“李四”
// var p2 = People('李四', 56)//undefined
// console.log(p1, p2);
//安全类
//在内部判断this是否是该类的实例,是则直接实例,否则重新实例并返回
function People(name, age) {
//判断this是不是People的实例化
if (this instanceof People) {
//直接赋值
this.name = name;
this.age = age;
} else {
return new People(name, age);
}
}
//创建对象
var p1 = new People('张三', 156)//People{name: "张三", age: 156}
//👇没有创建出对象,把People(构造函数)当作普通函数使用了
//👇普通函数this指向window
//👇this赋值被添加到全局了,污染全局作用域//在控制台直接输入name可输出结果“李四”
var p2 = People('李四', 56)
//👆undefined//修改后:People {name: "李四", age: 56}
console.log(p1, p2);
</script>
4. 内置构造函数
4.1内置构造函数的分类:
ECMAScript核心语法也支持一些内置构造函数:
Object Array Function String Number Boolean RegExp Error Date
4.1.1 Object Array Function String Number Boolean
//内置构造函数//是js语言天生存在的例如:Image
//Object,Array,Function,String,Number,Boolean,RegExp,Error,Date
//创建图片
var img = new Image();
console.log(img);//<img>
//数组与对象
console.log(new Array(2, 3,));//[2, 3]
//对象
console.log(new Object({ num: 100 }));//{num: 100}
//创建函数的三种方式
//1.函数定义式
function demo() {
console.log('deom');
}
//2.函数表达式
var demo2 = function () { console.log('demo2'); }
//3.构造函数式
//参数:最后一个参数表示函数体,前面的函数 表示函数的参数
//如:求两数和
var demo3 = new Function('a', 'b', 'console.log(a+b)');
console.log(demo3);
demo3(2, 3)//5
//设置返回值
var demo4 = new Function('a', 'b', 'return a*b');
console.log(demo4(5, 9));//45
//让字符串作为语句指向的第 2 种方式
eval('console.log("hello qaq")')//hello qaq
eval('var num = 10');
console.log(num);//10
//比较
var demo32 = new Function('a', 'b', 'var num2 = 66; console.log("inner",num2);return a*b');
// console.log(num2);//Uncaught ReferenceError: num2 is not defined
demo32()//inner 66
/**
* new Funcition 与 eval 的区别
* new Funcition 内部定义的变量是函数体内部定义的,是局部变量,不会传递到全局
* eval 字符串种定义的变量,由于在全局作用域中执行,会被存储在全局,污染全局作用域
* **/
//字符串 使用方法
//注意:字符串在后台也是通过 构造函数的方式去创建的
console.log(typeof 'abcdefg');//string
var arr = 'abcdefg'.split('');//字符串使用方法split()
console.log(arr);//["a", "b", "c", "d", "e", "f", "g"]
//内部运行展示
var str = new String('asfasdf');
//👆对象实例化,就可以调用原型上的属性与方法
var arr2 = str.split('')
console.log(arr2);//["a", "s", "f", "a", "s", "d", "f"]
console.log(String.prototype);//String{.....各种方法}
//数字 //原理同上
var num1 = 1000;
//保留两位小数
console.log(num1.toFixed(2));//1000.00
//在后台运行的展示
var num2 = new Number('998');
var num3 = num2.toFixed(3);
console.log(num3);//998.000
console.log(Number.prototype);//Number{.....各种方法}
//注意:字符串、数字当作工厂方法使用时,不是创建对象,而是包装对象(做类型的转换)
var str4 = new String('546');//创建的是实例
var str41 = String('555');//直接用字符串方法处理,即包装对象(做类型的转换)
console.log(str4, str41);//String {"546"} , "555"
var num4 = new Number("888");
var num41 = Number(777);
console.log(num4, num41);//Number {888} , 777
// 工作中,常使用String、Number工厂方法,来实现显性的数据类型转换
//隐性的数据类型转换: 转字符串: +'' ; 转数字: +-*/等 ; 转布尔值:!!
//布尔值
var bool = new Boolean('1424');//实例
var bool2 = Boolean(1424);//包装,显性转换
console.log(bool, bool2);//Boolean {true} , true
console.log(Boolean.prototype);// Boolean {false, constructor: ƒ, toString: ƒ, valueOf: ƒ}
4.1.2 RegExp,Error,Date
//Error,Date
//创建错误
// var err1 = new Error('这是一个帅逼')
// console.log(err1);//Error: 这是一个帅逼
// //抛出错误
// throw new Error('这是一个大大的帅逼')//原版报错样式
//注意:一旦抛出错误,程序将终止执行
function demo() {
console.log('before');
// throw new Error('STOP');//程序在此处终止
console.log('after');
}
demo();//before , STOP
console.log(new Error('这是一个帅逼'));//Error: 这是一个帅逼
console.log(Error('这是一个帅逼'));//Error: 这是一个帅逼
// Error 也是安全类,但工作中new Error更常用
核心代码:
function timeInput() {
var inp = document.getElementById('inp');
var inputOver = document.getElementById('inputOver');
var input = inp.value;//获取输入的值哦
var d = new Date();//现在的时间//Mon Sep 13 2021 19:13:43 GMT+0800(cn)
var nowD = new Date(input);//输入的时间
var qaq = nowD.getTime() - d.getTime();
var D = Math.floor(qaq / 1000 / 60 / 60 / 24);
var h = Math.floor(qaq / 1000 / 60 / 60 % 24);
var m = Math.floor(qaq / 1000 / 60 % 60);
var s = Math.floor(qaq / 1000 % 60);
inputOver.innerText = '距现在倒计时:' + D + '天' + h + '小时' + m + '分' + s + '秒';
// console.log(inputOver.innerHTML);
if (D === 0 && h === 0 && m === 0 && s === 0) {//注意:此处必须用全等
clearInterval(timebar2)
}
}
var timebar2 = setInterval(timeInput, 1000);
5.源生对象的原型链与构造函数
5.1源生对象的原型链与构造函数
//定义数组与对象
var arr = [];
var obj = {};
console.log(obj instanceof Object);//true
//instanceof会遍历整个原型链,去判断constructor
console.log(arr instanceof Array);//true
console.log(arr instanceof Object);//true
// console.log(arr instanceof null);
//👆报错//null不是对象是空对象,空对象不是对象,它含义是对象但是代表空
//构造函数constuctor
console.log(arr.constructor);//ƒ Array() { [native code] }
console.log(arr.constructor === Array);//true//原型链找到后,不会再往下寻找了
console.log(arr.constructor === Object);//false//被覆盖了,找不到了
console.log(obj.constructor === Object);//true
//构造函数是函数,因此是Function的实例
console.log(arr.constructor instanceof Function);//true
console.log(arr.constructor instanceof Object);//true
console.log(arr.constructor instanceof Array);//false
console.log(Function instanceof Object);//true
console.log(111, arr.constructor);//111 ƒ Array() { [native code] }
console.log(222, arr.constructor.constructor);//222 ƒ Function() { [native code] }
console.log(333, arr.constructor.constructor.constructor);//333 ƒ Function() { [native code] }
console.log(444, arr.constructor.constructor.constructor.__proto__);//444 ƒ () { [native code]
console.log(555, arr.constructor.constructor.constructor.constructor);//555 ƒ Function() { [native code] }
5.2Function
作用:
该内置构造函数用于创建函数
使用方式:
new Function(arg1, arg2, .... body);
可以接收多个参数,除了最后一个参数是函数体,其它的都是形参
特点:
通过new Function得到的函数,通过函数.name属性得到的是anonymous
而通过函数表达式和函数声明式,打点调用name属性得到的是函数名称
拓展:
函数.length得到的是形参的个数
arguments.length得到的是实参的个数
//new Function(arg1, arg2, .... body);
//可以接收多个参数,除了最后一个参数是函数体,其它的都是形参
//构造函数
//1.函数定义式
function demo1() {
console.log('deom');
}
//2.函数表达式
var demo2 = function (e) { console.log('demo2'); }
//3.构造函数式
//参数:最后一个参数表示函数体,前面的函数 表示函数的参数
//如:求两数和
var demo3 = new Function('a', 'b', 'console.log(a+b)');
var demo4 = new Function('a', 'b', 'return a*b');
demo1();//deom
demo2();//demo2
demo3(2, 3)//5
console.log(demo4(5, 9));//45
//函数有name属性,表示函数名称
console.log(demo1.name);//demo1
console.log(demo2.name);//demo2
console.log(demo3.name);//anonymous
console.log(demo4.name);//anonymous
//形参个数与实参个数
//函数.length得到的是形参的个数,
console.log(111, demo1.length);//0
console.log(111, demo2.length);//1// 注意此处传递了一个形参e,function (e)
console.log(111, demo3.length);//2
console.log(111, demo4.length);//2
//arguments.length得到的是实参的个数
function demo(a, b, c) {
//打印实参个数
console.log('实参', arguments.length);
}
demo(1, 2, 3, 4, 5);//实参 5
demo(1);//实参 1
console.log(demo.length);//形参 3
5.3 RegExp
作用:
该内置构造函数用于创建正则表达式
使用方式:
new RegExp(reg, letter);
接收两个参数:
第一个参数是正则表达式的表达体,字符串
第二个参数是正则表达式的修饰符,i、g、m
注意:因为字符串中也有转义,所以需要多转义一次
5.4其它内置构造函数
String
该内置构造函数是string值类型的对应包装类型
这也就说明了为什么一个字符串可以调用方法。
Number
是number值类型的对应包装类型。转为对应的包装类型
Boolean
是Boolean值类型的对应包装类型。转为对应的包装类型
6. 继承♥
6.1继承:
指的是子类继承父类的属性和方法。
继承方式:
1.类式继承
2.构造函数式继承
3.组合式继承
4.寄生式继承
5.寄生式组合继承
/**
* 继承:
* 父类:被继承的类,是一个大的范围
* 子类:去继承父类的属性与方法的类,是一个小的范围
* 例1:
* 父类:动物:是一个大的类
* 动物可以吃,跑,眼睛,鼻子等属性
* 子类:狗 :是动物的一种,是子类
* 狗也可以吃,跑眼睛,鼻子等属性,这些方法是一样的,我们希望是可以复用的
* 继承就是讨论如何让狗具有动物的吃,跑等行为。
* 如何让狗具有动物的吃,跑,眼睛,鼻子等属性或方法的过程 叫继承。
* 例2:
* 父类:人类 是一个大的范围
* 子类:肤色:黄种人、白人、黑人...
* 地域:亚洲人、欧洲人、非洲人...
**/
//面向对象主要就是:封装(类)、继承、多态(js中讨论多态代价很大,一般在js中不管它)
//js中面向对象主要讨论的是:封装(类)、继承
6.2类式继承
6.2.1构造函数
让父类的构造函数,在子类的对象(作用域)上执行,并传递子类的参数。
//定义父类:人类
function People(name, sex, age) {
//存储属性
this.name = name;
this.sex = sex;
this.age = age;
}
//行为方法
People.prototype.getName = function () {
console.log('我是' + this.name + '大帅逼');
}
//获取性别
People.prototype.getSex = function () {
console.log('我是' + this.sex + '人');
}
//获取年龄
People.prototype.getAge = function () {
console.log('我今年' + this.age + '岁');
}
//定义子类:学生类
function Student(name, sex, age, grade) {
/**
*构造函数
* 让父类的构造函数,在子类的对象(作用域)上执行,并传递子类的参数
**/
People.call(this, name, sex, age)
//存储属性
this.grade = grade;
//我们还可以在继承后重写属性(如果在继承前写,可能会出现覆盖问题)
this.name = 'QAQ:' + name;
}
//行为方法
Student.prototype.getName = function () {
console.log('我是' + this.name + '大帅逼');
}
//获取性别
Student.prototype.getSex = function () {
console.log('我是' + this.sex + '孩纸');
}
//获取年龄
Student.prototype.getAge = function () {
console.log('我今年' + this.age + '岁');
}
//获取年级
Student.prototype.getGrade = function () {
console.log('我今年' + this.grade + '年级');
}
//创建对象
var p1 = new People('老李', '男', 21);
var s1 = new Student('小白', '女', 12, '六');
//👇继承前:People {name: "老李", sex: "男", age: 21}
//👇Student {name: "小白", sex: "女", age: 12, grade: "六"}
console.log(p1, s1);
//👆继承后:Student {name: "QAQ:小白", sex: "女", age: 12, grade: "六"}
s1.getName();//我是小白大帅逼 //我是QAQ:小白大帅逼
p1.getName();//我是老李大帅逼
s1.getAge();//我今年12岁
p1.getAge();//我今年20岁
s1.getSex();//我是女孩纸
p1.getSex();//我是男孩纸
console.log(p1.getName === s1.getName);//false
console.log(p1.getSex === s1.getSex);//false
//👆属性与逻辑相同,但不是同一个函数//表示没有复用
6.2.2类式继承(原型式继承)
类式继承:
由于,父类的实例对象拥有父类原型属性与方法,因此,我们可以用父类的实例对象为子类的原型赋值
/**
* 类式继承:
* 由于,父类的实例对象拥有父类原型属性与方法,因此,我们可以用父类的实例对象为子类的原型赋值
*
问题:
*1.子类原型上出现了父类构造函数中添加的属性与方法,是多余且错误毫无作用的。
*2.多执行了一次父类的构造函数
*3.constuctor属性没了,指向错误
*4.无法复用构造函数中,存储属性的逻辑
* **/
Student.prototype = new People();
//改正原型上的构造函数
Student.prototype.constructor = Student;
//重写方法
Student.prototype.getSex = function () {
console.log('我是' + this.sex + '孩纸');
}
如此一来,父类的getSex='我是' + this.sex + '人'与子类的‘孩纸’不会互相影响
6.2.3组合继承
/**.
* 组合式的继承:
* 是一起使用构造函数继承,与类式继承
* 通过构造函数式继承:
* 复用构造函数中,存储属性的逻辑。(即:this.name = name;等)
* 通过类式继承:
* 复用父类原型上的方法
* 组合式继承的问题:
* 全等于类式继承的问题:
* * 问题:
* 1.子类原型上出现了父类构造函数中添加的属性与方法,是多余且错误毫无作用的。
* 2.多执行了一次父类的构造函数
* 3.constuctor属性没了,指向错误
* 4.无法复用构造函数中,存储属性的逻辑
* **/核心部分👇:
People.call(this, name, sex, age); //构造函数式继承
Student.prototype = new People(); //类式继承:
Student.prototype.constructor = Student; //改正原型上(重写)的构造函数
6.2.4寄生式及组合继承
/**
* 寄生式继承:
* 主要用于解决类式继承的问题
* 在一个继承方法中,创建一个寄生类,让寄生类的原型等于父类的原型,
* 再实例化寄生类,最后把寄生类赋值给子类原型(类式于一个中转站)
*
* 问题:
* 仍无法复用构造函数中,存储属性的逻辑:无法达到这个效果:People.call(this, name, sex, age);
* 解决:组合使用构造函数式:People.call(this, name, sex, age); 即可
* **/
function inherit(child, parent) {
//1.定义寄生类
function F() {
//4.修改构造函数
this.constructor = child;
}
//2.让寄生类的原型等于父类
F.prototype = parent.prototype;
//3.用寄生类的实例为子类赋值
child.prototype = new F();
// //4.修改构造函数
// child.prototype.constructor = child;
return child;//5.可以不写
}
//定义子类:学生类
function Student(name, sex, age, grade) {
//构造函数式继承
People.call(this, name, sex, age);//People代替this指向的Student
console.log(888, this);//this是执行
console.log(Student);//打印的是Student这个函数
//存储属性
// this.name = name;
// this.sex = sex;
// this.age = age;
this.grade = grade;
}
//寄生式继承
inherit(Student, People);
//重写方法
Student.prototype.getSex = function () {
console.log('我是' + this.sex + '孩纸');
}
//注意!继承后不能使用字面量对象,会被覆盖,也会报错,对象字面量语法
// Student.prototype = {
// getSex: function () {
// console.log('我是' + this.sex + '孩纸');
// },
// //其他属性方法
// color: 'red'
// }