JavaScript原型链(重要)
1. 构造函数与原型
先了解一个概念:
类就是对象的模板,对象就是类的实例。
在构造函数内部的this指向它的实例对象。
2. new关键字
这个也是面试常考的知识点
在实例化构造函数时,做了什么?或者说在JavaScript中new
-
创建空对象
var zs = { }
-
将构造函数内部的this指向空对象
this -> zs
-
把属性和方法挂载到实例对象上。
zs.name zs.age zs.sing()
-
隐式返回新对象。
3. 实例成员与静态成员
- 实例成员:在构造函数内部创建的对象成员称为实例成员,只能由实例化的对象来访问。
- 静态成员:在构造函数本身上添加的成员称为静态成员,只能由构造函数本身来访问。
var arr = new Array()
console.dir(arr);
console.dir(Array);
4. 构造函数与静态成员
同一个函数,会在内存中保存多份,会造成内存的浪费。
<script>
function Cup(color, size) {
this.color = color
this.size = size
this.save = function () {
console.log("储水");
}
}
var mycup1 = new Cup("紫色", "3400ml")
var mycup2 = new Cup("绿色", "400ml")
console.log(mycup1);
console.log(mycup2);
mycup1.save()
mycup2.save()
引用数据类型,比较的是内存中的地址
console.log(mycup1.save == mycup2.save);
输出false
地址不同,造成浪费
</script>
引用性数据类型比较的是地址
5. 原型对象Prototype/[__proto__]
是构造函数的属性;
是Object类
构造函数的原型protoeype,作用是实现对象和属性共享,解决内存浪费问题。
每个一构造函数都有一个protoeype属性指向一个对象,这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所有。
一般情况下,我们公共属性放在构造函数里面,公共的方法我们放到原型对象身上。
原型对象里面的this指向的就是这个实例对象
作用:可以通过原型对象,对原来的内置对象进行拓展自定义的方法。
function Cup(color, size) {
this.color = color
this.size = size
Cup.prototype.save = function () {
console.log("储水");
}
Cup.prototype.fill = function () {
console.log("储水");
}
}
6. __proto__
对象能够访问原型对象中的方法:
原因:对象的_ _ proto_ _ 属性指向构造函数的prototype/原型对象
名称:对象原型
__proto__对象原型和原型对象prototype是等价的
__proto__
对象原型的意义就在于为对象的查找机制提供一个方向,或者说是一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,他只是内部指向原型对象prototype.
非标准:
不能使用,只能直接看他指向的结果,不能设置,只能直接访问
7. constructor属性
对象原型(__proto__
)和构造函数(prototype)原型对象里面都有一个属性constructor属性,constructor我们称为构造函数,因此它指回构造函数本身。
- 作用:主要用于记录该对象引用哪个构造函数,它可以让原型对象重新指向原来的构造函数。
constructor属性在那里可以访问:
__proto__
有constructor属性
原型对象/prototype
有constructor属性
我们称constructor为构造函数。
使用:当以对象的形式修改原型对象
的内容时,会发现修改后的属性把原来的属性覆盖掉了,这时候就需要重新设置一下constructor属性,并指挥回原来的构造函数。
function Cup(color, size) {
this.color = color
this.size = size
}
Cup.prototype = {
constructor: Cup,
save: function () {
console.log("储水");
},
warn: function () {
console.log("保温");
}
}
(以对象的形式,修改了原型对象中的内容,这样修改后的原型对象constructor就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个constructor指向原来的构造函数)
8.原型链
JavaScript对象,都有__proto__属性
,指向原型对象。
(mycup,Cup,Object,null)
- 作用:js中在查找对象和属性的方式时,遵循的一条链式规则。
- 当访问一个对象的属性(包括方法)时,首先找到这个对象自身有没有该属性
- 如果没有就查找它的原型(也就是
__proto__
指向的prototype原型对象) - 如果还没有就查找原型对象的原型(Object原型对象)
- 以此类推一直找到Object为止(null)
__proto__
对象原型的意义在于为对象成员查找机制提供了一种方向,或者说一条路线。
如果原型链找不到属性,undefined。如果找不到方法,会报错。
-
关系:
instance.constructor.prototype == instance.__proto__
-
特点:JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
小例子:
function Cup(color, size) {
this.color = color
this.size = size
}
Cup.prototype = {
constructor: Cup,
save: function () {
console.log("储水");
},
warn: function () {
console.log("保温");
}
}
var mycup = new Cup("粉色", "1500ml")
console.log(mycup);
console.log(mycup.__proto__ == Cup.prototype);
console.log(mycup.__proto__.constructor); //指向当前的构造函数
console.log(Cup.prototype.constructor); //指向当前的构造函数
//验证原型链
console.log(mycup.__proto__ == Cup.prototype);
console.log(Cup.prototype.__proto__ == Object.prototype);
console.log(Object.prototype.__proto__ == null);
var arr = [1, 2, 3]
var arr1 = new Array(1, 2, 3)
console.log(arr, arr1);
arr1.push(4)
Array.prototype.sum = function () {
var total = 0;
for (var i = 0; i < this.length; i++) {
total += this[i];
}
return total
}
console.log(arr.sum());
console.log(arr1.sum());
9.继承
JavaScript如何实现继承
- 构造继承
- 原型继承
- 实例继承
- 拷贝继承
- 原型prototype机制或apply和call方法去实现较简单,建议使用构造函数与原型混合方式
function Parent(){
this.name = 'wang';
}
function Child(){
this.age = 28;
}
Child.prototype = new Parent();//继承了Parent,通过原型
var demo = new Child();
alert(demo.age);
alert(demo.name);//得到被继承的属性
ES6:利用extends实现继承
组合继承:构造函数+原型对象
es5继承:构造函数+原型对象
//es5继承:构造函数 + 原型对象
//1. 利用构造函数继承属性
function Father(name, age) {
this.name = name
this.age = age
}
Father.prototype.work = function () {
console.log("工作");
}
function Son(name, age, gender) {
Father.call(this, name, age);
this.gender = gender;
}
Son.prototype = new Father() //子构造函数的peototype = new 父构造函数
Son.prototype.constructor = Son; // 子构造函数的constructor手动设置为son
Son.prototype.study = function () {
console.log("学习");
}
var son = new Son('xh', 6, "男")
console.log(son);
核心原理:
- 将子类所共享的方法提取出来,让子类的prototype原型对象 = new 父类()
- 本质:子类原型对象等于是实例化父类,因为父类实例化后另外开辟空间,就不会影响原来的父类
- 将子类的constructoe重新指向子类的构造函数
Son.prototype = new Father() //子构造函数的peototype = new 父构造函数
Son.prototype.constructor = Son; // 子构造函数的constructor手动设置为son
利用原型对象继承方法
es6:继承: extend
//es6: 类 extends
class Father1 {
constructor(name, age) {
this.name = name
this.age = age
}
work() {
console.log("父亲工作");
}
}
class Son1 extends Father1 {
constructor(name, age, gender) {
super(name, age)
this.gender = gender
}
study() {
console.log("子学习");
}
}
var son1 = new Son1("小米", 20, "男")
console.log(son1);
es6继承的关键:
extend
,super
10.对象的特性
- 封装
将对象所有的组成部分组合起来,尽可能的隐藏对象的部分细节,使其受到保护,只提供有限的接口与外部发生联系。
- 优点:
- 安全,使用时无法看到具体的细节,只需要直接调用
- 便于修改操作
- 继承
将对象拥有另外一个对象的属性和方法。
父类(基类):被继承的对象。
子类:继承的对象
- 优点:
- 提高代码重用性,提高代码的逻辑性和可维护性。
使用es5和es6实现类的封装
- es5
//es5 构造函数+原型对象
function Cup(color, size) {
this.color = color
this.size = size
}
Cup.prototype = {
constructor: Cup,
save: function () {
console.log("储水");
},
warn: function () {
console.log("保温");
}
}
var mycup = new Cup("粉色", "1500ml")
- es6
//es6类 语法糖
class Cup1 {
constructor(color, size) {
this.color = color
this.size = size
}
save() {
console.log("储水");
}
warn() {
console.log("保温");
}
}
var mycup1 = new Cup("绿色", "500ml")
var mycup2 = new Cup1("蓝色", "600ml")
上面这两种完全等同,但es6的更简单,封装更容易
11.函数
函数的三种定义方式
- 利用funcation关键字定义函数,也叫
命名函数
//定义,利用funcation 命名函数
function fn() {
console.log(1);
}
fn()
- 利用字面量定义函数,也叫
匿名函数
//2. 字面量定义 匿名函数
var fn1 = function () {
console.log(2);
}
fn1()
由于声明函数方式的不同以及预解析的因素,导致1,2两种方式略微不同。1,这种定义方式,把函数调用提前是完全没有问题的,但2,这种方式就会报错。(预解析知识点)
- 实例化Funcation
//3.实例化Funcation
var fn2 = new Function('num', 'concole.log(num)')
fn2(3)
这种方式也是可以传参的,但由于写法复杂,用的并不多。
函数的调用
- 普通函数,this指向window
function fn() {
console.log("普通函数");
}
fn();
- 构造函数,this指向实例化的对象
function Cup(size) {
this.size = size
}
var cup = new Cup("500ml")
- 原型对象的方法,this指向实例化的对象
Cup.prototype = {
constructor: Cup,
save: function () {
console.log("储水");
}
}
var cup = new Cup("500ml")
cup.save();
- 事件处理函数,this指向事件源 < button >< /button >
var btn = document.querySelector("button")
btn.onclick = function () {
console.log("事件处理函数");
console.log(this);
}
- 定时器函数,this 指向window
setTimeout(function () {
console.log("定时器中的函数");
}, 1000)
- 立即执行函数,this 指向window
(function () {
console.log("立即执行函数");
})()
改变this指向的三种方式
call方法
- 调用函数
- 改变this指向
fn.call(thisAry,arg1,arg2,...)
- thisAry:当前调用函数this的指向对象
- arg1,arg2:传递的其他参数
返回值就是函数的返回值
var obj = { name: "zs", age: 20 }
function fn(num) {
console.log(1); 20
console.log(this); 21
console.log(num); 22
}
fn(0); 25
fn.call(obj, 'call') 28
很明显可以看到又调用了 一次函数。而且this指向也变成了obj,不再是window了
apply方法
- 调用函数
- 改变this指向
fun.apply(thisAry,[arg1,arg2])
- thisAry:在fun函数运行时指定的this值
- arg1,arg2 :传递的其他参数
返回值就是函数的返回值
var obj = { name: "zs", age: 20 }
function fn(num) {
console.log(1); 20
console.log(this); 21
console.log(num); 22
}
fn(0); 25
fn.apply(obj, ['apply']) 31
和call方法一样,也会调用函数
也可以不改变参数只传值,不常用。
fn.apply(null, ['apply'])
bind方法
用到比较多
- 改变this指向
fun.bind(thisArg,arg1,arg2,...)
- thisArg:在fun函数运行时指向的this值
- arg1,arg2:传递的参数
返回由原来函数改变this之后的新函数
var obj = { name: "zs", age: 20 }
function fn(num) {
console.log(1); 20
console.log(this); 21
console.log(num); 22
}
fn(0); 25
var f = fn.bind(obj, "bind") 33
fn() 34
总结
是否可以改变this指向 | 是否可以调用函数 | 返回值 | 使用场景 | |
---|---|---|---|---|
call | 是 | 是 | 就是函数返回值 | 用来继承 |
apply | 是 | 是 | 就是函数返回值 | 数组相关 |
bind | 是 | 否 | 返回由原函数改变this之后的新函数 | 不希望函数立即执行(比如定时器) |