做有态度的前端团队

网易FEG前端团队

JS面向对象相关知识的一些学习笔记

最近在看《Javascript高级程序设计》第6章面向对象的程序设计,结合自己的一些理解,以此文做个笔记吧,可能会写得比较零散。

1、理解prototype

我们创建的每个函数都有一个 prototype (原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。如果按照字面意思来理解,那么prototype就是通过调用构造函数而创建的那个对象实例的原型对象。前面抄了《Javascrpt高级程序设计》里的一段话,概念我就不说太多了,说了可能也不太严谨,我喜欢通过写一些例子去慢慢理解它。下面举这个例子看看这个prototype有什么用:

var arr1 = new Array(1,5,67,4,2,2);//为了方便说明问题,这里数组我就这样写了
var arr2 = new Array(2,5,3,2,2);

arr1.calculate = function(){
    var sum = 0;
    for(var i = 0, len = this.length; i<len ;i++){
        sum += this[i];
    }
    return sum;
}
console.log(arr1.calculate()) //81
console.log(arr2.calculate())//报错:arr2.calculate is not a function

上面使用了原生的数组对象说明问题,我定义了一个求和的方法,然后想分别求出arr1和arr2的和,显然这个calculate方法是属于arr1的,arr2并没有这个方法,所以报错了。如果我想arr2也可以用这个方法,怎么办?于是prototype就可以派上用场了!

写法如下:

Array.prototype.calculate = function() {
    var sum = 0;
    for (var i = 0, len = this.length; i < len; i++) {
        sum += this[i];
    }
    return sum;
}
console.log(arr1.calculate()) //81
console.log(arr2.calculate())//14

基本用法大概就这样,这样写了之后,于是所有实例都可以使用定义出来的方法/属性了。

Array是原生的构造函数,那么我们自己定义的构造函数可不可以这样用呢?当然也是可以的。如:

function Test(name) {
    this.name = name;
}
Test.prototype.say = function() {
    console.log("My name is"+ " "+this.name)
}
var t1 = new Test('Jack');
t1.say() //My name is Jack

var t2 = new Test('Lucy');
t2.say() //My name is Lucy

2、继承

继承有几种方法,这里主要介绍下利用原型链和构造函数组合使用的这种常见的继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

function Parent(name){
    this.name = name;
}
Parent.prototype.sayName = function(){
    console.log("My name is"+" "+this.name)
}
function Son(name,age){
    this.age = age;//独有属性
    Parent.call(this,name) //继承name属性
}
Son.prototype = new Parent();//继承了Parent

Son.prototype.sayAge = function(){//定义Son自己的方法
    console.log(this.age)
}

var s = new Son("Mike","18");//创建实例
s.sayName() //My name is Mike
s.sayAge() //18

在这个例子中, Parent构造函数定义了一个属性 nameParent 的原型定义了一个 sayName方法 。Son 构造函数定义了它自己的属性age,又调用 Parent 构造函数,通过使用 call 方法(或 apply方法)以继承name属性。通过创建Parent 的实例,并将该实例赋给Son.prototype以继承Parent的方法。另外,我想讨论个问题,能不能这样赋值呢:Son.prototype = Parent.prototype。虽然也能达到继承的目的,但有个值得注意的问题。我举个例子来说明问题吧:

var arr1 = [1, 2, 3];
var arr2 = arr1;

arr2.push(4);

console.log(arr1) //[1, 2, 3, 4]
console.log(arr2) //[1, 2, 3, 4]

上面的这个例子,我先是定义了一个数组arr1,然后再把arr1赋值给arr2;接着我给arr2 添加了一个4,从打印出来的结果可以看出,给arr2添加的这个4也添加到aar1上。这是怎么回事?实际上,引用类型赋值的时候,并非是真的复制出了一份,这里的arr2其实只是指向arr1所在的空间,再画个小图简单说明一下:

arr.jpg

如果希望这两个数组是相互独立,可以这么写:

var arr1 = [1, 2, 3, 4]
var arr2 = [];
 
for (var i = 0,len = arr1.length ; i < len; i++) {
    arr2[i] = arr1[i];
}

回到前面提到的问题,Son.prototype = Parent.prototype这样直接赋值的结果就是,如果我给Son添加一个新的方法,也将加到Parent上。那么也可以这样写来避免这个问题:

for(var i in Parent.prototype){
    Son.prototype[i] =  Parent.prototype[i]
}

3、运用相关知识写个拖拽

查看demo>>

        function Drag(id) { //定义一个构造函数
            var _this = this;
            this.oDiv = document.getElementById(id);
            this.disX = 0;
            this.disY = 0;
            this.oDiv.onmousedown = function(e) {
                _this.downFn(e); //this也是一个重要的知识点。这里为什么不直接用this?
                // console.log(this)  //打印出来看看就知道了
                // console.log(_this)
                return false; //如果页面上有文字,拖拽的时候会选中文字,return false可以阻止这个默认行为
            };
        }
        Drag.prototype.downFn = function(e) {
            var _this = this;
            var event = window.event || e;
            this.disX = event.clientX - this.oDiv.offsetLeft; //鼠标按下拖拽物体时,计算出鼠标距离物体左边的距离
            this.disY = event.clientY - this.oDiv.offsetTop; //同上

            //把事件加oDiv上会发现,拖拽的时候,拖着拖着就脱离物体了,原因是范围太小了,document就足够大。
            document.onmousemove = function(e) {
                _this.moveFn(e)
            }
            document.onmouseup = function() {
                _this.upFn()
            }
        }
        Drag.prototype.moveFn = function(e) {

            var event = window.event || e;
            this.oDiv.style.left = event.clientX - this.disX + 'px';//px记得要加上,不然出问题它也没报错
            this.oDiv.style.top = event.clientY - this.disY + 'px';
        }
        Drag.prototype.upFn = function() {
            document.onmousemove = null; //鼠标抬起后清掉没用的东西
            document.onmouseup = null;
        }

        function AnotherDrag(id) { //再定义一个构造函数
            Drag.call(this, id); //继承Drag的属性
        }


        for (var i in Drag.prototype) {
            AnotherDrag.prototype[i] = Drag.prototype[i]
        }

        //重写一下这个moverFn方法,是不会影响Drag的moverFn方法
        AnotherDrag.prototype.moveFn = function(e) {

            var event = window.event || e;
            var l = event.clientX - this.disX;
            var t = event.clientY - this.disY;

            //限制一下它的范围,不能拖出可视区
            if (l < 0) {
                l = 0
            } else if (l > document.documentElement.clientWidth - this.oDiv.offsetWidth) {
                l = document.documentElement.clientWidth - this.oDiv.offsetWidth;
            }

            if (t < 0) {
                t = 0;
            } else if (t > document.documentElement.clientHeight - this.oDiv.offsetHeight) {
                t = document.documentElement.clientHeight - this.oDiv.offsetHeight;
            }

            this.oDiv.style.left = l + 'px';
            this.oDiv.style.top = t + 'px';
        }

        window.onload = function() {
            new Drag("drag");
            new AnotherDrag("drag2");
        }

手机阅读请扫描下方二维码:

添加新评论

ali-40.gifali-41.gifali-42.gifali-43.gifali-44.gifali-45.gifali-46.gifali-47.gifali-48.gifali-49.gifali-50.gifali-51.gifali-52.gifali-53.gifali-54.gifali-55.gifali-56.gifali-57.gifali-58.gifali-59.gifali-60.gifali-61.gif