10.JavaScript-面向对象
10.1、JavaScript-面向对象基本概念
1.面向过程:
强调的是功能行为
关注的是解决问题需要哪些步骤
每一个具体步骤中我们都是参与方,并且需要面对具体的每一个步骤和过程。
2.面向对象Object Oriented Programming-OOP
将功能封装进对象,强调具备了功能的对象
关注的是解决问题需要哪些对象
3.从面向过程到面向对象(两者都是一种思想)
当需求单一,或者简单时,我们一步一步去操作没问题,并且效率也挺高,可随着需求的更改,功能的增加,发现需要面对每一个步骤非常麻烦,这是就要开始思考,能不能将这些步骤和功能再进行封装,封装时根据不同的功能,进行不同的封装,功能类似的封装在一起,这样结构就清晰多了。用的时候,找到对应的类就可以了,这就是面向对象的思想
4.举例说明
4.1、面向过程(亲力亲为)
了解电脑——了解自己的需求——对比参数——去电脑城——砍价,付钱——买回电脑——被坑
去超市买菜——摘菜——洗菜——切菜——炒菜——盛菜——吃
4.2、面向对象(专事专对象)
找班长——描述需求——班长把电脑买回来
去饭店——点菜——吃
10.2、JavaScript-创建默认对象
1.面向对象的特点
是一种符合人们思考习惯的思想
可以将复杂的事情简单化
将程序员从执行者转换成了指挥者
完成需求时:
先要去找具有所需的功能的对象来用
如果该对象不存在,那么创建一个具有所需功能的对象
这样简化开发并提高复用
2.类与对象的关系
面向对象的核心就是对象,那怎么创建对象?
现实生活中可以根据模板创建对象,编程语言也一样,也必须先有一个模板,在这个模板中说清楚将来创建出来的对象有哪些属性和行为
属性:人的身高,体重
行为:人会跑,跳
JavaScript中的类相当于图纸,用来描述一类事物。
JavaScript中可以自定义类, 但是也提供了一个默认的类叫做Object
3.使用默认的类Object创建对象
3.1、JavaScript中提供了一个默认的类Object,我么可以通过类来创建对象
3.2、由于我们是使用系统默认的类创建的对象,所以系统不知道我们想要什么属性和行为,所以我们必须手动的添加我们想要的属性和行为
4.如何给一个对象添加属性
对象名称.属性名称 = 值;
5.如何给一个对象添加行为
对象名称.行为名称 = 函数;
注意:实际不是函数,而是方法,这里先写为函数,方便理解。具体见下一节
6.如何获取一个对象的属性
对象名称.属性名称
7.如何获取一个对象的行为
对象名称.行为名称
8.默认的类Object创建对象的三种方式
8.1、创建对象的第一种方式
let obj = new Object();//创建对象
obj.name = "鸽子";//给对象添加属性
obj.age = "18";
obj.run = function () {//给对象添加行为,行为可以执行后面的函数
console.log("跑死宝宝");
}
console.log(obj.name);//获取一个对象的属性
console.log(obj.age);
obj.run();//获取一个对象的行为
8.2、创建对象的第二种方式
let obj1 = {};//等于let obj = new Object();创建对象
obj1.name = "鸽子";//给对象添加属性
obj1.age = "18";
obj1.run = function () {//给对象添加行为,行为可以执行后面的函数
console.log("跑死宝宝");
}
console.log(obj1.name);//获取一个对象的属性
console.log(obj1.age);
obj1.run();//获取一个对象的行为
8.3、创建对象的第三种方式
let obj2 = {//注意点:属性名称和取值之间用冒号隔开,属性和属性之间用逗号隔开
name:"JJ",
age:"3",
say:function () {
console.log("不会说啊");
}
};
console.log(obj2.name);
console.log(obj2.age);
obj2.say();
10.3、JavaScript-方法和函数的区别
1.什么是函数?
函数就是没有和其他的类显示的绑定在一起的,就称为函数。可以直接调用
2.什么是方法?
方法就是显示的和其他的类绑定在一起的,就称为方法。不能直接调用,要通过对象来调用
3.函数和方法的区别
3.1、函数可以直接调用,但是方法不能直接调用,只能通过对象来调用
3.2、函数内部的this输出的是window,方法内部的this输出的是当前调用的那个对象。
4.无论是函数还是方法,内部都有一个叫做this的小可爱
this是谁调用了当前的函数或者方法,那么当前的this就是谁
function demo() {
console.log("JJ");
console.log(this);//打印Window,是全局对象
}
// demo();//函数
window.demo();//完整写法( window:系统内置的全局对象,详见DOM)
let obj = {
name:"JJ",
say:function () {//方法
console.log("坏");
console.log(this);//{name: "JJ", say: ƒ},obj调用say,this是obj
}
};
obj.say();
10.4、JavaScript-工厂函数
创建了对象人,但是想要多个人就会产生冗余代码,因此引入工厂函数
1.什么是工厂函数
工厂函数就是专门用于创建对象的函数,我们就称之为工厂函数
/*多个人的创建,有代码冗余*/
let obj = {
name:"JJ",
age:"3",
say:function () {
console.log("不会说啊");
}
};
let obj = {
name:"GG",
age:"18",
say:function () {
console.log("会说啊");
}
};
引入工厂函数
function createPerson(myName, myAge) {
let obj = new Object();
obj.name = myName;
obj.age = myAge;
obj.say = function () {
console.log("说啊");
}
return obj;
}
let obj1 = createPerson("GG", 18);
let obj2 = createPerson("JJ", 3);
console.log(obj1, obj2);
10.5、JavaScript-构造函数
工厂函数批量创建对象的方法不专业,因此引入构造函数
1.什么是构造函数?
构造函数和工厂函数一样,都是专门用于创建对象的
构造函数本质上是工厂函数的简写
2.构造函数和工厂函数的区别
2.1、构造函数的函数名称首字母必须大写
2.2、构造函数只能通过new来调用
3、当我们new Person("JJ", 3);时,系统做了什么事情
3.1、会在构造函数中自动创建一个对象 (let obj = new Object();)
3.2、会自动将刚才创建的对象赋值给this (let this = obj;)
3.3、会在构造函数的最后自动添加 (return this;)
function Person(myName, myAge) {
//let obj = new Object();//系统自动添加的
//let this = obj;//系统自动添加的
this.name = myName;//相当于obj.name = myName;
this.age = myAge;
this.say = function () {
console.log("坏");
}
//return this;//系统自动添加的
}
let obj1 = new Person("JJ", 3);
let obj2 = new Person("GG", 18);
console.log(obj1, obj2);
4.目前构造函数小问题
4.1、方法中可以调用对象么
function Person(myName, myAge) {//构造函数
this.name = myName;//相当于obj.name = myName;
this.age = myAge;
this.say = function () {
console.log("坏");
//方法中的this:谁调用就是谁。obj1调用了say方法,this就是obj1
console.log(this.name, this.age);
}
}
let obj1 = new Person("JJ", 3);//创建对象
console.log(obj1.name, obj1.age);// 坏 JJ 3 外部访问对象
obj1.say();//JJ 3 内部访问对象
let obj2 = new Person("GG", 18);
obj2.say();//坏 GG 18
4.2、两个对象的相同的say实在一块内存空间中么?
判断是否在同一内存中的方法:===
function demo() {
console.log("demo");
}
/*通过===三个等号来判断两个函数名称,表示判断两个函数是否都存储在同一块内存中。*/
console.log(demo === demo);//true
判断方法say存储空间
console.log(obj1.say === obj2.say);//false 说明obj1和obj2的say不是存储在同一个内存上,不是同一个东西
由于两个对象中的say方法的实现都是一样的,但是保存到了不同的存储空间中。所以有性能问题
10.6、JavaScript-构造函数优化
1.优化存储性能一
function mySay() {
console.log("坏");
}
console.log(mySay === mySay);//true
function Person(myName, myAge) {//构造函数
this.name = myName;//相当于obj.name = myName;
this.age = myAge;
this.say = mySay;
}
let obj1 = new Person("JJ", 3);//创建对象
let obj2 = new Person("GG", 18);//创建对象
console.log(obj1.say === obj2.say);//false变为true,存储在了同一块存储空间
2.当前这种方式,解决存储问题之后存在的弊端
2.1、阅读性降低了
2.2、污染了全局的命名空间
3.优化存储性能二
同一个对象中的函数,地址一样。以此想法继续优化存储性能
let fns = {
mySay:function () {
console.log("坏");
}
}
console.log(fns.mySay === fns.mySay);//true
function Person(myName, myAge) {//构造函数
this.name = myName;//相当于obj.name = myName;
this.age = myAge;
this.say = fns.mySay;
}
let obj1 = new Person("JJ", 3);//创建对象
let obj2 = new Person("GG", 18);//创建对象
console.log(obj1.say === obj2.say);//true
let fns创建对象的方法不够专业。
4.优化存储性能三
由于console.log(Person.prototype);返回一个对象,因此不用专门定义fns对象
function Person(myName, myAge) {//构造函数
this.name = myName;//相当于obj.name = myName;
this.age = myAge;
// this.say = fns.mySay;
}
Person.prototype = {//解决性能和全局命名匮乏的最专业的方法
say:function () {
console.log("坏");
}
}
let obj1 = new Person("JJ", 3);//创建对象
obj1.say();//坏
let obj2 = new Person("GG", 18);//创建对象
obj2.say();//坏
console.log(obj1.say === obj2.say);//true
10.7、JavaScript-prototype特点
1.prototype特点
1.1、存储在prototype中的方法可以被对应构造函数创建出来的所有对象共享
1.2、prototype中除了可以存储方法以外,还可以存储属性
1.3、prototype中如果出现了和构造函数中同名的属性或者方法,对象在访问的时候,访问到的是构造函数中的数据。
2.prototype应用场景
prototype中一般情况下用于存储所有对象都相同的一些属性以及方法。
如果是对象特有的属性或者方法,我们会存储到构造函数中。
function Person(myName, myAge) {//构造函数
this.name = myName;//相当于obj.name = myName;
this.age = myAge;
this.currentType = "构造函数中的人"
}
Person.prototype = {
currentType:"人",
say:function () {
console.log("坏");
}
}
let obj1 = new Person("JJ", 3);//创建对象
console.log(obj1.currentType); //构造函数中的人
10.8、JavaScript-对象三角恋关系
1.每个“构造函数”中都有一个默认的属性,叫做prototype,prototype属性保存着一个对象,这个对象我们称之为“原型对象”。
2.每个“原型对象”中都有一个默认的属性,叫做constructor,constructor指向当前原型对象对应的那个“构造函数”。
3.通过构造函数创建出来的对象我们称之为“实例对象”,每个“实例对象”中都有一个默认的属性,叫做_proto_,_proto_指向创建它的那个构造函数的“原型对象”。
function Person(myName, myAge) {//构造函数
this.name = myName;//相当于obj.name = myName;
this.age = myAge;
}
Person.prototype = {
say:function () {
console.log("坏");
}
}
let obj1 = new Person("gege", 18);//obj1:实例对象
console.log(Person.prototype);//拿到原型对象
console.log(Person.prototype.constructor);//构造函数
console.log(obj1);//实例对象
console.log(obj1.__proto__);//拿到原型对象
10.9、JavaScript-Function函数
1、JavaScript中函数是引用类型(对象类型),既然是对象,所以也是通过构造函数创建出来的,“所有函数”都是通过Function构造函数创建出来的实例对象(Person构造函数也是通过Function函数创建出来的实例对象)
2、JavaScript中只要是“函数”就有prototype属性。“Function函数”的prototype属性指向“Function原型对象”
3、JavaScript中只要“原型对象”就有constructor属性,“Function原型对象”的constructor指向它对应的构造函数。
console.log(Function);//Function构造函数
console.log(Function.prototype);//原型对象
console.log(Function.prototype.constructor);//Function构造函数
console.log(Function === Function.prototype.constructor);//true,都是相同的东西
function Person(myName, myAge) {//构造函数
this.name = myName;//相当于obj.name = myName;
this.age = myAge;
}
Person.prototype = {
say:function () {
console.log("坏");
}
}
console.log(Person.__proto__);//Function原型对象
console.log(Person.__proto__ === Function.prototype);//true 都是Function函数的原型对象
10.10、JavaScript-Object函数
console.log(Function.__proto__);
console.log(Function.__proto__ === Function.prototype);//true 都指向Function的原型对象。
console.log(Object); //Object() { [native code] }
console.log(Object.__proto__); //指向Function原型对象
console.log(Object.__proto__ === Function.prototype); //true
console.log(Object.prototype); //原型对象
console.log(Object.prototype.constructor); //Object的构造函数
console.log(Object.prototype.constructor === Object); //true
console.log(Object.prototype.__proto__); //null
10.11、JavaScript-函数对象完整关系图
1.强调
1.1、Function函数是所有函数的祖先函数
1.2、所有构造函数都有一个prototype属性
1.3、所有原型对象都有一个constructor属性
1.4、所有函数都是对象(引用类型)
1.5、所有对象都有一个__proto__属性
2.原型对象使用建议
- 私有成员(一般就是非函数成员)放到构造函数中
- 共享成员(一般就是函数)放到原型对象中
- 如果重置了 prototype 记得修正 constructor 的指向
console.log(Function.prototype.__proto__ === Object.prototype); //true
console.log(Person.prototype.__proto__ === Object.prototype); //true
console.log(Person.prototype.__proto__ === Function.prototype.__proto__); //true
10.12、JavaScript-原型链
function Person(myName, myAge) {//构造函数
this.name = myName;//相当于obj.name = myName;
this.age = myAge;
}
Person.prototype = {
//注意点:为了不破坏原有的关系,在给prototype赋值的时候,需要在自定义的对象中手动添加constructor属性,手动的指定需要指向谁
constructor:Person,//手动添加constructor属性,不然指向会出现问题
say:function () {
console.log("坏");
}
}
console.log(Person.prototype.constructor);//指向构造函数
想找对象say(),通过原型链查找。
先在自己的构造函数中找,没有的话顺着__proto__的指向在原型对象中找,若还没有再顺着__proto__的指向在Object原型对象中找,没有时就报错。这就是原型链的应用
10.13、JavaScript-属性注意点
function Person(myName, myAge) {//构造函数
this.name = myName;//相当于obj.name = myName;
this.age = myAge;
}
Person.prototype = {//修改了构造函数Person的原型对象
constructor:Person,//手动添加constructor属性,不然指向会出现问题
currentType:"人",
say:function () {
console.log("坏");
}
}
let obj = new Person("JJ", 3);//创建对象
console.log(obj.currentType); //人。先看构造函数有没有,没有通过原型链找到原型对象查找,有了就输出“人”
console.log(obj.__proto__.currentType); //人。直接找到原型对象查找,有就输出“人”
/*
注意点:在给一个对象(构造函数中)不存在的属性设置值的时候,不会去原型对象中查找,如果当前对象没有就会给当前对象(构造函数中)新增一个不存在的属性
*/
obj.currentType = "新设置的值";
console.log(obj.currentType); //新设置的值
console.log(obj.__proto__.currentType); //人
10.14、JavaScript-封装性
1.JS面向对象的三大特性
封装性,继承性,
2.局部变量和局部函数
无论是ES6之前还是ES6之后,只要定义一个函数就会开启一个新的作用域
只要在这个新的作用域中,通过let、var定义的变量就是局部变量
只要在这个新的作用域中,定义的函数就是局部函数
function demo() {//开启新的作用域
var num = 123;//定义变量,局部变量,只能在当前作用域中使用
let value = 456;
function test() {//定义函数,局部函数,只能在当前作用域中使用
console.log("test");
}
console.log(num, value);//123 456
test();//test
}
demo();
// console.log(num, value);//报错
// test();//报错
3.什么是对象的私有变量和函数
默认情况下对象中的属性和方法都是共有的,只要拿到对象就能操作对象的属性和方法
外界不能直接访问的变量和函数就是私有变量和私有函数
构造函数的本质也是一个函数,所以也会开启一个新的作用域,所以在构造函数中定义的变量和函数就是私有变量和私有函数。
4.什么是封装?
封装性就是隐藏实现细节,仅对外公开接口
function Person() {
this.name = "JJ";
// this.age = 3;
let age = 3;//私有变量,私有属性,不让别人改年龄
this.setAge = function (myAge) {//共有方法设置age。去除问题数据
if (myAge >= 0){
age = myAge
}
};
this.getAge = function () {//公有方法获取age
return age;
};
this.say = function () {
console.log("坏");
};
由于构造函数也是函数,所以也会开启一个新的作用域。
所以在构造函数中通过var、let定义的变量也是局部变量。又叫私有属性
所以在构造函数中定义的函数也是局部函数。又叫私有函数
var num = 123;//局部变量,私有属性
let value = 456;
function test() {//局部函数,私有函数
console.log("test");
}
}
let obj = new Person();
结论:默认情况下,对象得属性和方法都是共公开的,只要拿到对象,就可以操作对象的属性和方法。为了保密性可以使用私有属性。
console.log(obj.name);
obj.age = 55;
console.log(obj.age); //共有属性,年龄被更改。
obj.say();
// console.log(age);//私有属性,访问不了
obj.setAge(-5);
console.log(obj.getAge()); //3
5.为什么要封装?
- 不封装的缺点:当一个类把自己的成员变量暴露给外部的时候,那么该类就失去对该成员变量的管理权,别人可以任意的修改你的成员变量
- 封装就是将数据隐藏起来,只能用此类的方法才可以读取或者设置数据,不可被外部任意修改是面向对象设计本质(将变化隔离)。这样降低了数据被误用的可能 (提高安全性和灵活性)
10.15、JavaScript-私有属性注意点
function Person() {
this.name = "JJ";//公有属性
let age = 3;//私有变量,私有属性,不让别人改年龄
this.setAge = function (myAge) {//共有方法设置age。去除问题数据
if (myAge >= 0){
age = myAge
}
};
this.getAge = function () {//公有方法获取age
return age;
};
this.say = function () {
console.log("坏");
};
}
操作的是私有属性(局部变量)
let obj = new Person();
obj.setAge(-5);
console.log(obj.getAge());
操作公有属性
obj.age = -3;
console.log(obj.age);//-3
注意点:
在给一个对象不存在的属性设置值的时候,不会去原型对象中查找,如果当前对象没有就会给当前对象新增一个不存在的属性。
由于私有属性的本质就是一个局部变量,并不是真正的属性,所以如果通过对象.XXX的方式是找不到私有属性的,所以会给当前对象新增一个不存在的属性
10.16、JavaScript-属性方法分类
1.在JavaScript中属性和方法分类:两类
1.1、实例属性/实例方法
在企业开发中通过实例对象访问的属性,我们称之为实例属性
在企业开发中通过实例对象调用的方法,我们称之为实例方法
1.2、静态属性/静态方法
在企业开发中通构造函数访问的属性,我们就称之为静态属性
在企业开发中通构造函数调用的方法,我们就称之为静态方法
function Person() {
this.name = "JJ";
this.say = function () {
console.log("坏");
};
}
通过构造函数创建出来的对象,我们称之为“实例对象”
let obj = new Person(); //实例对象
console.log(obj.name); //根据实例对象访问属性:实例属性
obj.say(); //根据实例对象访问方法:实例方法
静态属性,静态方法
Person.num = 0; //静态属性
Person.run = function () { //静态方法
console.log("泡你妹");
}
console.log(Person.num);
Person.run();
10.17、JavaScript-继承方式一
儿子继承父亲的物品就是继承最好的体现
js中继承目的: 把子类型中共同的属性和方法提取到父类型中
较少代码的冗余度, 提升代码的复用性
继承方式一:借用原型链实现继承
- 直接将子类的原型对象修改为父类对象, 这样就能使用原型链上的属性和方法
function Person() {
this.age = 0;
this.name = null;
this.say = function () {
console.log(this.name, this.age);
}
}
let arr = new Person();
arr.name = "GG";
arr.age = 18;
arr.say();
Student和Person都有age、name、say。Student is a Person。学生是一个人。
在企业开发中,如果构造函数和构造函数之间的关系是is a的关系,那么就可以使用继承来优化代码,减少代码的冗余度。
继承不是构造函数中有相同代码就可以用。狗和人都会叫,但是不能继承
function Student() {
/* this.age = 0;
this.name = null;
this.say = function () {
console.log(this.name, this.age);
}*/
this.scorc = 0;
this.study = function () {
console.log("day day up");
}
}
Student.prototype = new Person();
//将Student的原型对象改为Person的实例对象
Student.prototype.constructor = Student;
//修改了原型对象后,将修改后对象的constructor改一下
let stu = new Student();
stu.age = 18;
stu.name = "JJ";
stu.say();
stu.scorc = 80;
stu.study();
方法一弊端:
儿子无法传递形式参数,父类可以传递参数,但是子类不可以。
function Person(myName,myAge) {
this.age = myAge;
this.name = myName;
this.say = function () {
console.log(this.name, this.age);
}
}
let per = new Person("JJ", 3);
per.say();
/*
儿子无法传递形式参数
*/
function Student(myName,myAge, myScore) {
this.scorc = 0;
this.study = function () {
console.log("day day up");
}
}
Student.prototype = new Person();
Student.prototype.constructor = Student;
10.18、JavaScript-bind-call-apply方法
1.什么是this
谁调用当前的方法或者函数,谁就是this
function test() {
console.log(this);
}
test();//相当于window.test();this就是window
function Person() {
this.name = "GG";
this.say = function () {
console.log(this);//Person
}
}
let per = new Person();
per.say();
2. 这三个方法的作用是什么
这三个方法都是用于修改函数或者方法中this的
2.1、bind方法的作用
修改函数或这方法中的this为指定的对象,并且会返回一个修改之后的新函数给我们
注意点:bind方法除了可以修改this以外, 还可以传递参数,只不过参数必须写在this对象的后面。
2.2、call方法的作用
修改函数或这方法中的this为指定的对象,并且会立即调用修改之后的函数
注意点:call方法除了可以修改this以外, 还可以传递参数,只不过参数必须写在this对象的后面。
2.3、apply方法的作用
修改函数或这方法中的this为指定的对象,并且会立即调用修改之后的函数
注意点:apply方法除了可以修改this以外, 还可以传递参数,只不过参数必须通过数组的方式传递,写在this对象的后面。
//1.修改函数的三种方式
let obj = {//定义一个对象
name:"GG",
};
function text(a, b) {
console.log(a, b);
console.log(this);
}
text(10, 20);//默认window,可以传参
//将text中的this改为指定的obj对象,将修改后的新函数返回
// let fn = text.bind(obj, 10, 20);//也可以传参
// fn();//{name: "GG"}
// text.call(obj, 10, 20);
text.apply(obj, [10, 20]);
//2.修改对象的三种方式
function Person() {
this.name = "GG";
this.say = function () {
console.log(this);
}
}
let p = new Person();
let fn2 = p.say.bind(obj);
fn2();
p.say.call(obj);
p.say.apply(obj);
10.19、JavaScript-继承方式二
继承方式二:借用构造函数实现继承
在子类中调用父类构造函数, 并且将父类构造函数的this修改为子类对象
function Person(myName,myAge) {
// let per = new Object();//构造函数自动添加
// let this = per;//构造函数自动添加
//this = stu
this.age = myAge;//stu.age = myAge
this.name = myName;//stu.name = myName;
this.say = function () {//stu.say = function () {
console.log(this.name, this.age);
}
// return this;//构造函数自动添加
}
function Student(myName,myAge, myScore) {
// let stu = new Object();//构造函数自动添加
// let this = stu;//构造函数自动添加
Person.call(this, myName,myAge,);// Person.call(Stu)将Person构造函数中的this改为stu。子类就可以传递参数啦
this.score = myScore;
this.study = function () {
console.log(this.score, "day day up");
}
// return this;//构造函数自动添加
}
let stu = new Student("JJ", 18, 60);
// stu.name = "GG";
// stu.age = 3;
// stu.score = 99;
stu.say();
stu.study();
方法二弊端:
在Person对象的原型中添加say方法后,stu无法找到Person原型中的say方法。
function Person(myName,myAge) {
this.age = myAge;//stu.age = myAge
this.name = myName;//stu.name = myName;
/*this.say = function () {//stu.say = function () {
console.log(this.name, this.age);
}*/
}
Person.prototype.say = function () {
console.log(this.name, this.age);
};
function Student(myName,myAge, myScore) {
Person.call(this, myName,myAge,);// Person.call(Stu)将Person构造函数中的this改为stu
this.score = myScore;
this.study = function () {
console.log(this.score, "day day up");
}
}
let stu = new Student("JJ", 18, 60);
stu.say(); //stu.say is not a function
stu.study();
10.20、JavaScript-继承方式三
借用构造函数+借用原型链组合继承
- 通过借用构造函数实现属性继承,可以使用父类构造函数的属性和方法
- 通过借用原型链实现方法继承,可以使用父类原型中的属性和方法
function Person(myName,myAge) {
this.age = myAge;//stu.age = myAge
this.name = myName;//stu.name = myName;
/*this.say = function () {//stu.say = function () {
console.log(this.name, this.age);
}*/
}
Person.prototype.say = function () {
console.log(this.name, this.age);
};
function Student(myName,myAge, myScore) {
Person.call(this, myName,myAge,);// Person.call(Stu)将Person构造函数中的this改为stu
this.score = myScore;
this.study = function () {
console.log(this.score, "day day up");
}
}
//注意点:要想使用Person原型对象中的属性和方法,那么就必须将Student的原型对象改为Person的原型对象
Student.prototype = Person.prototype;
Student.prototype.constructor = Student;
let stu = new Student("JJ", 18, 60);
// stu.name = "GG";
// stu.age = 3;
// stu.score = 99;
stu.say();
stu.study();
方法三弊端:
破坏了父类的三角恋关系。
父类同子类共用一个原型对象,若给子类添加属性方法,父类也会相应添加属性方法。污染父类原型
function Person(myName,myAge) {
this.age = myAge;//stu.age = myAge
this.name = myName;//stu.name = myName;
}
Person.prototype.say = function () {
console.log(this.name, this.age);
};
function Student(myName,myAge, myScore) {
Person.call(this, myName,myAge,);// Person.call(Stu)将Person构造函数中的this改为stu
this.score = myScore;
this.study = function () {
console.log(this.score, "day day up");
}
}
Student.prototype = Person.prototype;
Student.prototype.constructor = Student;
Student.prototype.run = function(){
console.log("run");
};
let per = new Person();
per.run(); //run。父类原型对象被污染
10.21、JavaScript-继承方式四
1.js中继承的终极方案
1.1、在子类的构造函数中通过call借助父类的构造函数
1.2、将子类的原型对象修改为父类的实例对象。
function Person(myName,myAge) {
this.age = myAge;//stu.age = myAge
this.name = myName;//stu.name = myName;
}
Person.prototype.say = function () {
console.log(this.name, this.age);
};
function Student(myName,myAge, myScore) {
Person.call(this, myName,myAge,);// Person.call(Stu)将Person构造函数中的this改为stu
this.score = myScore;
this.study = function () {
console.log(this.score, "day day up");
}
}
// Student.prototype = Person.prototype;
Student.prototype = new Person();
Student.prototype.constructor = Student;
Student.prototype.run = function(){
console.log("run");
};
let stu = new Student("JJ", 18, 60);
stu.say();
stu.study();
stu.run()//run
let per = new Person();
per.run();//per.run is not a function
10.22、JavaScript-多态
1.什么是强类型语言,什么是弱类型语言
1.1、什么是强类型语言:
一般编译型语言都是强类型语言。C/C++/JAVA
强类型语言,要求变量的使用要严格符合定义
例如定义 int num; 那么num中将来就只能够存储整型数据
1.2、什么是弱类型语言:
一般解释型语言都是弱类型语言,
弱类型语言,不会要求变量的使用要严格符合定义
例如定义let num; num中既可以存储整型,也可以存储布尔类型等。
1.3、由于js语言是弱类型的语言,所以我们不用关注多态
2.什么是多态?
多态是指事物的多种状态
例如:
按下F1键
若当前在webstrom界面下弹出的就是webstrom的帮助文档
若在word下弹出的就是word帮助
同一个事件发生在不同的对象上会产生不同的结果。
3.多态在编程语言种的体现
父类型变量保存子类型对象,父类型变量当前保存的对象不同,产生的结果也不同
11、JavaScript-面向对象-ES6
11.1、JavaScript-ES6类和对象
1.在ES6之前如何定义一个类?
通过构造函数来定义一个类
function Person(myName, myAge) {
//实例属性
// this.name = "JJ";
// this.age = 3;
this.name = myName;
this.age = myAge;
//实例方法
this.say = function () {
console.log(this.name, this.age);
};
//静态属性
Person.num = 666;//类,函数也是一个对象,因此可以添加方法
//静态方法
Person.run = function () {
console.log("run");
}
}
// let p = new Person();
let p = new Person("JJ", 3);
p.say();
console.log(Person.num);
Person.run();
2.从ES6开始,系统提供了一个名称叫做class的关键字,这个关键字就是专门用于定义类的
class Person{
//当我们通过new创建对象的时候,系统会自动调用constructor(构造方法)
constructor(myName, myAge){
this.name = myName;
this.age = myAge;
}
//实例属性
// name = "GG";
// age = 3;
//实例方法
say(){
console.log(this.name, this.age);
};
//静态属性
static num = 666;
//静态方法
static run() {
console.log("run");
}
}
// let p = new Person();
let p = new Person("JJ", 18);
p.say();
console.log(Person.num);
Person.run();
3.ES6注意点
a)、注意点一
以下定义“实例属性”的方法并不是ES6正式版标准中的写法,大部分的浏览器不支持
在ES标准中添加实例属性都需要在constructor中添加
class Person{
//实例属性
// name = "GG";
// age = 3;
constructor(){
this.name = "GG";
this.age = 3;
}
//实例方法
say(){
console.log(this.name, this.age);
};
}
let per = new Person();
console.log(per);
b)、注意点二
以下定义“静态属性”的方法并不是ES6正式版标准中的写法,大部分的浏览器不支持,会报错
在ES标准中static只支持定义静态方法,不支持定义静态属性(变量)
class Person{
//静态属性
// static num = 666;
//静态方法
static run() {
console.log("run");
}
}
Person.num = 666;//ES6中定义静态属性
let per = new Person();
console.log(per);
c)、注意点三
ES6以前
function Person(myName,myAge) {
//实例属性
this.age = myAge;
this.name = myName;
//实例方法
this.hi = function () {
console.log("hi");
}
}
//原型中的方法
Person.prototype.say = function () {
console.log(this.name, this.age);
};
let per = new Person();
console.log(per);
ES6
class Person{
constructor(myName,myAge){ //相当于在构造函数中写的东西
this.age = myAge;
this.name = myName;
this.hi = function () {
console.log("hi");
}
}
say(){ //这种写法会使say在原型上,相当于给原型添加的方法
console.log(this.name, this.age);
}
}
let per = new Person("GG", 18);
console.log(per);
d)、注意点四——ES6前后,定义原型对象的方法的方案
function Person(myName,myAge) {
//实例属性
this.age = myAge;
this.name = myName;
//实例方法
this.hi = function () {
console.log("hi");
}
}
ES6以前,想在原型上添加属性和方法有两种方案
方案一:原型上的方法
Person.prototype.type = "人";
Person.prototype.say = function () {
console.log(this.name, this.age);
};
方案二:自定义原型
Person.prototype = {
constructor:Person,
num: 3,
run:function () {
console.log("run");
}
};
let per = new Person("GG", 3);
console.log(per);
ES6,想在原型上添加属性和方法
class Person{
constructor(myName,myAge){//相当于在构造函数中写的东西
this.age = myAge;
this.name = myName;
this.hi = function () {
console.log("hi");
}
}
say(){//在原型上添加方法
}
}
方案一:原型上的方法。只能用此方法
Person.prototype.type = "人";
Person.prototype.say = function () {
console.log(this.name, this.age);
};
方案二:自定义原型对象方式,不可取,因为会同class中定义的原型对象的方法冲突
let obj = {
constructor:Person,
nun:"123",
run:function () {
console.log(run);
}
};
Person.prototype = obj;
let per = new Person("GG", 3);
console.log(per);
11.2、JavaScript-ES6继承
1. ES6之前的继承
1.1、在子类的构造函数中通过call借助父类的构造函数
1.2、将子类的原型对象修改为父类的实例对象。
function Person(myName,myAge) {
this.age = myAge;//stu.age = myAge
this.name = myName;//stu.name = myName;
}
Person.prototype.say = function () {
console.log(this.name, this.age);
};
function Student(myName,myAge, myScore) {
Person.call(this, myName,myAge,);// Person.call(Stu)将Person构造函数中的this改为stu
this.score = myScore;
this.study = function () {
console.log(this.score, "day day up");
}
}
// Student.prototype = Person.prototype;
Student.prototype = new Person();
Student.prototype.constructor = Student;
Student.prototype.run = function(){
console.log("run");
};
let stu = new Student("JJ", 18, 60);
stu.say();
stu.study();
stu.run()//run
2. ES6的继承
class Person{//父类
constructor(myName, myAge){
this.name = myName;//相当于stu.name = myName;
this.age = myAge;//相当于stu.age = myAge;
}
say(){
console.log(this.name, this.age);
}
}
//以下代码的含义:告诉浏览器将来Student这个类需要继承Person这个类
class Student extends Person{
constructor(myName, myAge, myScore){
//找到父类构造函数,将其中的this修改为stu。并将参数传递给父类的name,age
super(myName, myAge);//相当于 Person.call(this, myName,myAge,);
this.score = myScore;
}
study(){
console.log("up up");
}
}
let stu = new Student("GG", 3, 90);
stu.say();
stu.study();
11.3、JavaScript-ES6获取对象类型
1.如何获取构造函数名称
let obj = new Object(); ——>object
let arr = new Array(); ——>array
let p = new Person(); ——>person
2. 用原有方法typeof实验
let obj = new Object();
console.log(typeof obj);//object
let arr = new Array();
console.log(typeof arr);//object
function Person(){
//let obj = new Object();
//let this = obj;
this.name = "GG";
this,age = 8;
this.say = function () {
console.log(this.name, this.age);
}
// return this;
}
let p = new Person();
打印的还是object的原因:
构造函数本质是工厂函数。构造函数在创建对象的时候系统默认let obj = new Object();创建,因此本质上还是创建了一个object
console.log(typeof p);//object
3. 如何输出的是对象真实的构造函数的名称array、Person,而不是object呢?
在arr实例对象中找constructor,没有就看原型对象中有没有,有,原型中的constructor指向Array构造函数。 构造函数中有一个name属性可以获取名称
let arr2 = new Array();
// console.log(typeof arr);//object
console.log(arr2.constructor.name); //拿到构造函数的name属性.打印后为Array
function Person2(){
this.name = "GG";
this,age = 8;
this.say = function () {
console.log(this.name, this.age);
}
}
let p2 = new Person2();
console.log(p2.constructor.name);//Person
11.4、JavaScript-instanceof关键字
1.什么是instanceof关键字
instanceof用于判断“对象”是否是指定构造函数的“实例”
class Person{
name = "GG";
}
let p = new Person();
console.log(p instanceof Person);//判断p这个实例对象是否是通过Person构造函数创建出来的。若是返回true
class Cat{
name = "江";
}
let c = new Cat();
console.log(c instanceof Person);//false
2.instanceof关键字注意点
只要构造函数的原型对象出现在实例对象的原型链中都会返回true
function Person2(myName) {
this.name = myName;
}
function Student(myName, myScore) {
Person2.call(this, myName);
this.score = myScore;
}
Student.prototype = new Person2();
Student.prototype.constructor = Student;
let stu = new Student();
返回true的原因:
instanceof在判断stu实例对象是否是通过Person2构造函数创建出来的时候。只要指定构造函数的原型对象出现在了当前实例对象的原型链中,就会返回true
console.log(stu instanceof Person2);//true
11.5、JavaScript-isPrototypeOf属性
1.什么是isPrototypeOf属性
isPrototypeOf用于判断一个对象时候是另一个对象的原型
class Person{
name = "GG";
}
let p = new Person();
console.log(Person.prototype.isPrototypeOf(p));//true
class Cat{
name = "江";
}
let c = new Cat();
console.log(Cat.prototype.isPrototypeOf(p));//false
2.isPrototypeOf属性注意点
只要原型对象在实例对象的原型链中,就返回true
function Person2(myName) {
this.name = myName;
}
function Student(myName, myScore) {
Person2.call(this, myName);
this.score = myScore;
}
Student.prototype = new Person2();
Student.prototype.constructor = Student;
let stu = new Student();
console.log(Person2.prototype.isPrototypeOf(stu));//true
返回true的原因:
只要原型对象在实例对象的原型链中,就返回true
11.6、JavaScript-判断对象属性
需求:判断某一个对象或者原型对象中是否拥有某一个属性
in的特点:只要类中或者原型对象中有,就会返回true
class Person{
name = null;
age = 0;
}
Person.prototype.height = 0;
let p = new Person();
console.log("name" in p);//对象p中是否有名称叫做name的属性。拥有返回true
console.log("width" in p);
console.log("height" in p);//true
需求:判断某一个对象自身是否拥有某一个属性
console.log(p.hasOwnProperty("height"));//false
console.log(p.hasOwnProperty("name"));//true
11.7、JavaScript-对象增删改查
class Person{}
let p = new Person();
增加C
p.name = "GG";//方式一
p["age"] = "13";//方式二
p.say = function(){
console.log("妹");
};
p["run"] = function(){
console.log("run");
};
console.log(p);
删除R
delete p["name"];
delete p.age;
delete p.run;
delete p["say"];
console.log(p);
修改U
p.name = "JJ";
p["age"] = "18";
p.say = function(){
console.log("哥");
};
p["run"] = function(){
console.log("run2");
};
console.log(p);
查询D
console.log(p.name);
console.log(p["name"]);
p.say();
11.8、JavaScript-对象的遍历
1.在JavaScript中对象和数组一样是可以遍历的
2.什么是对象的遍历?
对象的遍历就是依次取出对象中所有的属性和方法
3.如何遍历一个对象?
在JS中可以通过高级for循环来遍历对象
以下代码的含义:将指定对象中所有的属性和方法的名称取出来依次的赋值给key这个变量
for(let key in obj){
}
class Person{
constructor(myName,myAge){//相当于在构造函数中写的东西
this.age = myAge;
this.name = myName;
this.hi = function () {
console.log("hi");
}
}
//注意点:ES6定义类的格式,会将方法默认放到原型对象中
say(){//在原型上添加方法
console.log(this.name, this.age);
}
}
let p = new Person("GG", 2);
console.log(p);//查看say是否在原型对象中
for (let key in p){
console.log(key);
}
/*没有say,因为say默认在原型对象中,原型对象中取不出*/
function Person2(myName, myAge) {
this.name = myName;
this.age = myAge;
this.say = function () {
console.log(this.name, this.age);
};
}
let p2 = new Person2("JJ", 3);
for (let key in p2){
if(p2[key] instanceof Function){//函数不打印
continue
}
console.log(key);//属性和方法有哪些
注意点:以下代码的含义取出p对象中名称叫做当前遍历到的名称的属性或方法的取值
console.log(p2[key]);
注意点:以下代码的含义取出p对象中名称叫做key的属性的取值
console.log(p2.key);
}
11.9、JavaScript-对象的解构赋值
1.注意点:对象的解构赋值和数组的解构赋值除了符号不一样,其他的一模一样
数组解构使用[]
对象解构使用{}
2.数组解构赋值的注意点
2.1、在数组解构赋值中,等号左边的格式必须和等号右边的格式一模一样,才能完全解构(完全解构:数组中的每个值都取出来)
let [d, e, f] = [1, 2, 3];
2.2、在数组的解构赋值中,左边的个数可以和右边的个数不一样
2.3、在数组的解构赋值中,如果右边的个数和左边的个数不一样(左大于右),那么我们可以给左边指定默认值
let [d, e = 666, f = 999] = [1];
let obj = {
name : "GG",
age : "4",
};
/*不用解构赋值,取出对象中的属性*/
let name = obj.name;
let age = obj.age;
console.log(name, age);//GG 4
/* 使用解构赋值取值*/
let {name, age} = obj;
// 相当于let {name, age} = {name : "GG",age : "4",};
console.log(name, age);//GG 4
/*左边的个数小于右边*/
let {name} = obj;
console.log(name);//GG
/*左边多于右边*/
let {name, age, height} = obj;
console.log(name, age, height);//GG 4 undefined
/*左边多于右边,指定默认值*/
let {name, age, height = 1.8} = obj;
console.log(name, age, height);//GG 4 1.8
/*
本条以上的解构赋值都通数组类似。本条为不同点:
数组中左边变量名称可以随便写
在对象的解构赋值中,左边的变量名称必须和对象的属性名称一致,才能够解构出数据
*/
let {name1, age1} = obj;
console.log(name1, age1);//undefined undefined
/*找对应名称*/
let {age} = obj;
console.log(age);//4
11.10、JavaScript-对象的解构赋值应用场景
1、数组解构赋值的应用
/*定义函数求arr数组内两个数的和*/
1.1、老方法
let arr = [1, 3];
function sum(a, b) {
return a + b;
}
let res = sum(arr[0], arr[1]);
console.log(res);//4
1.2、解构赋值的方法
let arr = [1, 3];
/*定义函数求arr数组内两个数的和*/
function sum([a, b]) {
return a + b;
}
let res = sum(arr);
console.log(res);//4
2、 对象解构赋值的应用
/*定义函数,说出人的年龄姓名*/
2.1、老方法
let obj = {
name:"GG",
age:4,
}
function say(name, age) {
console.log(name, age);
}
say(obj.name, obj.age);//GG 4
2.2、使用解构赋值
let obj2 = {
name:"GG",
age:4,
}
function say2({name, age}) {
console.log(name, age);
}
say2(obj2);//GG 4
12、JavaScript-其他
12.1、JavaScript-深拷贝和浅拷贝
1.什么是深拷贝什么是浅拷贝
1.1、深拷贝
修改新变量的值不会影响原有变量的值
默认情况下基本数据类型都是深拷贝
1.2、浅拷贝
修改新变量的值会影响原有变量的值
默认情况下引用类型都是浅拷贝
深拷贝:修改num2(新变量)的值,不会影响num1(原有变量)的值
let num1 = 123;//在内存中开辟空间给num1存储123
let num2 = num1;//在内存中开辟空间给num2存储num1的值
num2 = 666;//修改新变量的值,用666覆盖num2中的值。
console.log(num1, num2);//123 666
浅拷贝:修改p2指向对象的属性值,也会修改p1指向对象的属性值
class Person{
name = "GG";
age = 3;
}
let p1 = new Person();
/*
let p1:内存中分配变量p1的空间,
new Person();并且给new出来的对象分配存储空间。
let p1 = new Person();并将对象的地址复制给变量
*/
let p2 = p1;//将p1的值赋值给p2
p2.name = "JJ";//用JJ覆盖p2指向对象中的name
console.log(p1.name, p2.name);//JJ JJ
12.2、JavaScript-对象深拷贝
1.引用类型是浅拷贝的主要原因:
多个变量指向了同一块存储空闲
多个变量操作的是用一块存储空间
2.将引用类型变为深拷贝的方法:
多个变量指向不同存储空间
class Person{
name = "GG";
age = 3;
}
let p1 = new Person();
//浅拷贝
let p3 = p1;
p3.name = "JJ";
//深拷贝
let p2 = new Object();//开辟新的存储空间
/*
方法一:(low,麻烦)
p2.name = p1.name;//将p1中的name和值copy给p2
p2.age = p1.age;
*/
/*
方法二:遍历(属性多时非常方便,但还是low)
for (let key in p1){
p2[key] = p1[key];
}
*/
/*
方法三:assign方法,
传递两个参数。将参数二中对象的属性和方法拷贝到参数一中
*/
Object.assign(p2, p1); //p1复制给p2
p2.name = "ZZ";
console.log(p1.name, p2.name, p3.name); //JJ ZZ JJ
3.上文的三种深拷贝方法仅适用于对象中都是基本数据类型的情况。
class Person{
name = "GG";//基本数据类型
cat = {//引用数据类型
age : 3,
};
score = [1, 3, 5];//引用数据类型
}
let p1 = new Person();
let p2 = new Object();
/*基本数据类型深拷贝*/
p2.name = p1.name;
p2.name = "JJ";
console.log(p1.name, p2.name);//GG JJ
/*引用类型深拷贝错误。*/
p2.cat = p1.cat;
p2.cat.age = 666;
console.log(p1.cat.age, p2.cat.age);//666 666
/*
真正的深拷贝,不管基本数据类型还是引用类型都可以使用
自定义函数depCopy
*/
depCopy(p2, p1);
console.log(p2);
function depCopy(target, scorce) {
//1.通过遍历拿到souece中所有的属性
for (let key in scorce){
//2.取出当前遍历到的属性对应的取值
let sourceValue = scorce[key];
// console.log(sourceValue);
//3.判断当前的取值是否是引用数据类型
if (sourceValue instanceof Object){
//console.log(sourceValue.constructor);//拿到对象的构造函数
//console.log(new sourceValue.constructor);//拿到对象的构造函数,new数组就创建数组,new对象就创建对象
let sunTarget = new sourceValue.constructor;//新建数组或对象
target[key] = sunTarget;
depCopy(sunTarget,sourceValue);//将取值中所有的数据copy到新建的数组或对象中。
}else {
target[key] = sourceValue;
}
}
}
12.3、JavaScript-调试技巧
12.4、JavaScript-数组高级API
讲解数组同函数、对象的高级用法
let arr = [1, 3, 5, 7]//定义数组,数组本质是对象
a)、需求:要求遍历数组
1.利用传统循环来遍历数组
for (let i = 0; i < arr.length; i++){
console.log(arr[i]);
}
2.利用高级循环forin来遍历
//注意点:在企业开发中不推荐使用forin循环来遍历数组,因为forin循环是专门用来遍历对象的,是遍历无序的。而数组是有序的。
for (let key in arr){//将索引依次赋值给key
console.log(arr[key]);
}
证明对象的属性是无序的:
function Person() {
this.name = "GG";
this.age = 3;
this.score = 99;
}
let p = new Person();//通过构造函数创建对象
console.log(p);
注意点:对象中的属性是无序的
forin循环是专门用来遍历对象的,但是对象的属性无序的。因此forin循环专门用于遍历无序的东西,所以不推荐使用forin循环来遍历数组。
3.利用ES6中推出的for... of循环遍历数组
for (let value of arr){
console.log(value);
}
4.还可以利用Array对象的forEach方法来遍历数组
forEach方法会自动调用传入函数。
每次调用都会将当前遍历到的元素和当前遍历到的索引和当前遍历的数组传递给这个函数。
arr.forEach(function (currentValue, currentIndex, currentArray) {
// console.log(currentValue, currentIndex, currentArray);//当前遍历到的元素取值,元素索引,被遍历的数组
console.log(currentValue);
})
5.forEach实现方法
Array.prototype.myForEach = function (fn) {
for (let i = 0; i < this.length; i++) {//this:谁调用就是谁。arr数组调用的就是这个数组。this === [1, 3, 5, 7]
fn(this[i], i, this);
}
};
arr.myForEach(function (currentValue, currentIndex, currentArray) {
console.log(currentValue, currentIndex, currentArray);
})
b)、数组的查找
let arr = [1, 3, 6, 5, 7, 6];
方法一:从左往右查找,找到返回索引,找不到返回-1
let index1 = arr.indexOf(6);
console.log(index1);//2
方法二:从右至左查找,找到返回索引,找不到返回-1
let index2 = arr.lastIndexOf(6);
console.log(index2);//5
方法三:从左往右查找,找到返回true,找不到返回false
let resulr = arr.includes(6);
console.log(resulr);//true
方法四:数组的findIndex方法
//findIndex方法:定制版的indexOf,找到返回索引,找不到返回-1
let index3 = arr.findIndex(function (currentValue, currentIndex, currentArray) {
if (currentValue === 6){
return true;
}
});
console.log(index3);//2
//数组的find方法
方法五:find方法:返回找到的元素值,找不到返回undefined
let value = arr.myFind(function (currentValue, currentIndex, currentArray) {
// console.log(currentValue, currentIndex, currentArray);
if (currentValue === 6){
return true;
}
});
console.log(value);
/*find方法实现原理*/
Array.prototype.myFind = function (fn) {
for (let i = 0; i < this.length; i++) {//this:谁调用就是谁。arr数组调用的就是这个数组。
fn(this[i], i, this);
}
};
arr.myFind(function (currentValue, currentIndex, currentArray) {
console.log(currentValue, currentIndex, currentArray);
});
c)、数组的过滤
d)、数组的映射
let arr = [1, 2, 3, 4, 5]
数组的过滤
1.数组的filter方法:
将满足条件的元素添加到一个新的数组中
将arr中偶数元素添加到新数组
let newArray = arr.filter(function (currentValue, currentIndex, currentArray) {//遍历arr数组是调用函数,并返回当前遍历到的元素,当前遍历到的索引以及当前遍历到的数组。
if (currentValue % 2 === 0) {
return true;//只要return为true,就会将当前currentValue添加到新的数组中,并返回
}
})
console.log(newArray);
数组的映射
2.数组的map方法:
将满足条件的元素映射到一个新的数组中
let newArray2 = arr.map(function (currentValue, currentIndex, currentArray) {//遍历arr数组是调用函数,并返回当前遍历到的元素,当前遍历到的索引以及当前遍历到的数组。
if (currentValue % 2 === 0) {
return currentValue;//只要return为true,就会将当前currentValue添加到新的数组中,并返回
}
})
console.log(newArray2);
12.5、JavaScript-删除数组元素注意点
需求:遍历的同时删除数组中所有元素
let arr = [1, 2, 3, 4, 5, 5, 9];
复习删除数组的方法:
arr.splice(0, 1);//从索引为0的元素开始,删除一个元素
console.log(arr);
delete arr[1];//删除数组第1个元素
console.log(arr);
使用for循环删除数组
//i递增,length递减。因此删除不干净。
// 0 0<6 删除
// 1 1<5 删除
// 2 2<4 删除
// 3 3<3 遍历结束
for (let i = 0; i < arr.length; i++){
console.log(arr.length);//6 5 4
arr.splice(i, 1);
}
存在问题:
删除索引为0的元素后,其余元素上移
删除索引为1的元素,索引为0的元素未删除,1以后元素上移
删除索引为2的元素,索引为0,1的元素未删除,2以后元素上移
以此类推,因此未删除干净
换个方法:下面也不行
let len = arr.length;
for (let i = 0; i < len; i++){
// console.log(arr.length);//6 5 4
arr.splice(i, 1);
}
console.log(arr);
*/
解决数组自动上移方法:
1.从数组的最后删除元素
2.使用delete删除数组
从数组的最后删除元素
let len = arr.length;
for (let i = len - 1; i >= 0; i--){
// console.log(arr.length);//6 5 4
arr.splice(i, 1);
}
console.log(arr);
使用delete删除数组
for (let i = 0; i < arr.length; i++){
console.log(arr.length);//6
//注意点:通过delete来删除数组中的元素,数组的length属性不会发生变化。
//splice是真的删除,delete是将空赋值给删除的元素
delete arr[i];
}
console.log(arr);// [empty × 6]
12.6、JavaScript-数组排序方法
let arr = ["c", "a", "b"];
arr.sort();//0: "a" 1: "b" 2: "c"
如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;
如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。
如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。
注意点:如果元素是字符串类型,那么比较的是字符串的Unicode编码。
arr.sort(function (a, b) {
if (a > b){
// return 1;//升序
return -1;//降序
} else if (a < b){
// return -1;//升序
return 1;//降序
} else{
return 0;
}
});
console.log(arr);
let arr2 = [3, 4, 2, 5, 1];
arr2.sort();//[1, 2, 3, 4, 5]
arr2.sort(function (a, b) {
//写法一:同字符串排序
/* if (a > b){
// return 1;//升序
return -1;//降序
} else if (a < b){
// return -1;//升序
return 1;//降序
} else{
return 0;
}*/
//写法二:
规律:如果数组中的元素是数值类型
如果需要升序排序,那么就返回a - b;
如果需要降序排序,那么就返回b - a;
return a-b;//升序
return b - a;//降序
});
console.log(arr2);
按照元素的长度排列
let arr3 = ["1234", "21", "54321", "123", "6", "321"];
arr3.sort(function (str1, str2) {
//字符串是特殊的数组,也有length属性
return str1.length - str2.length;//从短到长
// return str2.length - str1.length;//从长到短
});
console.log(arr3);
let student = [//数组中的每个元素都是一个对象
{name : "GG", age : 17},
{name : "JJ", age : 3},
{name : "zz", age : 1},
{name : "hd", age : 25},
];
student.sort(function (o1, o2) {
return o1.age - o2.age;
});
console.log(student);
12.7、JavaScript-字符串常用方法
在JS中字符串可以看做一个特殊的数组,所以大部分数组的属性/方法字符串都可以使用。
1.获取字符串长度 .length
let str = "avsd";
console.log(str.length);//4
2.获取字符 [索引] / charAt
let str2 = "adfvsd";
console.log(str2[3]);//v 高级浏览器才支持,兼容性不好
let ch = str2.charAt(1); //没有兼容性问题。
console.log(ch);//d
3.字符串查找 indexOf / lastIndexOf / includes
let str3 = "sdfsdfojid";
let index = str3.indexOf("d");
let index2 = str3.lastIndexOf("d");
let index3 = str3.includes("d");
let index4 = str3.includes("p");
console.log(index, index2, index3, index4);//1 9 true false
4.拼接字符串 concal / +
let str4 = "sefwoiefjocv";
let str5 = "sdf";
let str6 = str4 + str5;
let str7 = str4.concat(str5);//不推荐
console.log(str6, str7);
5.截取子串
let str8 = "sefwoiefjocv";
console.log(str8.slice(1, 6));//efwoi 数组的方式
console.log(str8.substring(1, 6));//efwoi 推荐使用
console.log(str8.substr(1, 6));//efwoie
6.字符串切割
let arr = [1, 2, 5, 6, 8, 9];
let str9 = arr.join(" ");//数组转为字符串
console.log(str9);//1 2 5 6 8 9
let arr2 = "1-2-5-6-8-9";
let str10 = arr2.split("-");//字符串转数组
console.log(str10);// ["1", "2", "5", "6", "8", "9"]
7.判断是否以指定字符串开头 ES6
let str11 = "jiangshiwangbadan";
console.log(str11.startsWith("jiang"));//true
8.判断是否以指定字符串结尾 ES6
let str12 = "jiangshiwangbadan";
console.log(str12.endsWith("dan"));//true
9.字符串模板 ES6
定义字符串可以使用反引号。反引号:esc下面的键
let str13 = `noinon`;
console.log(str13, typeof str13);//noinon string
下面的方法,写起来太麻烦
let str14 = "<ul>\n" +
" <li>我是第1li</li>\n" +
" <li>我是第2li</li>\n" +
" <li>我是第3li</li>\n" +
" <li>我是第4li</li>\n" +
" <li>我是第5li</li>\n" +
"</ul>";
console.log(str14);
let str15 = `<ul>
<li>我是第1li</li>
<li>我是第2li</li>
<li>我是第3li</li>
<li>我是第4li</li>
<li>我是第5li</li>
</ul>`
console.log(str15);
let name = "GG";
let age = 3;
let str16 = "我的名字是" + name + ",年龄是" + age;
console.log(str16);
let str17 =`我的名字是${name},年龄是${age}`;
console.log(str17);
12.8、JavaScript-基本数据类型和基本包装类型
1.基本数据类型
字符串类型 / 数值类型 / 布尔类型 / 空类型 / 未定义类型
2.通过字面量创建的基本数据类型的数据都是常量
3.常量的特点和注意点
常量是不能被修改的
每次修改或者拼接都是生成一个新的
666;//字面量创建数值类型的常量
"asd";//字面量创建字符串类型的常量
let str = "asd";
str[1] = "m";
console.log(str);//asd 常量不能被修改
let newStr = str.replace("a", "m");
//将b都替换成m。本质是生成新的字符串返回
console.log(str, newStr);//asd msd
let str2 = "sd";
let str3 = "ssdd";
let str4 = str2 + str3;
//生成新的str4返回,不会修改原常量。因此在企业开发中,不要过多的拼接字符串,会占用内存。存在性能问题。
console.log(str2, str3, str4);//sd ssdd sdssdd
4.基本类型特点
没有属性和方法
5.对象类型特点
有属性和方法
6.以前能访问基本数据类型的属性和方法,是因为在运行的时候系统自动将基本数据类型包装成了对象类型。
常见基本包装类型:
String() / Number() / Boolean()
let str5 = "inh";
str5.age = 5;
str5.say = function () {
console.log(hello);
};
console.log(str5.age);//undefined
str5.say();//str5.say is not a function 基本类型特点没有属性和方法
let str6 = "sdf";
//let str7 = new String(str6);//系统默认将基本数据类型创建为对象
str6.split(".");
12.9、JavaScript-三大对象
JavaScript中提供三种自带的对象, 分别是"本地对象"/"内置对象"/"宿主对象"
什么是宿主?
宿主就是指JavaScript运行环境, js可以在浏览器中运行,也可以在服务器上运行(nodejs)
1.本地对象
与宿主无关,无论在浏览器还是服务器中都有的对象
就是ECMAScript标准中定义的类(构造函数)。
在使用过程中需要我们手动new创建
例如:Boolean、Number、String、Array、Function、Object、Date、RegExp等。
2.内置对象
与宿主无关,无论在浏览器还是服务器中都有的对象
ECMAScript已经帮我们创建好的对象。
在使用过程中无需我们手动new创建
例如:Global(万物皆对象)、Math、JSON
3.宿主对象
对于嵌入到网页中的JS来说,其宿主对象就是浏览器,所以宿主对象就是浏览器提供的对象
包含: Window和Document等。都是在浏览器中使用的,服务器中无法使用
所有的DOM和BOM对象都属于宿主对象。
4.自定义对象
我们自己编写的类创建的对象
12.10、JavaScript-内置对象Math
Math.floor() 向下取整
Math.ceil() 向上取整
Math.round() 四舍五入
Math.abs() 绝对值
Math.random() 生成随机数
let num = -3.2;
//直接砍掉所有小数位
let value = Math.floor(num);
console.log(value);//-3
//只要有小数位就会给整数位+1,然后砍掉所有小数位
let value2 = Math.ceil(num);
console.log(value2);//-4
let value3 = Math.round(num);
console.log(value3);//-3
let value4 = Math.abs(num);
console.log(value4);//-3
let value5 = Math.random();
console.log(value5);//0-1的随机数