面向对象
面向对象编程介绍
面向过程思想
- 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
举个栗子∶将大象装进冰箱,面向过程做法。
1、打开冰箱门
2、大象装进去
3、关闭冰箱
面向过程,就是按照我们分析好了的步骤,按照步骤解决问题。
面向对象思想
- 面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。
举个栗子∶将大象装进冰箱,面向对象做法。
先找出对象,并写出这些对象的功能︰
1 大象对象
进去
⒉冰箱对象
打开
关闭
3 使用大象和冰箱的功能
面向对象是以对象功能来划分问题,而不是步骤。
ES6中的类和对象
面向对象的思维特点:
1.抽取(抽象)对象共用的属性和行为组织(封装)成一个类(模板);
2.对类进行实例化,获取类的对象。
面向对象编程我们考虑的是有哪些对象,按照面向对象的思维特点不断的创建对象,使用对象,指挥对象做事情
对象
在JavaScript中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等。
- 对象是由属性和方法组成的∶
- 属性︰事物的特征,在对象中用属性来表示(常用名词)
- 方法∶事物的行为,在对象中用方法来表示(常用动词)
类
类抽象了对象的公共部分,它泛指某一大类( class )对象特指某一个,通过类实例化一个具体的对象
创建类
语法
<script>
// 1. 创建类class
class className {
// class body
}
// 2. 创建对象new
new className();
</script>
类constructor构造函数
constructor()
方法是类的构造函数(默认方法),用于传递参数,返回实例对象,通过new命令生成对象实例时,自动调用该方法。
如果没有显示定义,类内部会自动给我们创建一个constructor()
<script>
// 1. 创建类class
class Star {
// new的时候自动调用constructor
constructor(uname, age) { // constructor可以传递参数
this.uname = uname;
this.age = age;
}
}
// 2. 创建对象new
var ldh = new Star("刘德华", 18); // constructor可以返回实例对象
var zxy = new Star("张学友");
console.log(ldh); // Star {uname: "刘德华", age: 18}
console.log(zxy); // Star {uname: "张学友", age: undefined}
console.log(ldh.age); // 18
console.log(zxy.uname); // 张学友
// (1) 通过class 关键字创建类, 类名我们还是习惯性定义首字母大写
// (2) 类里面有个constructor 函数,可以接受传递过来的参数,同时返回实例对象
// (3) constructor 函数 只要 new 生成实例时,就会自动调用这个函数, 如果我们不写这个函数,类也会自动生成这个函数
// (4) 生成实例 new 不能省略
// (5) 最后注意语法规范, 创建类 类名后面不要加小括号,生成实例 类名后面加小括号, 构造函数不需要加function(类中的方法都不加function)
</script>
在类中添加方法
语法:
class Person {
constructor (name, age) { // constructor构造器或者构造函数
this.name = name;
this.age = age;
}
say () {
console. log (this.name + "你好");
}
}
<script>
// 1. 创建类class
class Star {
// 类的共有属性放到constructor里面
constructor(uname, age) {
this.uname = uname;
this.age = age;
} // ,这里不加逗号
sing(song) {
console.log(this.uname + song);
}
}
// 2. 创建对象new
var ldh = new Star("刘德华", 18);
var zxy = new Star("张学友");
console.log(ldh); // 依然打印 Star {uname: "刘德华", age: 18} 因为new的时候只调用constructor,所以这里打印的内容不包括sing
console.log(zxy); // 依然打印 Star {uname: "张学友", age: undefined}
ldh.sing('冰雨'); // 刘德华冰雨 ldh调用了sing函数,那么sing中的this就指向ldh,那么sing中的this.name就是ldh.name
zxy.sing('李香兰'); // 张学友李香兰
// (1) 我们类里面所有的函数不需要写function
// (2) 多个函数方法之间不需要添加逗号分隔
</script>
类的继承
语法
// 1. 类的继承
class Father {
}
class Son extends Father {
// 此时已经实现了继承
}
举例
<script>
// 1. 类的继承
class Father {
constructor() {
}
money() {
console.log(100);
}
}
class Son extends Father {
//此时已经实现了继承
}
var son = new Son();
son.money(); // 100
</script>
super关键字
super 关键字用于访问和调用对象父类上的函数。
可以调用父类的构造函数,也可以调用父类的普通函数
super关键字调用父类构造函数举例
<script>
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
class Son extends Father {
constructor(x, y) {
//错误写法
// this.x = x;
// this.y = y;
//正确写法 调用父类中的构造函数
super(x, y);
}
}
var son = new Son(1, 2); //按照错误写法,这里的实参只能传到Son的构造函数里,传不到Father的构造函数中,那么sum就接收不到参数,无法进行运算
son.sum(); //3 son自己本身没有sum方法,但是son继承了father的方法,所以这里执行father.sum()
</script>
但是这样就可以
<script>
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
class Son extends Father {
constructor(x, y) {
super(x, y);
this.x = x;
this.y = y;
}
}
var son = new Son(1, 2); //按照错误写法,这里的实参只能传到Son的构造函数里,传不到Father的构造函数中,那么sum就接收不到参数,无法进行运算
son.sum(); //3
</script>
这样的写法也没错
<script>
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
class Son extends Father {
}
var son = new Son(1, 2);// son自己没有构造函数,那就是执行了父亲的构造函数,1和2都传给了父亲
son.sum(); // 3
</script>
但是下面这样写就会报错
<script>
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
class Son extends Father {
constructor(x, y) {
// 这样就会报错
}
}
var son = new Son(1, 2);
son.sum();
</script>
- 上述举例说明,子类重写父类的方法,就会覆盖从父类继承过来的方法,以下例子也是同样的道理
<script>
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
class Son extends Father {
constructor(x, y) {
super(x, y);
}
sum() {
console.log(this.x - this.y);
}
}
var son = new Son(1, 2);
son.sum(); // -1 子类的sum将父类的sum覆盖掉了
</script>
super关键字调用父类普通函数举例
<script>
// super 关键字调用父类普通函数
class Father {
say() {
return '我是爸爸';
}
}
class Son extends Father {
say() {
console.log(super.say() + '的儿子');
// super.say() 就是调用父类中的普通函数 say()
}
}
var son = new Son();
son.say();
// 继承中的属性或者方法查找原则: 就近原则
// 1. 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
// 2. 继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
</script>
子类继承父类方法的同时扩展自己的方法
注意:子类在构造函数中使用super,必须放到this前面(必须先调用父类的构造方法,在使用子类构造方法)
<script>
// 父类有加法方法
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
// 子类继承父类加法方法 同时 扩展减法方法
class Son extends Father {
constructor(x, y) {
// super 必须在子类this之前调用 super()放在第一行
super(x, y); //利用super 调用父类的构造函数 这样子类的参数5,3才会传给父类的构造函数,sum()才会接收到5,3
this.x = x;
this.y = y;
}
subtract() {
console.log(this.x - this.y);
}
}
var son = new Son(5, 3);
son.subtract();// 2
son.sum();// 8
</script>
- 以下这样的写法也是正确的,以下是子类有自己的构造方法,只是在自己的构造方法中调用了父类的构造方法
<script>
// 父类有加法方法
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
// 子类继承父类加法方法 同时 扩展减法方法
class Son extends Father {
constructor(x, y) {
// super 必须在子类this之前调用 super()放在第一行
super(x, y); // 利用super 调用父类的构造函数 这样子类的参数5,3才会传给父类的构造函数,sum()才会接收到5,3
console.log(this);// Son {x: 5, y: 3}
}
subtract() {
console.log(this.x - this.y);
}
}
var son = new Son(5, 3);
son.subtract();//2
son.sum();//8
</script>
- 注意:子类继承父类的构造方法以及子类,在自己的构造方法中调用父类构造方法的区别
ES6中类和对象的3个注意点
- 在ES6中类没有变量提升,所以必须先定义类,才能通过类实例化对象。
- 类里面的共有属性和方法一定要加this使用
<script>
class Star {
constructor(uname, age) {
this.uname = uname;
this.age = age;
this.sing(); // 在构造函数内部调用普通函数前面要加this 函数前面的this
}
sing() {
console.log(this.uname);// 函数里面的this
}
}
var ldh = new Star('刘德华'); // 类实例化对象放在类定义的后面 实例化的时候自动调用sing()
console.log(ldh); // Star {uname: "刘德华", age: undefined}
</script>
函数前面的this和函数里面的this都指向函数的调用者。
更深入的理解:
<button>点击</button>
<script>
class Star {
constructor(uname, age) {
this.uname = uname;
this.age = age;
this.btn = document.querySelector('button');
this.btn.onclick = this.sing;
// 对于这些共有的属性和方法的前面一定要加this ,因为这些属性和方法是属于实例对象的,而this就是指向我们的实例对象
// this.btn.onclick = this.sing;表示点击完成之后调用该函数
// this.btn.onclick = this.sing();表示实例化对象的时候就调用该函数
}
sing() {
console.log(this.uname);
}
}
var ldh = new Star('刘德华'); // 类实例化对象放在类定义的后面
console.log(ldh); // Star {uname: "刘德华", age: undefined, btn: button}
// 点击按钮之后调用sing()打印undefine
</script>
- 类里面的this指向问题。
- constructor里面的this指向实例对象,方法里面的this指向这个方法的调用者
<body>
<button>点击</button>
<script>
var that;
var _that;
class Star {
constructor(uname, age) {
this.uname = uname;
this.age = age;
this.btn = document.querySelector('button');
this.btn.onclick = this.sing;
// 1. constructor 里面的this 指向的是 创建的实例对象
that = this;
console.log(this); // Star {uname: "刘德华", age: undefined, btn: button}
}
sing() {
// 2. 普通函数的this指向的是它的调用者
console.log(this); // <button>点击</button> 这个sing方法里面的this 指向的是 btn 这个按钮,因为这个按钮调用了这个函数
console.log(this.uname); // undefine btn中没有uname这个属性,所以打印undefine
console.log(that.uname); // 刘德华 that里面存储的是constructor里面的this,所以打印刘德华
}
dance() {
// 2. 普通函数的this指向的是它的调用者
_that = this; // 这个dance里面的this 指向的是实例对象 ldh 因为ldh 调用了这个函数
console.log(this); // Star {uname: "刘德华", age: undefined, btn: button}
}
}
var ldh = new Star('刘德华');
console.log(that === ldh); // true 证明了constructor 里面的this 指向的是 创建的实例对象
ldh.dance();
console.log(_that === ldh); // true 证明了dance里面的this 指向的是实例对象 ldh 因为ldh 调用了这个函数
</script>
</body>
构造函数和原型
概述
在典型的OOP的语言中(如Java ),都存在类的概念,类就是对象的模板,对象就是类的实例,但在ES6之前,JS中并没用引入类的概念。
ES6,全称ECMAScript 6.0,2015.06发版。但是目前浏览器的JavaScript是ES5版本,大多数高版本的览器也支持ES6,不过只实现了ES6的部分特性和功能。
在ES6之前,对象不是基于类创建的,而是用一种称为构建函数的特殊函数来定义对象和它们的特征。
创建对象可以通过以下三种方式∶
- 1.对象字面量
- 2.new Object
- 3.自定义构造函数
构造函数
构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与new一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。
new在执行时会做四件事情:
- 在内存中创建一个新的空对象。
- 让this指向这个新的对象。
- 执行构造函数里面的代码,给这个新对象添加属性和方法。
- 返回这个新对象(所以构造函数里面不需要return)。
例子
- ES5中创建对象的方式(复习)
<script>
// 1. 利用 new Object() 创建对象
var obj1 = new Object();
// 2. 利用 对象字面量创建对象
var obj2 = {};
// 3. 利用构造函数创建对象
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我会唱歌');
}
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
console.log(ldh);//Star {uname: "刘德华", age: 18, sing: ƒ}
ldh.sing();
zxy.sing();
console.log(ldh.uname);//刘德华
</script>
- ES6中创建对象的方式(类)
<script>
// 1. 创建类class
class Star {
// new的时候自动调用constructor
constructor(uname, age) { // constructor可以传递参数
this.uname = uname;
this.age = age;
}
}
// 2. 创建对象new
var ldh = new Star("刘德华", 18); // constructor可以返回实例对象
console.log(ldh); // Star {uname: "刘德华", age: 18}
</script>
JavaScript的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部的this上添加。通过这两种方式添加的成员,就分别称为静态成员和实例成员。
静态成员∶在构造函数本上添加的成员称为静态成员,只能由构造函数本身来访问
实例成员∶在构造函数内部创建的对象成员称为实例成员,只能由实例化的对象来访问
<script>
// 构造函数中的属性和方法我们称为成员, 成员可以添加
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我会唱歌');
}
}
var ldh = new Star('刘德华', 18);
// 1.实例成员就是构造函数内部通过this添加的成员 uname age sing height就是实例成员
// JS是一门动态语言,可以直接这样添加实例成员,如下
ldh.height = 180;
// 实例成员只能通过实例化的对象来访问,如下
console.log(ldh.uname);
console.log(ldh.height);
ldh.sing();
console.log(ldh); //Star {uname: "刘德华", age: 18, height: 180, sing: ƒ}
// 实例成员不可以通过构造函数来访问实例成员,如下
// console.log(Star.uname);
// 2. 静态成员 在构造函数本身上添加的成员,如下:
Star.sex = '男'; //sex 就是静态成员
// 静态成员只能通过构造函数来访问
console.log(Star.sex); // JS是一门动态语言,可以直接这样添加静态成员
console.log(ldh); // {uname: "刘德华", age: 18, height: 180, sing: ƒ}
// 不能通过对象来访问
// console.log(ldh.sex);
</script>
构造函数的问题
<script>
// 1. 构造函数的问题.
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我会唱歌');
}
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
console.log(ldh.sing === zxy.sing);//false 比较这两个函数的时候比较的是地址
</script>
构造函数原型prototype
构造函数通过原型分配的函数是所有对象所共享
的。
JavaScript规定,每一个构造函数都有一个prototype属性,指向另一个对象。注意这个prototype就是个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法。
<script>
// 1. 构造函数的问题.
function Star(uname, age) {
this.uname = uname;
this.age = age;
// this.sing = function() {
// console.log('我会唱歌');
// }
}
Star.prototype.sing = function() { //在原型对象里添加sing这个方法 因为对象可以动态地添加属性和方法
console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
console.log(ldh.sing === zxy.sing); //true 比较这两个函数的时候比较的是地址
// console.dir(Star); // 发现prototype这个属性,这个属性的值是用{}表示的,可见Star的prototype属性就是一个对象
ldh.sing(); //我会唱歌
zxy.sing(); //我会唱歌
// 2.一般情况下,我们的公共属性定义到构造函数里面, 公共的方法我们放到原型对象身上
</script>
对象原型__proto__
对象都会有一个属性__proto__
指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__
原型的存在。
__proto__
对象原型和原型对象prototype是等价的__proto__
对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象prototype
<script>
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function() {
console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
ldh.sing();
console.log(ldh); // 对象身上系统自己添加一个 __proto__ 指向我们构造函数的原型对象 prototype
console.log(ldh.__proto__ === Star.prototype);// true
// 方法的查找规则: 首先先看ldh 对象身上是否有 sing 方法,如果有就执行这个对象上的sing
// 如果没有sing 这个方法,因为有__proto__ 的存在,就去构造函数原型对象prototype身上去查找sing这个方法
</script>
- 对象原型指向的是构造函数的原型对象,对象原型是一个非标准属性,实际开发的时候,不可以对其进行赋值等操作。
构造函数constructor
对象原型(__proto__
)和构造函数(prototype
)原型对象里面都有一个属性constructor属性,constructor我们称为构造函数,因为它指回构造函数本身
。
<script>
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function() {
console.log('我会唱歌');
};
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
console.log(Star.prototype);
console.log(ldh.__proto__);
</script>
- constructor主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
<script>
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 很多情况下,需要利用constructor 这个属性指回原来的构造函数
// Star.prototype.sing = function() {
// console.log('我会唱歌');
// }
// Star.prototype.movie = function() {
// console.log('我会演电影');
// }
// Star.prototype是一个对象,
// 如果采用Star.prototype.sing = function(){}的形式,那就是在这个对象里追加公共方法。
// 但是若采用第二种方法Star.prototype = {}添加公共方法,
// 这就是对Star.prototype这个对象赋值,将Star.prototype对象中原先的内容全部覆盖了,那么Star.prototype中就没有constructor这个属性了
Star.prototype = {
// 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
constructor: Star,
sing: function() {
console.log('我会唱歌');
},
movie: function() {
console.log('我会演电影');
}
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
ldh.sing();
// console.log(Star.prototype);
// console.log(ldh.__proto__);
console.log(Star.prototype.constructor);
console.log(ldh.__proto__.constructor);
</script>
构造函数、原型对象、实例之间的关系
(1)每一个构造函数里面都有一个原型对象,是通过构造函数Star.prototype指向这个原型对象的
(2)原型对象里面有一个属性constructor,它又指回了构造函数
(3)构造函数可以创建一个实例对象,只要new了构造函数就会产生一个实例对象
(4)实例里面也有一个对象原型__proto__
,它指向原型对象。所以__proto__
和prototype完全一样
(5)对象实例里也有一个constructor,它也可以指回构造函数
(6)实际上ldh.__proto__
指向的就是原型对象,原型对象里的constructor可以指回构造函数
原型链
Star.prototype和ldh一样也是一个对象,只要是对象那么就有对象原型__proto__
的存在;只要是对象原型那么它们指向的都是原型对象
<script>
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function() {
console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
// 1. 只要是对象就有__proto__ 原型, 指向原型对象
console.log(Star.prototype);//打印该对象,发现存在__proto__,__proto__里面的constructor指向的是Objcet.prototype
console.log(Star.prototype.__proto__ === Object.prototype);//true
</script>
Object.prototype是由Object创建出来的,同理在Object.prototype一定有一个constructor指回Object。
Object.prototype也是一个原型对象,那么他也有__proto__
,那么Object.prototype.__proto__
的内容是什么呢?答:null
<script>
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function() {
console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
// 1. 只要是对象就有__proto__ 原型, 指向原型对象
console.log(Star.prototype);//打印该对象,发现存在__proto__,__proto__里面的constructor指向的是Objcet.prototype
console.log(Star.prototype.__proto__ === Object.prototype);
// 2.我们Star原型对象里面的__proto__原型指向的是 Object.prototype
console.log(Object.prototype.__proto__);//null
// 3. 我们Object.prototype原型对象里面的__proto__原型 指向为 null
</script>
原型链就像一个链路一样,让我们在查找对象成员的时候一层一层地往上查找,如果最终还是没有找到,那么就返回null。
先看对象实例上有没有待查找的成员,如果无,那么就去Star的原型对象上查看是否有该成员,如果无,再去Object上的原型对象上查看是否有这个成员,如果还没有就返回null。(每一级的原型对象prototype里存放的是该机的constructor、__proto__
以及公共方法eg:sing)
JS中的成员查找机制
- 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
- 如果没有就查找它的原型(也就是
__proto__
指向的prototype原型对象) - 如果还没有就查找原型对象的原型( Object的原型对象)。
- 依此类推一直找到Object为止( null )。
__proto__
对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
<script>
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function() {
console.log('我会唱歌');
}
// Star.prototype.sex = '女';
// Object.prototype.sex = '男';
var ldh = new Star('刘德华', 18);
// ldh.sex = '男';
console.log(ldh.sex);// 上述3行注释,消除任意一行注释,都可以使这里正常输出
console.log(ldh.toString()); // toString()方法是Object才有的,这里根据链式查找,也是可以使用这个方法的
</script>
原型对象中的this指向问题
<script>
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
var that;
Star.prototype.sing = function() {
console.log('我会唱歌');
that = this;
}
var ldh = new Star('刘德华', 18);
// 1. 在构造函数中,里面this指向的是对象实例 ldh
ldh.sing();
// 2.原型对象的函数里面的this 指向的是 实例对象 ldh(函数指向它的调用者)
console.log(that === ldh);//true
</script>
扩展内置对象
可以通过原型对象,对原来的内置对象进行扩展自定义的方法。
比如给数组增加自定义求偶数和的功能。
对于数组本身就有很多内置的方法:反转数组,排序数组等。
<script>
console.log(Array.prototype);// 通过打印原型对象可以查看数组所有的内置方法
</script>
现在考虑为数组这些内置方法中再追加求和的方法
<script>
// 原型对象的应用 扩展内置对象方法
Array.prototype.sum = function() {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
};
// 以下是错误写法,如果这样写就会把原来数组里面的所有方法全部覆盖
// Array.prototype = {
// sum: function() {
// var sum = 0;
// for (var i = 0; i < this.length; i++) {
// sum += this[i];
// }
// return sum;
// }
// }
var arr = [1, 2, 3]; // arr是通过Array的构造函数创建出来的,这就是一个实例化的对象(像之前的ldh)
console.log(arr.sum()); // 6
console.log(Array.prototype);// 再次打印原型对象可以查看数组所有的内置方法
// 声明数组的另一个方法
var arr1 = new Array(11, 22, 33);
console.log(arr1.sum());// 66
</script>
注意∶数组和字符串内置对象不能给原型对象覆盖操作Array.prototype ={}
,只能是Array.prototype.xxx = function(){}
的方式
继承
继承
ES6之前并没有给我们提供extends继承。
可以通过构造函数+原型对象模拟实现继承,被称为组合继承
call()方法
- 可以调用某个函数
- 可以修改函数运行时的this指向(注意是修改此次运行时的this指向,而不是永久的this指向)
fun.call (thisArg, arg1, arg2, ...)
- thisArg:当前调用函数this的指向对象
- arg1 , arg2∶传递的其他参数
<script>
// call 方法
function fn(x, y) {
console.log(this); //修改之前this指向的是window,修改之后this指向的是o这个对象
console.log(x + y); //3
}
var o = {
name: 'andy'
};
// fn();传统的函数调用方法
// 1. call() 可以调用函数
fn.call(); //Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
// 2. call() 可以改变这个函数的this指向 此时这个函数的this 就指向了o这个对象
fn.call(o, 1, 2); //{name: "andy"}
</script>
利用构造函数继承父类的属性
- ES6之前是利用构造函数继承父类的属性;利用原型对象继承父类的方法。
- 利用构造函数继承父类的属性的核心方法是:通过call把父类型的this指向子类型的this,这样就可以实现子类型继承父类型的属性。
<script>
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname,age){
// 这里的this原本指向父构造函数的实例对象
this.uname = uname;
this.age = age;
}
// 2. 子构造函数
function Son(uname, age){
// 这里的this指向子构造函数的对象实例
}
</script>
现在上述两个构造函数没有任何关系,那么子构造函数如何才可以使用父构造函数的两个属性呢?
思路:将父构造函数中的this指向改成子构造函数的实例对象。
<script>
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例father
console.log(this);
this.uname = uname;
this.age = age;
}
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age); // 在调用父类构造方法的同时,将父类的this指向改为son
console.log(this);
this.uname = uname;
this.age = age;
this.score = score; // 这是子构造函数独有的属性
}
var son = new Son('刘德华', 18, 100); // Son {uname: "刘德华", age: 18, score: 100}
console.log(son); // Son {uname: "刘德华", age: 18, score: 100}
var farther = new Father('张学友', 20); // 父构造函数的this指向还是指向父构造函数的实例对象 Father {uname: "张学友", age: 20}
</script>
- 在子构造函数中写上
Father.call(this, uname, age);
这行代码就相当于将父构造函数的代码
借用原型对象继承父类型方法
之前提到:共有的属性写在构造函数里面,共有的方法写在原型对象里
(1)在父亲的原型对象中添加一个公共方法money()
<script>
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
this.uname = uname;
this.age = age;
}
// 父构造函数的方法
Father.prototype.money = function() {
console.log(1000000);
}
// 2 .子构造函数
function Son(uname, age, score) {
Father.call(this, uname, age);
this.score = score;
}
var son = new Son('刘德华', 18, 100);
console.log(son);
// 通过打印儿子可以看出,儿子没有继承父类的money(),儿子只是调用了父亲的构造函数,没有调用父亲的原型对象,所以儿子找不到父亲的money()
</script>
(2)若将父构造函数的原型对象赋值给子构造函数的原型对象,如下:
<script>
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
this.uname = uname;
this.age = age;
}
//父构造函数的方法
Father.prototype.money = function() {
console.log(1000000);
}
// 2 .子构造函数
function Son(uname, age, score) {
Father.call(this, uname, age);
this.score = score;
}
// 若将父构造函数的原型对象赋值给子构造函数的原型对象,如下
Son.prototype = Father.prototype;
var son = new Son('刘德华', 18, 100);
console.log(son);
// 打印儿子,可以发现在儿子的对象原型的指向中(指向Son构造函数的对象原型)果然出现了money方法
</script>
(3)再追加子构造函数专门的方法
<script>
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
this.uname = uname;
this.age = age;
}
// 父构造函数的方法
Father.prototype.money = function() {
console.log(1000000);
}
// 2 .子构造函数
function Son(uname, age, score) {
Father.call(this, uname, age);
this.score = score;
}
// 若将父构造函数的原型对象赋值给子构造函数的原型对象,如下
Son.prototype = Father.prototype;
// 子构造函数专门的方法
Son.prototype.exam = function() {
console.log("考试");
}
var son = new Son('刘德华', 18, 100);
console.log(son);
//此时再打印一下父构造函数的原型对象
console.log(Father.prototype);
// 此时出现问题:子构造函数的原型对象有从父构造函数中继承过来的money()也有自己独有的exam(),但是父构造函数的原型对象中也有子构造函数原型对象独有的方法exam()
</script>
出错原因:Son.prototype = Father.prototype;相当于将父原型对象的地址给子原型对象,即让子原型对象指向父原型对象。此时若修改子原型对象,那么父原型对象回一起被修改。那么正确的继承方法是什么呢?
<script>
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例father
this.uname = uname;
this.age = age;
}
//父构造函数的方法
Father.prototype.money = function() {
console.log(1000000);
}
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age); //在调用父类构造方法的同时,将父类的this指向改为son
this.score = score; //这是子构造函数独有的属性
}
//正确的继承方法
Son.prototype = new Father();
//子构造函数专门的方法
Son.prototype.exam = function() {
console.log("考试");
}
var son = new Son('刘德华', 18, 100);
console.log(son);
console.log(Father.prototype); // 父亲的原型对象中没有多添加子类的exam()
// console.log(Son.prototype.constructor);的时候,发现Son.prototype中的constructor指回的是父亲的构造函数
</script>
说明:Son.prototype = new Father();
- 父构造函数创建了一个实例对象,同时将其地址赋值给Son的原型对象,即让Son的原型对象指向这个Father的实例对象
- 而Father的实例对象可以通过自己的对象原型
__proto__
访问Father的原型对象prototype(链式查找) - 而Father的原型对象prototype中存放着money这个方法
原因:Son.prototype = new Father();等同于Son.prototype={}说明Son.prototype中的方法完全被覆盖了,Son就没有自己原先的constructor,修改办法:Son.prototype.constructor = Son;如下
<script>
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例father
this.uname = uname;
this.age = age;
}
// 父构造函数的方法
Father.prototype.money = function() {
console.log(1000000);
}
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age); //在调用父类构造方法的同时,将父类的this指向改为son
this.score = score; //这是子构造函数独有的属性
}
Son.prototype = new Father();
// 修改办法:如果利用对象的形式修改了原型对象,别忘了用constructor指回原来的构造函数
Son.prototype.constructor = Son;
// 子构造函数专门的方法
Son.prototype.exam = function() {
console.log("考试");
}
var son = new Son('刘德华', 18, 100);
console.log(son); //Son {uname: "刘德华", age: 18, score: 100}
console.log(Father.prototype);
console.log(Son.prototype.constructor); //!!!指向了父构造函数
</script>
ES5新增方法
- 数组方法
- 字符串方法
- 对象方法
数组方法
迭代(遍历)方法: forEach()、map()、filter()、some()、every() ;
forEach()方法
array.forEach ( function (currentvalue,index,arr))
- currentValue:数组当前项的值
- index:数组当前项的索引
- arr:数组对象本身
<script>
// forEach 迭代(遍历)数组
var arr = [1, 2, 3];
var sum = 0;
arr.forEach(function(value, index, array) {
console.log("每个数组的元素" + value);
console.log("每个数组的索引号" + index);
console.log("数组本身" + array);
// 求和
sum += value;
})
</script>
filter()方法
filter()方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,主要用于筛选数组
array.filter (function (currentvalue,index,arr))
- 注意它直接返回一个新数组
- currentValue:数组当前项的值
- index:数组当前项的索引
- arr:数组对象本身
<script>
// filter筛选数组
var arr = [12, 66, 4, 88,3,7];
var newArr = arr.filter(function(value, index) {
// 返回数组中值大于20的元素
// return value >= 20;
// 筛选所有的偶数
return value % 2 === 0
});
console.log(newArr); // [66, 88] // [12, 66, 4, 88]
</script>
some()方法
- some()方法用于检测数组中的元素是否满足指定条件。通俗点查找数组中是否有满足条件的元素
- 注意它返回值是布尔值如果查找到这个元素,就返回true,如果查找不到就返回false。
- 如果找到第一个满足条件的元素,则终止循环不在继续查找。
array.some (function (currentvalue,index,arr))
- currentValue:数组当前项的值
- index:数组当前项的索引
- arr:数组对象本身
例子
<script>
// some 查找数组中是否有满足条件的元素
var arr = [10, 30, 4];
var flag = arr.some(function(value) {
return value >= 20;
});
console.log(flag); // true 数组中有大于20的元素,所以返回true
var arr1 = ['red', 'pink', 'blue'];
var flag1 = arr1.some(function(value) {
return value == 'pink'; // 检查数组中是否有'pink'元素
});
console.log(flag1);// true
</script>
- filter 也是查找满足条件的元素,返回的是一个数组,而且是把所有满足条件的元素返回回来
- some也是查找满足条件的元素是否存在,返回的是一个布尔值,如果查找到第一个满足条件的元素就终止循环;value/index/array用到哪个参数就写哪个参数,用不到就省略不写
forEach和some的区别
<script>
var arr = ['red', 'green', 'blue', 'pink'];
// 1. forEach
arr.forEach(function(value, index) {
if (value == 'green') {
console.log('找到了该元素');
return true; // 在forEach里面return不会终止迭代,仅仅终止了本轮循环
}
console.log(index);
})
// 2.some
// 如果查询数组中唯一的元素,用some合适
arr.some(function(value, index) {
if (value == 'green') {
console.log('找到了该元素');
return true; // 在some里面return true终止迭代
}
console.log(index);
})
// 3.filter
arr.filter(function(value, index) {
if (value == 'green') {
console.log('找到了该元素');
return true; // 在filter里面return true不终止迭代,和forEach一样
}
console.log(index);
})
</script>
字符串方法
- trim()方法会从一个字符串的两端删除空白字符。
str.trim ()
- trim()方法并不影响原字符串本身,它返回的是一个新的字符串
<script>
//trim方法去除字符串两倍空格 不去除中间空格
var str = ' an dy ';
console.log(str); // an dy
var str1 = str.trim();
console.log(str1);//an dy
</script>
<body>
<input type="text"><button>点击</button>
<div></div>
<script>
var btn = document.querySelector('button');
var input = document.querySelector('input');
var div = document.querySelector('div');
//在input中输入:空格空格andy空格空格
btn.onclick = function() {
// 改进
var str = input.value.trim();
// if (input.value.trim() === '') {
if (str === '') {
alert('内容不能为空');
} else {
console.log(input.value); //空格空格andy空格空格
console.log(input.value.length); //8
div.innerHTML = input.value;
}
}
</script>
</body>
对象方法
Object.keys()方法
- Object.keys()用于获取对象自身所有的属性
object.keys(obj)
- 效果类似for…in
- 返回一个由属性名组成的数组
<script>
// 用于获取对象自身所有的属性名(得不到属性值)
var obj = {
id: 1,
pname: '小米',
price: 1999,
num: 2000
};
// 返回的是一个数组
var arr = Object.keys(obj);
console.log(arr);
// 后面需要使用属性名的时候将数组拿出来遍历就可以了
arr.forEach(function(value) {
console.log(value);
})
</script>
Object.defineProperty()方法
- Object.defineProperty()定义对象中新属性或修改原有的属性。
object.defineProperty(obj, prop,descriptor)
- obj :必需。目标对象
- prop :必需。需定义或修改的属性的名字
- descriptor :必需。目标属性所拥有的特性
以下是对第3个参数descriptor的说明,以对象{}的形式书写(以下的默认值都是在使用Object.defineProperty方法定义的属性时的默认值)
- value:设置属性的值,默认undefined;
- writable:属性值是否可以重写,true|false,默认false,其中true可以修改属性值;
- enumerable:目标属性是否可以被枚举遍历,true|false;默认false,false不允许遍历;
- configurable:目标属性是否可以被删除 或 是否可以再次修改特性,true|false;默认false,表示该属性不可以删除,而且 不可以再次修改descriptor里面所有参数
<script>
// Object.defineProperty() 定义新属性或修改原有的属性
var obj = {
id: 1,
pname: '小米',
price: 1999
};
// 1. 以前的对象添加和修改属性的方式
// obj.num = 1000;
// obj.price = 99;
// console.log(obj);
// 2. Object.defineProperty() 定义新属性或修改原有的属性
//(2)
Object.defineProperty(obj, 'num', {
value: 1000
});
console.log(obj); // {id: 1, pname: "小米", price: 1999, num: 1000}
//(3)
Object.defineProperty(obj, 'price', {
value: 9.9
});
console.log(obj); // {id: 1, pname: "小米", price: 9.9, num: 1000}
//(4)
Object.defineProperty(obj, 'id', {
writable: false,
});
obj.id = 2;// id无法被修改
console.log(obj); // {id: 1, pname: "小米", price: 9.9, num: 1000}
//(5)
Object.defineProperty(obj, 'address', {
value: '中国',
enumerable: false
});
console.log(obj); // { id: 1, pname: "小米", price: 9.9, num: 1000, address: "中国"}
console.log(Object.keys(obj)); // ["id", "pname", "price"] num和address都遍历不出来
//(6)
Object.defineProperty(obj, 'address', {
value: '中国',
configurable: false
});
delete obj.address;
console.log(obj); // {id: 1, pname: "小米", price: 9.9, num: 1000, address: "中国"} 无法删除address
delete obj.pname;
console.log(obj); // {id: 1, price: 9.9, num: 1000, address: "中国"} 可以删除pname,因为pname不是通过Object.defineProperty这个方法添加的属性
//(7)
Object.defineProperty(obj, 'address', {
value: '中国',
enumerable: true,
configurable: true
});
console.log(obj.address);
// 会报错 因为在(6)中configurable: false;所以该属性的value、writable、enumerable、configurable都不可以在修改
</script>