js原型链看这篇就够了

面向对象

把描述同一个事物(同一个对象)的属性和方法放在同一个内存空间下
不同事物之间的属性即使属性名相同,相互也不会冲突
这种方式为js的面向对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var person1 = {
name:"zack",
age:32
}

var person2 = {
name:"vi",
age:32
}

var jsPerson1={
name:"zack",
age:32,
writeJs:function(){
console.log("my name is " + this.name + ", i can write js ")
}
}
jsPerson1.writeJs()

工厂模式

把实现同一个事情的相同代码放在同一个函数中

1
2
3
4
5
6
7
8
9
10
11
12
function createJSPerson(name, age){
var obj={}
obj.name = name
obj.age =age
obj.writeJs = function(){
console.log("my name is "+ this.name + " , i can write js")
}
return obj
}

p1 = createJSPerson("zack", 32)
p1.writeJs()

通过函数createJSPerson批量产生类实例

构造函数模式

构造函数的目的就是创建一个自定义类,并且创建这个类的实例
构造函数模式和工厂模式的区别
1 执行的时候
普通函数执行 ->createJsPerson()
构造函数模式 ->new createJsPerson()
new 执行后 createJsPerson就是一个类

1
2
3
4
5
6
7
8
9
10
11
function CreateJsPerson(name, age) {
var obj={}
obj.name = name
obj.age = age
obj.writeJs = function(){
console.log("my name is "+ this.name + " , i can wirte js")
}
return obj
}
p2 = new CreateJsPerson("zack", 32)
p2.writeJs()

类是函数类型, 他通过new执行变成了一个类,但他本身也是一个普通函数
实例都是对象类型
2 在函数代码执行的时候
相同:都形成私有作用域,然后经历形参赋值->预解释->代码从上到下执行(类和普通函数一样)
不同:在代码执行之前,不用自己在手动创建对象了,浏览器会默认创建一个对象数据类型的值
这个对象其实就是我们当前类的一个实例
接下来,代码从上到下执行,以当前实例为执行主体(this代表的就是当前实例)分别把属性名和属性值
赋值给当前实例

1
2
3
4
5
6
7
function PersonClass(name, age){
this.name = name;
this.age = age;
this.writeJs = function(){
console.log("my name is " + this.name + ", i can write js")
}
}

在构造函数模式中,类中(函数体中)出现的this.xxx = xxx 中的this是当前类的一个实例

1
2
3
4
var p1 = new PersonClass("zack", 32)
p1.writeJs()
var p2 = new PersonClass("vico", 32)
p2.writeJs()

3 p1和p2都是createjsperson这个类的实例,都拥有writeJs这个方法,但是不同实例之间的方法是不一样的
在类中给实例增加的属性(this.xxx=xxx)属于当前实例的私有属性,实例和实例之间是单独的个体
私有属性之间是不相等的

1
2
3
4
5
6
console.log(p1.writeJs === p2.writeJs)
res = PersonClass("rolin",31)
//未返回对象,所以res为undefined
console.log(res)
//函数模式this为window,所以this.name = "rolin",window属性name为rolin
console.log(window.name)

类中的this

1
2
3
4
5
6
7
function Fn(){
var num = 10;
this.x = 100;
this.getX = function(){
console.log(this.x)
}
}

1 构造函数模式中new Fn()执行, 如果Fn不需要传递参数,那么小括号可以省略
2 this的问题:在类中出现的this.xxx=xxx出现的this都是当前类的实例,而某一个属性值如果是方法
该方法中的this要看执行时前面是否有”.”才能知道this是谁

1
2
3
4
5
6
var f1 = new Fn;
//->方法中的this是f1 ->100
f1.getX();
var ss = f1.getX;
//->方法中的this是window ->undefined
ss();

3 类有普通函数的一面,当函数执行的时候,var num 其实只是当前形成的私有作用域中私有变量而已
它和我们的f1这个实例没有任何关系

1
2
//undefined
console.log(f1.num)

4 在构造函数模式中,浏览器会默认把我们的实例返回(返回的是一个对象数据类型的值)
如果我们自己手动写了return返回,返回的是一个基本数据类型的值,当前实例是不变的,例如return 100
返回的是一个引用类型的值,当前的实例会被自己返回的值给替换掉 例如 return {name:”zack”}
5 检测某一个实例是否属于这个类

1
2
3
4
5
6
7
8
9
console.log(f1 instanceof Fn)
//因为所有的实例都是对象数据类型的,
//而每个对象数据类型都是Object这个内置类型的一个实例
//所以f1也是他的一个实例
console.log(f1 instanceof Object)
//对于检测数据类型来说,typeof有自己的局限性,
//不能细分object下的对象,数组,正则...
var a=[];
console.log(a instanceof Array)

6 f1和f2都是Fn这个类的一个实例,都拥有x和getx两个属性
但是这两个属性是各自的私有属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var f2 = new Fn;
console.log(f1.getX === f2.getX)
//in:检测某一个属性是否属于这个对象(attr in object)
//不管是私有属性还是公有属性,只要存在,用in来检测都是true
//hasOwnProperty:用来检测某一个属性是否为这个对象的"私有属性"
//这个方法只能检测私有属性
console.log("getX" in f1)
console.log(f1.hasOwnProperty("getX"));
//检测某一个属性是否为该对象的"公有属性" hasPubProperty
function hasPubProperty(obj, attr) {
return attr in obj && !(obj.hasOwnProperty(attr))
}

console.log(hasPubProperty(f1,"getX"))

批量设置属性

构造函数有一个问题就是变量的方法不是公有的,每个变量保存了一份自己的方法。这样不符合面向对象的设计
其实每个类都有一个prototype属性,它指向一片系统开辟的空间,所有类实例共享这篇空间。
而每个实例都有一个_proto_属性,也指向这个空间,这个空间同样存储了一个类对象,类对象有一个属性contructor表示
代表他的构造类,该类对象还有一个_proto_属性也指向了一个系统开辟的空间,该空间存储的类实例对象为Object类型的。
这个类对象的constructor属性值为Object
所以Object类的prototype也指向了这个Object类实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script type="text/javascript">
function Fn(){
this.x = 100
this.sum = function(){
console.log(this.x)
}
}

var pro = Fn.prototype
//把原来原型指向的地址赋值给pro,现在他们操作的是同一个内存空间
pro.getX = function(){
console.log(this.x)
}

pro.sum = function(){
console.log(this.x)
}

var f1 = new Fn;
var f2 = new Fn;
</script>

将上述类关系画成图形如下
1.png
Fn类的prototype指向了系统开辟的空间,该空间存储了一个实例,(我们假设命名其为A)其constructor属性为Fn,
并且包括getX和sum公有方法。
每一个类对象都有一个_proto_属性,f1和和f2的_proto_也指向了A。
因为A是类的实例,所以A也有_proto_属性,其属性指向另一个对象B,这个B也有一个constructor属性为Object,
B的_proto_和Object的prototype指向的都是B自己。
所以每一个对象实例都可以根据_proto_找到Object类型的实例。
每一个类的都可以根据prototype找到Object类型的实例。这就是原型链

重构原型链对象

我们可以自己开辟一块新的内存,存储我们公有的属性和方法,把浏览器原来的内存替换掉

1
2
3
4
5
6
7
8
9
10
11
12
13
function Fn(){
this.x=100
}
Fn.prototype={
constructor:Fn,
a:function(){},
b:function(){}
}

var f = new Fn()
f.a()
f.b()
console.log(f.constructor)

只有浏览器天生给Fn.prototype开辟的堆内存里才有constructor,我们自己开辟的堆内存中没有这个属性,所以要手动添加。
我们可以通过修改堆内存对象的属性,达到添加方法的目的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Array.prototype.mysort=function(){
for (var i = 0; i < this.length; i ++){
for(var j=i+1; j < this.length; j++){
if(this[i] > this[j]){
var temp = this[i]
this[i] = this[j]
this[j] = temp
}
}
}
}

var ary = [1,2,2,1,2,3,4,2,1,3]
ary.mysort()
console.log(ary)

原型模式的this

在原型模式中,this常用的有两种情况
在类中this.xxx=xxx; this代表的就是当前实例
在某一个方法中的this,this需要看执行的时候方法前”.”是谁,this就是谁

  1. 需要先确定this指向是谁
  2. 把this替换成对应的代码
  3. 按照原型链查找机制,一步步查找结果
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    function Fn(){
    this.x=100
    this.y=200
    this.getY= function(){
    console.log(this.y)
    }
    }

    Fn.prototype={
    constructor:Fn,
    y:300,
    getX:function(){
    console.log(this.x)
    },
    getY:function(){
    console.log(this.y)
    }
    }

    var f = new Fn
    f.getX() //f.x -> 100
    f.getY() //f.y -> 200
    f.__proto__.getX()//undefined
    Fn.prototype.getX()//undefined
    f.__proto__.getY() //300
    1
    2
    3
    4
    5
    f.getX()中this为f,所以f.x为100
    f.getY()中this为f, 所以f.y为200
    f.__proto__.getX() 因为f.__proto__中没有x,所以为undefined
    Fn.prototype.getX() 因为Fn.prototype和f.__proto__指向的堆内存是一个,所以也为undefined
    f.__proto__.getY() 因为f.__proto__中有y,所以输出为300

我们实现一个数组去重的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Array.prototype.myUnique = function() {
var obj={}
for (var i = 0; i < this.length; i ++){
var cur = this[i]
if(obj[cur] == cur){
this[i] = this[this.length-1]
i--
this.length--
continue
}
obj[cur] = cur
}
obj = null
return this
}

var ary=[12,12,23,44,56,44]
ary.myUnique()
console.log(ary)

我们实现了去重方法,并在方法内返回this,这么做的好处就是可以继续调用array的其他方法,也就是大家所说的链式调用
比如我们可以继续调用sort方法

1
ary.myUnique().sort(function(a,b){return a-b})

总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
构造函数模式找到了类和实例的概念,并且实例和实例之间是独立开的
构造函数的原型模式解决了方法或者属性公有的问题,把实例之间相同的
属性和方法提取成公有的属性和方法
1 每一个函数数据类型(普通函数,类)都有一个天生自带的属性:prototype(原型)
并且这个属性是一个对象数据类型的值
2 并且在prototype上浏览器给他加了一个属性constructor(构造函数)
属性值是当前函数本身
3 每一个对象数据类型(普通对象,实例,prototye)
也天生自带一个属性: __proto__ , 属性值是当前实例所属类的原型(prototype)

原型链查找方式
通过对象名.属性名 的方式获取属性值的时候,首先在对象的私有属性上查找,
如果私有属性中没这个属性则获取的是私有属性值
如果私有的没有,则通过__proto__找到所属类的原型
(类的原型上定义的属性和方法都是当前实例公有的属性和方法),
原型上存在的话,获取的是公有的属性值
如果原型上也没有,则继续通过原型上的__proto__向上查找,一直找到Object.prototype为止...

感谢关注

感谢关注我的公众号
wxgzh.jpg