JavaScript面向对象

2019-04-24  本文已影响0人  风暴java之灵

创建默认对象

  1. JavaScript中提供了一个默认的类Object,我们可以通过这个类创建对象
  2. 由于是用系统默认的类创建的对象,系统不知道我们需要给对象添加什么属性和方式,所以需要手动的添加我们需要的属性和方法
  3. 如何给一个对象添加属性
    对象名.属性名 = 值 ;
  4. 如何给一个对象添加方法
    对象名.方法名 = function (){
    代码;
    }
let obj = Object(); //创建一个名为obj的对象
obj.name = "wj";//给obj对象添加一个name的属性
obj.say = function(){
      console.log("我是对象");
}
obj.say();// 结果  我是对象

第二种方法

let obj = {}; //创建一个名为obj的对象
obj.name = "wj";//给obj对象添加一个name的属性
obj.say = function(){
      console.log("我是对象");
}
obj.say();// 结果  我是对象

第三种方法
注意: 属性名称和取值之间用冒号隔开, 属性和属性之间用逗号隔开

let obj = {; //创建一个名为obj的对象
name : “wj” ,//给obj对象添加一个name的属性
say : function(){
      console.log("我是对象");
}
}
obj.say();// 结果  我是对象

函数和方法的区别

函数: 没有和其他类绑定在一起的
方法: 和其他类绑定在一起的
函数可以直接调用,方法只能通过对象调用

let obj = { //创建一个名为obj的对象
name : "wj" ,
say : function(){
   console.log(this);
}
}
obj.say();  //结果为  obj对象

工厂函数

专门用于创建对象的函数被称为工厂函数.

function createPerson(myName,myAge) { //创建工厂函数
       let obj = new Object();
       obj.name = myName;
       obj.age = myAge;
       obj.say = function(){
      console.log("我是工厂函数")   
}
return obj; //返回对象
}
let obj = createPerson("wj",18);
let obj2 = createPerson("李四",22);
console.log(obj);
console.log(obj2);

构造函数

  1. 构造函数也是用来创建对象的
    构造函数本质是工厂函数的简写
  2. 构造函数和工厂函数的区别
    构造函数的函数名称首字母必须大写
    构造函数只能通过new来调用
function Person (myName,myAge) {
//系统会自动添加  let obj = new Object();  let this = obj;
       this.name = myName;
       this.age = myAge;
       this. say = function (){
              console.log("我是构造函数");
}
//系统自动添加  return this;
}
let obj = new Person("文件",18);
let obj1 = new Person("wj",21);
console.log(obj);
console.log(obj1);
function Person (myName,myAge) {
       this.name = myName;
       this.age = myAge;
}
Person.prototype = {
      this. say = function (){
              console.log("我是构造函数");
}
}

let obj = new Person("文件",18);
let obj1 = new Person("wj",21);
    console.log(obj.say===obj1.say);//结果为 true
obj.say();  //结果为  我是构造函数

prototype的特点

  1. 存储在prototype中的方法和属性可以被对应构造函数创建的对象共享
    2.如果prototype中的存储的方法或者属性和构造函数的同名,那么访问到的是构造函数中的方法或者属性
    prototype的应用场景:主要用来存放所有对象都相同的一些属性以及方法
    如果是对象特有的属性或者方法,一般放到构造函数中

函数对象关系

原型链

对象中由proto组成的链条,我们称为原型链
对象在查找属性和方法时,会优先会在自身找,如果没有就会去上一级的原型对象中查找,如果找到Object的原型对象中还没有,就会报错。
注意:在给prototype赋值的时候,为了不破坏对象中原有的关系,我们需要手动指定constructor指向谁

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

        Person.prototype = {
            constructor:Person,
            say :function () {
                console.log("我是原型对象");
            }
        }
let obj = new Person("wj",21);
console.log(obj.__proto__ === Person.prototype);//结果为 true 
##属性注意点
在JavaScript对象中,如果要给一个对象不存在的属性或者方法设定的值得时候,他不会去原型对象中去查找,而是会给这个对象添加这个不存在的方法或者属性
```javaScript  
 function Person (myName,myAge) {
            this.name = myName;
         this.age = myAge;
        }

        Person.prototype = {
            constructor:Person,
             sex: 2,
            say :function () {
                console.log("我是原型对象");
            }
        }
let obj = new Person("wj",21);
console.log(obj.sex);//结果 2
console.log(obj.__proto__.sex);//结果 2
obj.sex=1;
console.log(obj.sex);//结果 1
console.log(obj.__proto__.sex);//结果 2

三大特性之封装性

  1. 当你的属性暴露在外部时,封装可以防止别人随意的修改你的数据
  2. 封装是将数据隐藏起来,只有用特定的方法才可以设置或者调用数据,不能被外界随意修改,这样降低了数据被误用的可能
  function Person (myName,myAge) {
            this.name = myName;
            let age = 45;
            this.setAge = function (myAge) {
                if(myAge>0&&myAge<150)
                    age = myAge;
            }
            this.getAge = function () {
                return age;
            }

        }

        Person.prototype = {
            constructor:Person,
            say :function () {
                console.log("我是构造函数");
            }
        }
         let obj = new  Person("wj",21);
        console.log(obj.getAge()); // 结果 21
        obj.setAge(86);
        console.log(obj.getAge());// 结果 86
  function Person (myName,myAge) {
            this.name = myName;
            let age = 45;
            this.setAge = function (myAge) {
                if(myAge>0&&myAge<150)
                    age = myAge;
            }
            this.getAge = function () {
                return age;
            }

        }

        Person.prototype = {
            constructor:Person,
            say :function () {
                console.log("我是构造函数");
            }
        }
         let obj = new  Person("wj",21);
        console.log(obj.getAge()); // 结果 21
        obj.setAge(86);
        console.log(obj.getAge());// 结果 86
      obj.age= -3;  //当前对象新增一个不存在的属性age
console.log(obj.age); //结果为 -3

属性方法分类

通过实例对象调用的方法或者属性,被称为实例方法或者属性
通过构造函数调用的方法或属性,被称为静态方法或者属性

三大特性之继承

在企业开发中如果构造函数和构造函数之间的关系是is a关系, 那么就可以使用继承来优化代码, 来减少代码的冗余度

  1. 子构造函数名.prototype = 父类实例对象
  2. 父类实例对象.constructor = 子类构造函数名
  function Person (myName,myAge) {
            this.name = myName;
         this.age = myAge;
        }

        Person.prototype = {
            constructor:Person,
            say :function () {
                console.log("我是原型对象");
            }
        }
let obj = new Person("wj",21);
 function Student (myName,myAge,myScore) {
           this.score = myScore;
          this.say = function() {
      console.log("我是子类");
        }
Student.prototype = new Person();
Student.prototype.constructor = Student;
let  stu = new Student("w",12,88); //在Student构造函数中找不到myName,myAge对应的属性  
所以该方法有个弊端  不能在创建Student对象的同时给继承父类属性赋值
function test(){
   name : 12;
}
function fn(){
console.log(this);
}
fn();//返回window
let obj = fn.bind(test);
obj(); //返回 test

bind方法改变this指向的同时也可以设定参数,只要将设定的参数写在新指向的对象名后面,用逗号隔开

function test(){
   name : 12;
}
function fn(a,b){
console.log(a,b);
console.log(this);
}
fn(1,10);//返回1 10  window
let obj = fn.bind(test,2,9);
obj(); //返回 2 , 9  test
function test(){
   name : 12;
}
function fn(){
console.log(this);
}
fn();//返回window
 fn.call(test);//返回 test

call方法改变this指向的同时也可以设定参数,只要将设定的参数写在新指向的对象名后面,用逗号隔开

function test(){
   name : 12;
}
function fn(a,b){
console.log(a,b);
console.log(this);
}
fn(1,10);//返回1 10  window
 fn.call(test,2,9); //返回 2 , 9  test
function test(){
   name : 12;
}
function fn(){
console.log(this);
}
fn();//返回window
 fn.apply(test);//返回 test

call方法改变apply指向的同时也可以设定参数,只不过需要用数组来传递参数

function test(){
   name : 12;
}
function fn(a,b){
console.log(a,b);
console.log(this);
}
fn(1,10);//返回1 10  window
 fn.apply(test,[2,9]); //返回 2 , 9  test

继承方法二 用call方法在子类构建函数中改变父类的this指向,这种方法解决了不能在创建Student对象的同时给继承父类属性赋值

//建议去温习一下工厂函数和构造函数
  function Person (myName,myAge) {
            //let obj = new Object();
            //let this = obj;
           // this = stu ;
            this.name = myName;
         this.age = myAge;
      // return this;
        }
let obj = new Person("wj",21);
 function Student (myName,myAge,myScore) {
           //let  stu = new Object();
           //let this = stu;
           Person.call(this,myName,myAge); //Person.call(stu,myName,myAge);
           this.score = myScore;
          this.say = function() {
      console.log("我是子类");
        }
}
let stu = new Student("ww",21,99);
 console.log(stu);

弊端:父类构造函数的原型对象中的东西无法获取

  function Person (myName,myAge) {
            this.name = myName;
         this.age = myAge;   
        }
  Person.prototype.speak= function () {
            console.log("我是父类的");
        }
 function Student (myName,myAge,myScore) {    
           Person.call(this,myName,myAge); 
           this.score = myScore;
          this.say = function() {
      console.log("我是子类");
        }
}
Student.prototype = Person.prototype ;
Student.prototype.constructor  = Student;
let stu = new Student("ww",21,99);
 console.log(stu);
stu.speak();//结果 我是父类的

弊端:破坏了原有对象的三角恋关系,由于父类和子类的原型对象是同一个,如果给子类添加方法或者属性,父类也会同步添加这个方法或者属性

  1. 在子类的构造函数中通过call借助父类的构造函数
  2. 将子类的原型对象修改为父类的实例对象
该方式解决了继承第三代的弊端
 function Person (myName,myAge) {
            this.name = myName;
         this.age = myAge;   
        }
  Person.prototype.speak= function () {
            console.log("我是父类的");
        }
 function Student (myName,myAge,myScore) {    
           Person.call(this,myName,myAge); 
           this.score = myScore;
          this.say = function() {
      console.log("我是子类");
        }
}
Student.prototype =  new Person() ;
Student.prototype.constructor  = Student;
let stu = new Student("ww",21,99);
Student.prototype.run = function(){
            console.log("run");
        }
let per = new Person();
per.run(); //报错   解决了继承第三代的弊端

三大特性之多态

1.什么是强类型语言, 什么是是弱类型语言
1.1什么是强类型语言:
一般编译型语言都是强类型语言,
强类型语言,要求变量的使用要严格符合定义
例如定义 int num; 那么num中将来就只能够存储整型数据
1.2什么是弱类型语言:
一般解释型语言都是弱类型语言,
弱类型语言, 不会要求变量的使用要严格符合定义
例如定义 let num; num中既可以存储整型, 也可以存储布尔类型等
1.3由于js语言是弱类型的语言, 所以我们不用关注多态
2.什么是多态?
多态是指事物的多种状态
例如:
按下 F1 键这个动作,
如果当前在 webstorm 界面下弹出的就是 webstorm 的帮助文档;
如果当前在 Word 下弹出的就是 Word 帮助;
同一个事件发生在不同的对象上会产生不同的结果。
3.多态在编程语言中的体现
父类型变量保存子类型对象, 父类型变量当前保存的对象不同, 产生的结果也不同

ES6类和对象

从ES6开始系统提供了一个名称叫做class的关键字, 这个关键字就是专门用于定义类的
格式 :
class 类名 {
实例属性;
实例方法;
静态属性;
静态方法;
}
如果想在创建对象的时候动态的设置属性或者方法,可以在constructor构造函数中设定

class Person {
//当创建对象的时候系统会自动调用constructor构造函数
  constructor(myAge) {
                 this.age = myAge;
       }
name = "wj"; //实例属性
say(){
console.log("我是实例方法");
}
static sex = 2 ; //静态属性
static test(){
    console.log("我是静态方法");//静态方法
}
}
let per = new Person(21);
console.log(per);

但是以上实例属性和静态属性的写法并不是ES6的标准写法,因此大部分的浏览器都不支持
标准写法是将实例属性写到constructor()里面。
在ES6的标准中,static只能用来定义静态方法,不能定义静态属性,静态属性只能动态的添加

class Person {
//当创建对象的时候系统会自动调用constructor构造函数
  constructor(myAge) {// constructor中的东西相当于ES6之前构造函数中的内容
                 this.age = myAge;
                 this.name = "wj"; //实例属性
                 this.say = function() {
                  console.log("我是实例方法");
                                                           }
       }
speak(){ //在constructor中的定义的方法会直接存放到原型对象中
console.log("我在原型对象中");
}
static test(){
    console.log("我是静态方法");//静态方法
}
}
Person.sex = 2 ; //静态属性(只能通过这种方法定义静态属性)
let per = new Person(21);
console.log(per);

如果通过class定义类, 那么不能自定义这个类的原型对象
如果想将属性和方法保存到原型中, 只能动态给原型对象添加属性和方法

class Person {
            constructor(myAge) {
                this.age = myAge;
                this.name = "wj"; //实例属性
                this.say = function(){
                    console.log("我是实例方法");
                }
            }
            static test(){
                console.log("我是静态方法");//静态方法
            }
        }
            Person.sex = 2 ;//定义静态变量
    /*Person.prototype.speak = function() {
               console.log("我是动态添加的");
}
      let per = new Person(21);
         console.log(per);
        per.speak(); //  结果 我是动态添加的
*/
        let obj ={//定义这个类的原型对象
            constructor: Person ,
            name : 12,
            speak:function() {
                console.log("我是定义的原型对象");
            }
        }
        Person.prototype = obj ; 
    let per = new Person(21);
        console.log(per);
        per.speak(); //报错

ES6继承

ES6中有专门的继承方法:通过extends继承
继承格式:class 子类名 extends 父类名 {}
子类在constructor函数中可以通过super调用父类的构造函数

 class Person {
        constructor(myAge){
            this.age = myAge;
            this.say = function () {
                console.log("我是父类");
            }
        }
speak(){
            console.log(this.age);
        }
    }
    class Student extends Person {//继承
        constructor(myAge,myScore){
            super(myAge);//调用父类的构造函数(相当于Person.call(Student,myAge,myScore))
            this.score = myScore;
        }
        study(){
            console.log("我是学生");
        }
    }
    let stu = new Student(21,99);
  stu.speak();//结果 21

获取对象类型

如果想知道某个对象是由谁创建的可以通过console.log(对象名.constructor.name);来获取

 class Person {
        constructor(myAge){
            this.age = myAge;
            this.say = function () {
                console.log("我是父类");
            }
        }
    }
let obj =  new Person();
    console.log(obj.constructor.name); //结果为  Person

instanceof关键字

instanceof关键字用于判断对象是否是谁的实例 如果是返回true 否则返回false

 class Person {
        constructor(myAge){
            this.age = myAge;
            this.say = function () {
                console.log("我是父类");
            }
        }
    }
let obj =  new Person();
    console.log(obj instanceof Person); //结果为  true

注意 如果判断的构造函数的原型在实例对象的原型链上 也会返回true

 class Person {
        constructor(myAge){
            this.age = myAge;
            this.say = function () {
                console.log("我是父类");
            }
        }
    }
class Student extends Person{
           constructor(myAge){
                super(myAge);
           }
 }
let obj = new Student(12);
console.log(obj instanceof Student); //结果为  true
console.log(obj instanceof Person); //结果为  true

isPrototypeOf

isPrototypeOf ()用于判断一个对象是否是另一个对象的原型

 class Person {
        constructor(myAge){
            this.age = myAge;
            this.say = function () {
                console.log("我是父类");
            }
        }
    }
let obj =  new Person();
    console.log(Person.prototype.isPrototypeOf(obj)); //结果为  true

注意 只要调用者在传入对象的原型链上就会返回true

 class Person {
        constructor(myAge){
            this.age = myAge;
            this.say = function () {
                console.log("我是父类");
            }
        }
    }
class Student extends Person{
           constructor(myAge){
                super(myAge);
           }
 }
let obj = new Student(12);
console.log(Person.prototype.isPrototypeOf(obj)); //结果为  true
console.log(Student.prototype.isPrototypeOf(obj)); //结果为  true

判断对象属性是否存在

格式: "属性名" in 对象名 :
如果在对象的类里或者原型对象里有就返回true,否则返回false

class Person {
  constructor(){
this.name = "wj";
}
}
Person.prototype.age = 18;
let obj = new Person();
console.log("name" in obj);//true
console.log("age" in obj);// true

格式 对象名.hasOwnProperty("属性名");
该方法值会在类本身查找,不会去原型对象中查找

class Person {
  constructor(){
this.name = "wj";
}
}
Person.prototype.age = 18;
let obj = new Person();
console.log(obj.hasOwnProperty("name"));//true
console.log(obj.hasOwnProperty("age"));// false

对象的增删改查

增添改操作
有两种格式:

  1. 对象名.属性/方法名 = 值;
  2. 对象名["属性/方法名"] = 值;
    删除操作
    两种格式:
  3. delete 对象名.属性/方法名;
  4. delete 对象名["属性/方法名"];
class Person{
}
let obj = new Person();
obj["name"] = "wj"; //添加属性
obj["say"] = function (){ //添加方法
      console.log("我是添加的")
}
delete  obj["name"]; //删除属性

对象遍历

通过高级for循环可以拿到对象中所有的属性和方法名称
格式 for(let key in 对象名){//会将对象中所有的方法和属性取出来依次赋值给key

}

```javaScript
 class Person {
        constructor(myAge,myName){
            this.age = myAge;
            this.name = myName;
            this.say = function () {
                console.log("我是父类");
            }
        }
speak(){ // 该方法存放在原型对象中
   console.log("我在原型对象中");
}
    }
let obj = new Person(21,"wj");
for(let key in obj) {
    //console.log(obj.key); //结果为undefined(p.key意思是obj中名为
key的属性,obj中并没有,所以结果为未定义)
   console.log(obj[key]);//相当于obj["key"]  = obj["age"] 取出的才是属性和方法
}//结果中没有speak方法是因为高级for循环不会载原型对象中查找
//如果不想取出对象中的方法可以通过如下方法来实现
 class Person {
        constructor(myAge,myName){
            this.age = myAge;
            this.name = myName;
            this.say = function () {
                console.log("我是父类");
            }
        }
speak(){ // 该方法存放在原型对象中
   console.log("我在原型对象中");
}
    }
let obj = new Person(21,"wj");
for(let key in obj) {
if(obj[key] instanceof Function){
continue;
}
   console.log(obj[key]);
}

对象解构

对象解构和数组一样
不同在于:
1.符号不同,数组用[], 对象用{};

  1. 对象解构中左边的变量名称必须和右边的一致,否则解构不出来
 class Person {
        constructor(myAge,myName){
            this.age = myAge;
            this.name = myName;
            this.say = function () {
                console.log("我是父类");
            }
        }
}
let obj = new Person(21,"wj");
let {name,age} = obj ;
 console.log(age,name);//  21 "wj"

应用场景 数组或者对象赋值的时候简化代码

class Person {
        constructor(myAge,myName){
            this.age = myAge;
            this.name = myName;
            this.say = function () {
                console.log("我是父类");
            }
        }
}
let obj = new Person(21,"wj");
function speak({name,age}) {
console.log(name,age);
}
speak(obj); //  "wj"   21

深拷贝和浅拷贝

深拷贝是指给给新变量赋值时,不会影响原先变量的值
默认情况下基础类型的都是深拷贝
浅拷贝是指给给新变量赋值时,也会改变原先变量的值
默认情况下引用类型的都是浅拷贝

let num1 = 123;           //深拷贝
        let num2 = num1;
        num2 = 666; // 修改形变量的值
        console.log(num1); // 123
        console.log(num2);  //666
class Person{     //浅拷贝
            name = "lnj";
            age = 34;
        }
        let p1 = new Person();
        let p2 = p1;
        p2.name = "zs"; // 修改变量的值
        console.log(p1.name);// "zs"
        console.log(p2.name);  "zs"

对象深拷贝

如果对象中只有基础类型的属性的话可以通过assign方法实现
assign可以将第二个参数的对象的属性拷贝到第一个参数的对象中

 class Person {
       constructor(myAge, myName) {
           this.age = myAge;
           this.name = myName;
           this.say = function () {
               console.log("我是父类");
           }
       }
   }
   class Student {

   }
       let obj = new Person(21,"wj");
        let p2 = new Object();
        Object.assign(p2,obj);//深拷贝
         console.log(p2);

如果对象中含有方法的话,用上面的办法就能完全的深拷贝
这个时候就需要我们自定义一个方法来实现深拷贝

function deepCopy(a,b) {//自定义方法实现对象深拷贝
           for(let key in b){// 1.通过遍历拿到source中所有的属性
             let bValue = b[key];// 2.取出当前遍历到的属性对应的取值
           if(b[key] instanceof Object){// 3.判断当前的取值是否是引用数据类型
                 a[key] = new bValue.constructor;
                     deepCopy(a[]key],bValue);//运用递归拷贝对象
                     }

             else {
                       a[key] = bValue; 
                    }   
}
}

上一篇下一篇

猜你喜欢

热点阅读