面向对象(Oriented Object)
1、面向过程:
把解决问题的方法和步骤,一步一步写出来(JS代码);
2、面向对象:
Oriented Object (Analysis Design分析设计)
把解决问题的方法和步骤中,涉及到的属性和方法,封装成一个构造函数(对象),即对象的抽象
一、构造函数
构造函数.prototype 指向的是原型对象
实例对象.proto 指向的是原型对象
1、创建构造函数:
和普通函数的书写方式一样(命名以大驼峰方式)
创建方式,先创建一个空对象,然后创建这个对象的属性并赋值,添加到这个空对象中去,最后返回这个对象
function Students(name, age, gender) {
// let this = {}
this.name = name;
this.age = age;
this.gender = gender;
// return this; // 默认
}
let stu1 = new Students("xiaoguo", 18, "male");
let stu2 = new Students("xiaomi", 18, "famale");
console.log(stu1.name); // xiaoguo
console.log(stu2.name); // xiaomi
2、构造函数手动添加的返回值:
如果返回值是基本数据类型,默认返回值是不会改变的。
如果返回的是引用数据类型,默认返回值就会改变。
function Students(name, age, gender) {
// let this = {} // 在第一行先生成一个this空对象。
this.name = name;
this.age = age;
this.gender = gender;
// return this; // 默认:JS会自动返回this对象
// return 123; // 无效:依然返回this对象
// return [123]; // 有效:引用数据类型,则返回引用数据类型的对象,不返回this对象。
}
3、普通函数和构造函数本质区别:
1、调用方式不同:
普通函数是 函数名() ;
构造函数是 new 函数名();
类: new 类名();
2、返回值不同:
普通函数默认返回值是undefined,或者是return后面的
构造函数返回值是this这个对象
二、自动装箱,自动拆箱
内置对象(包装类)
A)自动装箱
let str = "abc";
// String length属性
console.log(str.length); // 3 (自动装箱)
1、基本数据类型:字符串
可以直接使用String的方法和属性
2、基本数据类型:数值
不可以直接使用toString的方法,原因:数值分整型和浮点型(小数)
// 123.toString() 123后面的点,会被视为小数点,不会装箱为对象
// 解决方式如下:
(123).toString()
123.0.toString()
B)自动拆箱
let num = new String(456);
num.abc = 123;
// console.log(num.abc); // 123
// 自动转换为基本数据类型 String,然后字符串+1,字符串拼接为4561
console.log(num + 1); // 4561
// 自动转换为基本数据类型 String,然后字符串-1,隐式转换为数值456-1,最后输出455
console.log(num - 1); // 455
三、ES6类 class
类是对象的抽象, 对象是类的实例。
语法糖:ES6的书写方式
typeof 类 —function
注:类似封装一个构造函数;类就是函数
书写方式
class Students {
// constructor 构造器
constructor(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
haha() {
console.log('hahaha',this.name);
}
};
let stu = new Students('z3', 18, 180);
console.log(stu.name); // z3
stu.haha(); // hahaha z3
// ES5可以使用普通函数调用方式,ES6会报错,提示调用前加 new
let stu5 = Students();
四、原型
1、判别属性是私有的还是公用的
注:同类别的实例对象可以使用公用的属性和方法
1)私有:
this.属性名 = 属性值;
this.方法名 = function () { };
2)公用:
类.prototype.属性名 = 属性值;
类.prototype.方法名 = function () { };
实例.__proto__.属性名 = 属性值;
实例.__proto__.方法名 = function () { };
2、new 一个实例的工作流程
1、this的空对象
2、this的指向" proto “类.prototype”
3、执行代码(给this添加属性和方法)
4、return this(即返回对象给实例)
注:手动更改返回值
五、this
1、this 指向
1)对象中的this,
谁调用就指向谁
2)构造函数中的this
指向的是实例化对象
3)事件监听中的this
指向的是绑定事件的节点
4、普通函数中的this
指向的是window
注:严格模式下(“use strict”),指向的是undefined
let name = "cc";
var name = "dd";
function f70() {
// "use strict" 严格模式
console.log("A", this.name); // this 指向的是window
}
f70();
5、箭头函数中this的指向
取决于所处环境
注:箭头函数没有自己的 arguments,this
let str = x => console.log(this);
str(); // window
document.addEventListener("click", function () {
let str = x => console.log(this);
str(); // document
})
2、改变this指向
1、call 和 apply 是立即执行,
A)call传参第二个参数及之后的参数是以基本数据类型传入,
B)apply传参第二个参数是以数组的形式传参
2、bind不是立即执行的,
需要用一个变量来接收,使用时和函数一样需要调用他,
传参改变this指向的时候传参,也可以在调用的时候在传参,
传参可以传所有的参数也可以传部分的,他会自己识别参数
function F70 (name) {
// let this = {};nh
this.name = name;
this.say = function (weight,height) {
console.log(this.name,weight,height);
}
}
let stu1 = new F70("asd");
// stu1.say(); // a
let stu2 = {
name:"xiaoguo",
}
stu1.say.call(stu2,"80kg","188"); // xiaoguo 80kg 188
stu1.say.apply(stu2,["70kg","188"]);; // xiaoguo 70kg 188
let xiaoguoH = stu1.say.bind(stu2,"60kg","188");
xiaoguoH(); // xiaoguo 60kg 188
stu1.say.bind(stu2,"60kg","188")();
六、原型
每个函数都有一个prototype属性叫原型
1、原型链
1、找自身的属性.方法,如果没有就做第二步,
2、在原型找属性.方法,如果没有就做第三步,
3、在原型的__proto__找属性
七、面向对象的三大特征
1)封装
A)ES5 构造函数添加属性和方法
function F70(name) {
this.name = name;
this.say = function () {
console.log(this.name);
}
}
B)ES6 属性和方法封装成一个对象
class F70{
constructor(name){
this.name = name;
}
say(){
console.log(this.name);
}
}
2)继承 (inherit)
A)ES5 版本 工作流程
通过构造函数的执行过程变相实现继承:
1)继承属性
a、先创建一个this空对象
b、通过构造函数的特点,把(传承者)这个构造函数赋值给自己的属性名,(产生一个新的方法)
c、执行这个方法,通过构造函数的执行过程 把他这个方法里面的所有属性添加到this这个对象中去,
d、删除这个方法,因为这个方法中的所有属性已经通过构造函数执行过程存储到this这个对象中去,变相实现了继承效果
e、最后实例化对象是就会返回这个this对象,所以(继承者)就能拿到(传承者)的属性
2)继承原型中的方法与属性
通过把(继承者)的原型的__proto__指向(传承者)的原型,(继承者)就能拿到(传承者)的原型,
就可以使用(传承者) prototype中的方法,并且不会覆盖自己的原型
Grand.prototype.hello = function () {
console.log("hello");
}
function Grand() {
this.name = "123";
}
Father.prototype.say = function () {
console.log("abc");
}
function Father() {
this.gender = true;
this.age = 18;
}
function Son() {
// 1)继承属性
// a、先创建一个this空对象
// b、通过构造函数的特点,把Grand这个构造函数赋值给自己的属性名,(产生一个新的方法)
this.xiaoguo1 = Grand;
// c、执行这个方法,通过构造函数的执行过程 把他这个方法里面的所有属性添加到this这个对象中去,
this.xiaoguo1();
// d、删除这个方法,因为这个方法中的所有属性已经通过构造函数执行过程存储到this这个对象中去,变相实现了继承效果
delete this.xiaoguo;
this.xiaoguo2 = Father;
this.xiaoguo2();
delete this.xiaoguo;
this.gender = true;
// e、最后实例化对象是就会返回这个this对象,所以s1就能拿到 Father 和 Grand的属性
}
// 2)继承原型中的方法和属性
// 通过把Son的原型的__proto__指向Father的原型,s1就能拿到Father的原型,
// 就可以使用Father prototype中的方法,并且不会覆盖自己的原型
Son.prototype.__proto__ = Father.prototype;
// 把Father的原型的__proto__指向Grand的原型,s1就能拿到Grand的原型,
// 就可以使用Grand prototype中的方法。
Father.prototype.__proto__ = Grand.prototype;
let s1 = new Son();
console.log(s1.age); // 18
s1.say(); // abc
s1.hello(); // hello
console.log(s1.name);// 123
2)ES6 版本
ES6继承 通过 关键字 extends 与 super实现
书写格式 :
A)在继承者的类名后面加 extends 和传承者(必须为类的名字),
B)然后在被继承者的constructor函数中添加super关键字并在小括号里面添加传承者的实参;
注:super必须写在所有this之前
class Father {
constructor(money,name,age){
this.name = name;
this.age = age;
this.money = money;
}
say(){
console.log(this.name,this.age);
};
}
// ES6继承 通过 关键字 extends 与 super实现
// 书写格式 :
// A)在继承者的类名后面加 extends 和传承者(必须为类的名字),
// B)然后在被继承者的constructor函数中添加super关键字并在小括号里面添加传承者的实参;
class Son extends Father {
constructor(name,age,money){
// super必须写在this之前
super(money);
this.name = name;
this.age = age;
}
say(){
console.log(this.name,this.age);
}
}
let s1 = new Son("zx",12,"100w");
console.log(s1);
3)多态
同一个构造函数或类,实力化出不同的对象(根据参数不同)
不同的实例对象,调用同一个方法,得到不同的结果
class Students{
constructor(name,age,gender){
this.name = name;
this.age = age;
this.gender = gender;
}
hello(place){
console.log(this.name,place);
}
}
let stu1 = new Students("zx",17,"male");
let stu2 = new Students("as",16,"famale");
let stu3 = new Students("vd",19,"male");
stu1.hello('DB'); // zx DB
stu2.hello('SC'); // as SC
stu3.hello('LS'); // vd LS
八、补充内容:扩展
1、对象中添加新的属性
let obj = {
name: "zx",
}
obj.age = 123;
A)添加新属性
Object.defineProperty(obj, "gender", {
// 设置属性值
value: "male",
// 设置新增属性可更改(true),默认false 不可更改
writable: true,
// 设置新增属性可遍历(true),默认false 不可遍历
enumerable: true,
})
B)属性被赋值、更改、使用时
属性被赋值或更改时,触发set方法,
属性被使用时,触发get方法
Object.defineProperty(obj, "cc", {
set(value) { // 设置的值
if (value >= 40) {
this.age = 18
} else {
this.age = 19
}
},
get() { // 把结果反馈给cc
return this.age;
},
})
console.log(obj.age,obj); // 0
obj.cc = 40;
console.log(obj.age,obj); // 18
obj.cc = 20;
console.log(obj.age,obj); // 19
2、遍历对象中的属性值或属性名
A)遍历属性名
for (const index in obj) {
console.log(obj[index]);
};
B)遍历属性值
for (const item of Object.keys(obj)) {
console.log(item);
};
3、深拷贝,浅拷贝
A)浅拷贝:
公用一个地址,因此其中一个更改,另一个也会发生改变
let arr1 = [1,2,3];
let arr2 = arr
arr[2] = "a";
console.log(arr2); //[1,2,"a"]
console.log(arr); //[1,2,"a"]
B)深拷贝:
不是同一个地址,因此其中一个改变,另一个不会发生改变
let arr1 = [1,2,3];
let arr2 = [];
// 方法1
// arr1.map(a=>arr2.push(a));
// 方法2
let arr2 = arr1.slice(0);
arr1[2] = "a";
console.log(arr1); // [1,2,"a"]
console.log(arr2); // [1,2,3]
4、扩展运算符(… )功能
1、批量修改和赋值;
let obj1 = {
name: 1,
age: 2,
}
let obj2 = {...obj1};
obj1.name = "a";
console.log("a",obj1);
console.log("b",obj2);
2、不定参数
function date(age,name,...time) {
console.log(age,name,...time);
}
date(15,"zx","11点","12点") // 15 "zx" "11点" "12点"