js高级上篇
本系列分为上下两篇
知识导航:
- 构造函数与原型链
- 继承
- es5新增方法
- es6 类
1. 构造函数与原型链
先回忆一下对象的这种创建方式:
构造函数
function Per(name, age) {
this.name = name;
this.age = age;
}
var per = new Per("gong", "22");
1.1 区分一下静态成员和实例成员
静态成员
在构造函数本身上添加的成员,它只能由构造函数来访问。
function Per(name, age) {
this.name = name;
this.age = age;
}
Per.sex = "男";
console.log(Per.sex);
实例成员
构造函数中通过this添加的,它只能通过实例化的对象来访问
function Per(name, age) {
this.name = name;
this.age = age;
}
1.2 构造函数的缺点
主要是消耗内存
1.3 构造函数原型
JavaScript 规定,每一个构造函数都有一个prototype 属性(其数据结构也是一个对象)。同时在这个对象上的所有属性和方法都会被构造函数所拥有
故我们可以把一些不变得方法直接定义在这个对象里面。以达到被实例对象所共享
像这样:
function Per(name, age, say) {
this.name = name;
this.age = age;
}
Per.prototype.sayHi = function() {
console.log("你好啊");
}
var zs = new Per();
var ls = new Per();
zs.sayHi();
ls.sayHi();
1.4 对象原型
每个对象又都有一个 __proto__
属性(数据结构也是一个对象),它会指向这个对象的构造函数的prototype 原型对象
我们的实例对象能够使用构造函数在prototype 原型中定义的方法本质便是有这个 __proto__
的存在
看一下1.3中的执行顺序
实例化之后,发现本本身没有此方法。便顺着__proto__
像上找,在它的构造函数prototype 属性中寻到了。(ps:prototype 为复杂数据类型故会另开辟内存空间存储)
__proto__
对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线
当我们访问一个对象的属性或者方法时首先会查找该对象自身有无该属性方法,若无它就会向它的__proto__
(也即被指引到了构造函数的prototype)去寻找。顺着1.6所要说的原型链的顺序,直到找到了或者找不到最后到了null返回
1.5 constructor属性
打印一下Per.prototype
和zs.__proto
__
function Per(name, age) {
this.name = name;
this.age = age;
}
Per.prototype.sayHi = function() {
console.log("你好啊");
}
var zs = new Per("zhangsan", 18);
var ls = new Per();
console.log(Per.prototype);
console.log(zs.__proto__);
看输出结果
可发现它们都有一个constructor,并且这个属性里面的值保存的是我们的构造函数。
故constructor属性又被称为constructor构造函数。因为它指回了构造函数本身。它的作用主要是记录该对象是用哪个构造函数实例化的,同时它还可以让原型prototype重新指向原来的构造函数
看一下这种情况:
function Per(name, age) {
this.name = name;
this.age = age;
}
Per.prototype = {
sayHi: function() {
console.log("hi");
},
eat: function() {
console.log("eat");
console.log(this);
}
}
console.log(Per.prototype.constructor);
结果如图:
这时候我们需要手动将这个原型再指向我们的构造函数:
Per.prototype = {
constructor: Per,
sayHi: function() {
console.log("hi");
},
eat: function() {
console.log("eat");
console.log(this);
}
}
再看结果:
1.6 原型链
每一个对象都有__proto__
属性,它指向它的构造函数的原型,构造函数的原型也是一个对象所以也会有一个__proto__
属性同样指向它的构造函数的原型,就像这样一层一层往上找便组成了一个链子。这个链子被称为原型链
如图:
来看一下实例吧:
可见Per的原型的__proto_
指向的是Object,再往上指便成了空。
1.7 总结一下构造函数,实例对象,prototype
看图解:
1.8 prototype中的this指向
function Per(name, age) {
this.name = name;
this.age = age;
}
var that = null;
Per.prototype.sayHi = function() {
that = this;
}
var zs = new Per();
zs.sayHi();
console.dir(that === zs);
结果为ture
即这个this指向的是zs的实例对象
1.9 给内置对象添加方法
像Array便是一个构造函数,所以我们可以给它的原型prototype上添加我们自定义的方法
如Array.prototype=function(){...}
2. 继承
在es6之前是没有类的概念的我们只能通过原型链的方式来实现继承
2.1 继承属性
比如我们想实现这个:
function Fa(name, sex) {
this.name = name;
this.sex = sex;
}
function Son(age) {
this.age = age
}
var zs = new Son();
console.log(zs.name);
我们想让Son能进行Fa的属性,我们给Son实例就可以拿到父亲的方法。
Son的Fa怎么建立联系呢?
实例对象zs能调用Son的属性是因为实例过程中,Son的this指向了zs。我们想要访问Fa的属性则也需要Fa里面的this也指向我这个实例对象,即把Fa的this指向Son里面的this。那么Fa的this不就间接的执行实例对象了吗
先介绍一种可改变this指向的方法。
ele01.call(ele02,[形参1,形参2])它的作用是将ele01中的this指向ele02
实现结果:
2.2 继承方法
错误示范但可取到实例
function Fa(name, sex) {
this.name = name;
this.sex = sex;
}
Fa.prototype.sayHi = function() {
console.log("hi");
}
function Son(name, age, sex) {
Fa.call(Son, name, sex);
this.age = age
}
Son.prototype = Fa.prototype;
Son.prototype.Sty = function() {
console.log("学习");
}
var zs = new Son("zhangsan", "man", 19);
zs.sayHi();
把父构造函数的原型给与子构造函数的原型,这样虽然最后的实例对象也能访问到父构造函数里面的方法
但是值得注意的是Son.prototype = Fa.prototype;不是简单的赋值运算,这样操作会使两者的prototype建立联系,使得Son.prototype可以控制Fa.prototype
打印一下Fa.prototype
可发现它里面也多了一种son原型的方法
规范写法:
将Fa构造函数先实例化,Son.prototype =Fa构造函数实例化.__proto__
但一般对象.__proto__
属于不标准写法所以会省略属性名
直接Son.prototype =Fa构造函数实例化对象
结果:(可发现Fa原型未被改动,最后的实例对象zs也能调用Fa的方法)
2.3 继承总计
属性继承用改变this的方法
方法继承用修改原型的方法
3. es5新增方法
3.1 新增数组相关方法
3.1.1 遍历数组
以前我们对数组遍历的通用方法是使用for循环,接下来介绍一个新的方法arr.foreach
基本语法:
arr.forEach(function(value, index, array) {
//参数一:数组元素
//参数二:数组元素的索引
//参数三:当前的数组
})
栗子:
3.1.2 过滤数组
基本语法:
arr.filter(function(value, index,array) {
//参数一:数组元素
//参数二:数组元素的索引
//参数三:当前的数组
return value >= 某个值;
});
有返回值,返回一个新的数组
栗子:找出该数组中大于10的元素
3.1.3 筛选元素
查找数组中是否有满足条件的元素,返回一个布尔值
基本语法:
arr.some(function(value,index,array) {
//参数一:数组元素
//参数二:数组元素的索引
//参数三:当前的数组
return value>10;
});
栗子:
与filter的区别:
- 首先是返回值,前者是返回数组,后值是一个布尔值
- 在some 里面 遇到 return true 就是终止遍历,而filter会一直到遍历完整个数组。故filter的效率会更高一些
3.2 新增字符串相关方法
3.2.1 去除字符串两端空格
方法str.trim()
栗子:(对比打印结果)
值得注意:
它只能去除字符串两端的空格
3.3 新增对象相关方法
3.3.1 获取对象的属性名
方法:Object.keys(obj)
,可拿到该对象的所有属性名。返回值是一个数组
栗子:
3.3.2 控制对象属性
基本语法:
Object.defineProperty(对象,修改或新增的属性名,{
value:修改或新增的属性的值,
writable:true/false,//如果值为false 不允许修改这个属性值
enumerable: false,// 如果值为false 则不允许遍历
configurable: false // 如果为false 则不允许删除这个属性
})
栗子1:修改age的值,并不允许该值再被修改和被遍历到
栗子2:不允许删掉该属性
可见name属性被删掉了二age属性则没有
5. 类(ES6之后)
在ES6之后js终于添加了类的概念,想必有过java基础的人都是比较清楚的
它就是抽象了对象的公共部分,即一个模子。利用模子造出来的就是一个具体的对象
5.1 类的创建
基本语法:
class Demo {
// 内容
//构造函数
constructor(参数){
类的共有属性放到这里面
}
}
var demo = new Demo(); //实例化
//注意类名习惯性定义首字母大写
//constructor函数只要生成实例时,就会自动调用这个函数, 如果不写这个函数,类也会自动生成
栗子:
5.2 添加类中的方法
类中的属性放到这里构造函数constructor(参数){…},类的方法则直接罗列在类中即可
如下:
注意:多个函数方法之间不需要添加逗号分隔,直接罗列即可
5.3 类的继承
基本语法:
// 父类
class Father{
}
// 子类继承父类
class Son extends Father {
}
栗子:(继承了父类的属性和方法)
5.3.1 super关键字
在构造函数中使用时,super
关键字将单独出现,并且必须在使用this
关键字之前使用。super
关键字也可以用来调用父对象上构造函数。
有什么用途呢
先看这个:
注意看代码,按我们的想法子类已经把父类的属性继承过来了,故子类的构造函数中只需写上它独有的即可。可是看结果却不是这样的,它要求我们要重新调用一下父类的构造函数
像这样:
没问题了