JS高级:面向对象解析

1 实例属性/方法

都是绑定在使用构造函数创建出来的对象p上; 最终使用的时候也是使用对象p来进行访问;

    function Person(name, age, doFunc) {
        this.name = name;
        this.age = age;
        this.doFunc = doFunc;
    }

    var p1 = new Person('sz', 18, function () {
        console.log('sz在上课');
    });
    var p2 = new Person('王二小', 18, function () {
        console.log('王二小在放羊');
    });

2 静态属性/方法

函数本质也是一个对象, 既然是个对象, 那么就可以动态的添加属性和方法

只要函数存在, 那么绑定在它身上的属性和方法, 也会一直存在

eg,记录总共创建了多少个人对象:

2.1 全局变量

// 1. 设置一个全局的变量
    var personCount = 0;
    function Person(name, age, doFunc) {
        this.name = name;
        this.age = age;
        this.doFunc = doFunc;
        personCount++;
    }

    var p1 = new Person('sz', 18, function () {
        console.log('sz在上课');
    });
    var p2 = new Person('王二小', 18, function () {
        console.log('王二小在放羊');
    });

    console.log('总共创建了'+ personCount + '个人');  //2

2.2 静态属性/方法

    function Person(name, age, doFunc) {
        this.name = name;
        this.age = age;
        this.doFunc = doFunc;
        if (!Person.personCount) {
            Person.personCount = 0;	//创建静态属性
        }
        Person.personCount++;	
    }
	//创建静态方法
    Person.printPersonCount = function () {
        console.log('总共创建了'+ Person.personCount + '个人');
    };


    var p1 = new Person('sz', 18, function () {
        console.log('sz在上课');
    });
    var p2 = new Person('王二小', 18, function () {
        console.log('王二小在放羊');
    });

    Person.printPersonCount();

3 类型获取

3.1 内置对象类型获取

内置对象
• String
• Number
• Boolean
• Object
• Function
• Array
• Date
• RegExp
• Error

获取根据自己声明的构造函数创建的对象

    console.info("-->str")
    var str = "aaa";
    console.log(typeof str);                            //string
    console.log(str.constructor.name);                  //String
    console.log(Object.prototype.toString.call(str));   //[object String]

    console.info("-->obj")
    var obj = { 'name': '张三' };
    console.log(typeof obj);                            //object
    console.log(obj.toString());                        //[object Object]
    console.log(obj.constructor.name);                  //Object
    console.log(Object.prototype.toString.call(obj));   //[object Object]

    console.info("-->arr")
    var arr = [1, 2, 3];
    console.log(typeof arr);                          //object  
    console.log(arr.toString());                      //1,2,3
    console.log(arr.constructor.name);                //Array  
    console.log(Object.prototype.toString.call(arr)); //[object Array]  

    console.info("-->date")
    var date = new Date();
    console.log(typeof date);                           //object  
    console.log(date.toString());                        //Wed Oct 09 2019 00:08:15 GMT+0800 (中国标准时间)
    console.log(date.constructor.name);                 //Date  
    console.log(Object.prototype.toString.call(date));  //[object Date]  

3.2 自定义类型获取

还是使用 实例化对象.constructor.name

   function Person(name, age) {
        // var this = new Object();  自定义类型系统都把this指向Object,所以Object.prototype.toString.call(...) 获取都是 [object Object]
        this.name = name;
        this.age = age;
    }

    function Dog(name, age) {
        this.name = name;
        this.age = age;
    }

    function Cat(name, age) {
        this.name = name;
        this.age = age;
    }

    // 1. 实例化-->实例
    var p = new Person('zs', 18);
    var d = new Dog('小花', 8);
    var c = new Cat('小猫', 3);

    //  object
    console.log(typeof p);
    console.log(typeof d);
    console.log(typeof c);

    // [object Object]
    console.log(p.toString());
    console.log(d.toString());
    console.log(c.toString());

    // [object Object]
    console.log(Object.prototype.toString.call(p));
    console.log(Object.prototype.toString.call(d));
    console.log(Object.prototype.toString.call(c));

    console.log(p.constructor.name);    //Person
    console.log(d.constructor.name);    //Dog
    console.log(c.constructor.name);    //Cat

3.3 类型验证 instanceof

类型验证使用 instanceof

    function Person(name, age) {
        this.name = name;
        this.age = age;
    }

    function Dog(name, age) {
        this.name = name;
        this.age = age;
    }

    function Cat(name, age) {
        this.name = name;
        this.age = age;
    }

    // 1. 实例化-->实例
    var p = new Person('zs', 18);
    var d = new Dog('小花', 8);
    var c = new Cat('小猫', 3);

	//true
    console.log(p instanceof Person);
    console.log(d instanceof Dog);
    console.log(c instanceof Cat);
	
	//true
    console.log(p instanceof Object);
    console.log(d instanceof Object);
    console.log(c instanceof Object);
	
	//false
    console.log(p instanceof Dog);	
    console.log(d instanceof Cat);
    console.log(c instanceof Person);

    console.log(p);
    console.log(d);
    console.log(c);
	
	//true
    console.log([] instanceof Object); 

4 访问函数原型对象

	function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    // 原型对象
    Person.prototype.run = function () {
        console.log('跑');
    };

4.1 方式一:函数名.prototype

	console.log(Person.prototype);

浏览器

> {run: ƒ, constructor: ƒ}
	> run: ƒ ()
	> constructor: ƒ Person(name, age)
	> __proto__: Object

4.2 方式二:对象.proto(不推荐)

.__proto__看起来很像一个属性,但是实际上它更像一个getter/setter

    var p = new Person();
    console.log(p.__proto__);

浏览器

> {run: ƒ, constructor: ƒ}
	> run: ƒ ()
	> constructor: ƒ Person(name, age)
	> __proto__: Object

__proto__是一个非标准属性
即ECMAScript中并不包含该属性,这只是某些浏览器为了方便开发人员开发和调试而提供的一个属性,不具备通用性
建议:在调试的时候可以使用该属性,但不能出现在正式的代码中

4.2.1 __proto__可以设置

.__proto__是可设置属性,可以使用ES6的Object.setPrototypeOf(..)进行设置。然而,通常来说你不需要修改已有对象的[[Prototype]]。

    var newYX = {
        'add': function () {
            console.log('sum');
        }
    };

    p.__proto__ = newYX;
    console.log(p.__proto__);

浏览器

> {add: ƒ}
	> add: ƒ ()
	> __proto__: Object

5 判断原型对象是否存在某属性

5.1 in 属性

in 判断一个对象, 是否拥有某个属性(如果对象身上没有, 会到原型对象里面查找)

    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    Person.prototype.address = '上海';

    var p = new Person('撩课', 18);
    // console.log(name in p); // false
    console.log('name' in p); // true
    console.log('address' in p); // true

5.2 hasOwnProperty 属性

只到对象自身查找

    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    Person.prototype.address = '上海';

    var p = new Person('张三', 20);
    console.log(p.hasOwnProperty('name')); // true
    console.log(p.hasOwnProperty('address')); // false

6 判断一个对象的原型 isPrototypeOf 和 instanceOf

是的,它们执行相同的操作,都遍历原型链以查找其中的特定对象。

两者的区别在于它们是什么,以及如何使用它们,例如isPrototypeOf是对象上可用的函数。它允许您测试一个特定对象是否在另一个对象的prototype链中,因为此方法是在object上定义的原型,它对所有对象都可用。
instanceof是一个操作符,它需要两个操作数,一个对象和一个构造函数,它将测试传递的函数原型属性是否存在于对象链上(通过[[HasInstance]](V)内部操作,仅在函数对象中可用)。

B.isPrototypeOf(a) 判断的是A对象是否存在于B对象的原型链之中,检查B

a instanceof B 判断的是B.prototype是否存在与A的原型链之中,检查B.prototype

isPrototypeOf() 与 instanceof 运算符不同。在表达式 "object instanceof AFunction"中,object 的原型链是针对 AFunction.prototype 进行检查的,而不是针对 AFunction 本身。

function A () {
  this.a = 1;
}
function B () {
  this.b = 2;
}
B.prototype = new A();
B.prototype.constructor = B;

function C () {
  this.c = 3;
}
C.prototype = new B();
C.prototype.constructor = C;

var c = new C();

// instanceof expects a constructor function

c instanceof A; // true
c instanceof B; // true
c instanceof C; // true

// isPrototypeOf, can be used on any object
A.prototype.isPrototypeOf(c); // true
B.prototype.isPrototypeOf(c); // true
C.prototype.isPrototypeOf(c); // true

7 继承(MDN)

7.1 定义 Teacher() 构造器函数

function Person(first, last, age, gender, interests) {
  this.name = {
    first,
    last
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
};

Person.prototype.greeting = function() {
  console.log('Hi! I\'m ' + this.name.first + '.');
};


function Teacher(first, last, age, gender, interests, subject) {
  Person.call(this, first, last, age, gender, interests);   //用的是传送给Teacher(),而不是Person()的值

  this.subject = subject;
}

var p = new Person("张","三",22,"男",["篮球","KTV"]);

var t = new Teacher("李","四",18,"男",["英语","计算机"],"编程");

t.greeting(); 
//TypeError: t.greeting is not a function
//这里 Teacher 不能获取 greeting...

这里主要调用Person.call(this, first, last, age, gender, interests);定义 Teacher() 构造器函数,
我们很有效的在Teacher()构造函数里运行了Person()构造函数,得到了和在Teacher()里定义的一样的属性,但是用的是传送给Teacher(),而不是Person()的值(我们简单使用这里的this作为传给call()的this,意味着this指向Teacher()函数)。

7.2 设置 Teacher() 的原型

我们需要让Teacher()从Person()的原型对象里继承方法

Teacher.prototype = Object.create(Person.prototype);

在这个例子里我们用这个函数来创建一个和Person.prototype一样的新的原型属性值(这个属性指向一个包括属性和方法的对象),然后将其作为Teacher.prototype的属性值。

7.2.1 为什么不能使用 Teacher.prototype = Person.prototype

看看比较

7.3 设置 Teacher() 构造器引用

现在Teacher()的prototype的constructor属性指向的是Person(), 这是由我们生成Teacher()的方式决定的。

这或许会成为很大的问题,所以我们需要将其正确设置——您可以回到源代码,在底下加上这一行代码来解决:

Teacher.prototype.constructor = Teacher;

注:每一个函数对象(Function)都有一个prototype属性,并且只有函数对象有prototype属性,因为prototype本身就是定义在Function对象下的属性。当我们输入类似var person1=new Person(...)来构造对象时,JavaScript实际上参考的是Person.prototype指向的对象来生成person1。另一方面,Person()函数是Person.prototype的构造函数,也就是说Person===Person.prototype.constructor(不信的话可以试试)。

在定义新的构造函数Teacher时,我们通过function.call来调用父类的构造函数,但是这样无法自动指定Teacher.prototype的值,这样Teacher.prototype就只能包含在构造函数里构造的属性,而没有方法。因此我们利用Object.create()方法将Person.prototype作为Teacher.prototype的原型对象,并改变其构造器指向,使之与Teacher关联。

任何您想要被继承的方法都应该定义在构造函数的prototype对象里,并且永远使用父类的prototype来创造子类的prototype,这样才不会打乱类继承结构。

new 与 Object.create 区别

  1. new的话只能是class(即函数),但是Object.create()的参数可以为对象也可以为函数,
  2. 如果Object.create()的参数是对象的话,那么新的对象会继承原对象的属性;如果参数是类(函数)的话,Object.create()的参数为类(函数)原型,如您所说的,它没有绑定this,并没有属性被继承。
  3. Object.create(null)可以实现一个空对象,即没有原型的对象,但使用new就办不到。

7.4 多对象继承

Object.assign(目标对象, ...源对象) 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

//再创建一个基类
function Animal(age) {
 this.age = age;
}
Animal.prototype.say = function(language) { 
    console.log('you say ' + language);
}

function Student(name, sex, age) {
    Person.call(this, name, sex);
    Animal.call(this, age);
}
//原型链拼接
Student.prototype = Object.create(Person.prototype);
Object.assign(Student.prototype, Animal.prototype);
Student.prototype.constructor = Student;
Student.prototype.getInfo = function() {
    console.log('getInfo: [name:' + this.name + ', sex:' + this.sex + ', age:' +this.age + '].');
};
var s = new Student('coco', 'femal', 25);

7.5 JS继承练习

    function Animal(name, age) {
        this.name = name;
        this.age = age;
    }

    Animal.prototype.eat = function () {
        console.log('吃');
    };

    Animal.prototype.run = function () {
        console.log('跑');
    }

    function Person(name, age, job) {
        Animal.call(this, name, age);
        this.job = job;
    }

    Person.prototype = Object.create(Animal.prototype);
    Person.prototype.constructor = Person;
    
    //Person.prototype.jump 原型方法必须写在继承后
    Person.prototype.jump = function () {
        console.log('跳');
    };
    function Student(name, age, job, className) {
        Animal.call(this, name, age);
        Person.call(this, name, age, job);
        this.className = className;
    }

    Student.prototype = Object.create(Person.prototype);
    Student.prototype.constructor = Student;
    //原型方法必须写在继承后
    Student.prototype.study = function () {
        console.log('学习');
    }


    function Adolescent(name, age, job, className,sex) {
        Animal.call(this, name, age);
        Person.call(this, name, age, job);
        Person.call(this, name, age, job,className);
        this.sex = sex;
    }

    Adolescent.prototype = Object.create(Student.prototype);
    Adolescent.prototype.constructor = Adolescent;
    //原型方法必须写在继承后
    Student.prototype.play = function () {
        console.log('跳绳');
    }

    this.a2 = new Adolescent('张同学',18,'学生','高2.1版',"女");
    this.s1 = new Student('张同学',18,'学生','高2.1版');
    this.p1 = new Person('王老师',26,'教师');
    this.a1 = new Animal('校长',44);

原型方法必须写在设置原型 XXX.prototype = Object.create(...)和设置构造器引用XXX.prototype.constructor = xxx之后,不容会丢失。

7.6 内置对象的扩展

方式1(失败)

直接给对象动态添加属性和方法
	弊端:
		如果操作很多个对象, 则无法共享
		代码比较冗余

例子:arr2的run不能执行~

    var arr = [1,2,3];
    arr.run = function () {
        console.log('跑');
    }
    arr.run();


    var arr2 = [2];
    arr.run = function () {
        console.log('跑');
    }

方式2(容易覆盖)

直接给Array原型对象添加方法
	弊端:
		可能会产生覆盖的情况

例子:覆盖push

    Array.prototype.push = function () {
        console.log('跑');
    };

    var arr = [1,2,3];
    arr.push();


    var arr2 = [2];
    arr2.push();

方式3(推荐)

提供一个新的构造函数
修改构造函数的原型指向为数组的原型对象
	为了能够获取数组里面的属性和方法
	问题: 
		依然会修改数组原型对象内容
优化
	原型对象就是一个对象
	可以直接根据Array创建一个对象, 给新创建的函数原型对象进行赋值
    function MyArray() {}
    MyArray.prototype = new Array();
    MyArray.prototype.run = function () {
        console.log('跑');
    };
    var arr = new MyArray();
    arr.run();

8 深拷贝与浅拷贝

8.1 浅拷贝

8.1.1 遍历

 var obj1 = {name: '张三', age: 18};
    var obj2 = {};

    for (var key in obj1) {
        obj2[key] = obj1[key];
    }
    console.log(obj2);

8.1.2 Object.assign(目标对象, ...源对象)

方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

    var obj3 = {className: '六班'};
    Object.assign(obj3, obj1, {address: '重庆'});
    console.log(obj3);

具有相同的,则属性将被源对象中的属性覆盖

const target = { a: 1, b: 3 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);

console.log(returnedTarget);
// expected output: Object { a: 1, b: 4, c: 5 }

8.2 深拷贝

深拷贝/浅拷贝

区别:在于对引用值的处理

  • 浅拷贝, 直接拷贝一份地址
  • 深拷贝-拷贝地址对应的具体内容

深拷贝实现

  1. 提供一个函数,两个参数(元对象,要拷贝属性的对象)
  2. 在函数中先检查第一个参数是否有值,如果没有值那么就初始化一个空的对象
  3. for..in循环来变量参数2
    1. 检查当前的属性值是什么类型
    2. 如果是值类型,那么就直接拷贝赋值
    3. 如果是引用类型,那么就再调用一次这个方法,去内部拷贝这个对象的所有属性
    var obj = {
        name: "唐三三",
        age: 30,
        friend: ['张森', '夏赢'],
        address: {
            city: '重庆',
            county: '渝北'
        },
        play: function () {
            console.log(this.name + '喜欢桌球');
        }
    }

    //deep Copy
    function deepCopySource2Target(source, target) {
        for (var key in source) {
            var sourceValue = source[key];

            if (!(sourceValue instanceof Object)) {
                //value object
                target[key] = sourceValue;
            }
            else {
                //object object
                var temp = new sourceValue.constructor;
                deepCopySource2Target(sourceValue, temp);
                target[key] = temp;
            }
        }
    }

    var newObj = {};
    deepCopySource2Target(obj, newObj);
    console.log(newObj);
    newObj.play();

参考:

js继承实现之Object.create - 太阳的眼睛
javascript中继承-MDN
继承与原型链-MDN
ObjectPlayground.com - 一个非常有用的、用于了解对象的交互式学习网站。
彻底搞懂JavaScript中的继承- 掘金

posted @ 2019-10-08 00:00  【唐】三三  阅读(477)  评论(0编辑  收藏  举报