函数——面向对象(oop&&构造函数&原型链)

Posted davina123

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了函数——面向对象(oop&&构造函数&原型链)相关的知识,希望对你有一定的参考价值。

  一、概念

  面向对象:对于软件开发模式有两种,一种是面向对象,一种是面向过程。面向过程:只完成自己所需要的操作,但是这种设计缺少可维护性。面向对象:本质上是组件化的设计(模块化设计),方便局部维护但是设计上的要求规范比较多,也就是模块化的设计最重要的就是标准,以及整个项目的整体把控。

  面向对象的概念实际上有以下几个特点:封装性,保护内部的操作对外不可见;继承性:相当于一代代的传承问题;多态性:在一个范围内的定义改变。js可以模拟实现继承和封装,但不能模拟实现多态,所以js是基于事件,基于对象的语言。

  面向对象编程(Object Oriented Programming)

    简称"OOP",是一种编程开发思想,它将真实世界的各种复杂关系抽象成一个个对象,然后由对象之间的分工合作完成对现实世界的模拟。

  类和对象的概念:

    类:具有相同特征(属性)和行为(方法)的集合。如:人类->:属性:姓名、三围、星座  方法-> 吃、喝

    对象:从类中拿出具有确定属性值和方法的个体叫做对象。可以这样说,对象是包含属性和方法的集合,万物皆对象。

    二者关系:类是抽象的,对象是具体的,类是对象的抽象化,对象是类的具体化。

  二、创建对象

    1. 用字面量创建,不利于复用代码。

    <script>
        var person = {
            name: davina,
            age: 20,
            play: function () {
                console.log(write code);
            }
        }
        console.log(person); //{name: "davina", age: 20, play: ƒ}

        person.heigth = 170CM;
        console.log(person);  //{name: "davina", age: 20, heigth: "170CM", play: ƒ}
    </script>

    可以另外添加属性和方法,添加属性即为描述对象的特征,属性的值为非函数的任意数据类型,添加的方法是实现对象的一些功能,方法的值为函数。

  2、构造函数

    在js中,构造函数就是一个用来生成对象的函数,所有的对象都是由它创建。构造函数首字母一般大写,它与普通函数的区别是是否由new操作符调用 。new是语法糖,使用new操作符调用构造函数时,会创建一个新对象,将this指向该对象(将构造函数的作用域赋给新对象)。

    在普通函数执行的基础上加了“new xxx()”,这样的话就不是普通执行了,而是构造函数执行,当前的函数名称为“类名”,接收的返回结果是当前类的一个实例,实例由构造函数生成,平时用的都是实例化对象。简单来说,通过构造函数new出来的的对象叫做实例,创建对象的过程叫做实例化。

function Fn(){
    //......
}
let f = new Fn();//Fn是类,f是类的一个实例
console.log(f);
let f2 = new Fn();//f2也是Fn的一个实例,f2和f是独立分开的
function Func() { }
let f = Func();
console.log(f);//undefined

/* 
 *把它当做是普通函数执行(形成私有上下文,初始化作用域链,初始化this,初始化arguments,形参赋值,变量提升,代码执行......)
  *f获取的是函数的返回结果(函数中没有return,所以f为undefined)
 *
 */
function Func() { }
let f = new Func();
console.log(f);//Func{}
/* 
 * 这是构造函数执行,当做类来执行,此时Func被称为是‘类‘,返回的结果(f)被称为当前‘类’的一个‘实例’,它是一个实例对象
 */

    构造函数和普通函数的联系与区别:

    1.构造函数和普通函数执行大体上是一致的(它具备普通函数的一面)

    2.区别:在初始化作用域链后和初始化this前,首先默认创建一个对象(这个对象就是当前类的实例),让上下文中的this指向这个对象

    3 . 构造函数执行,不写return,浏览器会默认返回创建的实例,但是如果我们自己写了return分为两种情况:

      1).return的是一个基本值,返回的结果依然是类的实例,没有受到影响

                 2).如果返回的是引用值,则会把默认返回的实例覆盖,此时接收到的结果就不在是当前类的实例了。所以构造函数执行的时候 ,尽量减少return的使用,防止覆盖实例。

    4.用new调用的函数,这个函数就是一个用来创建对象的函数即构造函数,它得到的结果永远是一个对象,不管函数有无返回值。

技术图片

function Func(x, y) {
    // num只是当做普通函数执行的时候,给私有上下文中设置的私有变量,和实例对象没有关系,只有this是实例对象,所以this.XXX=XXX才和实例有关系
    let num = x + y;
    this.x = x;
    this.y = y;

    // return {
    //     name: ‘xxx‘
    // }; //=>返回基本类型值,f2依然是创建的实例对象;如果自己返回的就是一个引用值,一切以自己返回的为主,此时的f2={name:‘xxx‘}而不再是当前类的实例了
}
let f2 = new Func(10, 20);
console.log(f2);

  instanceof 可以检测当前对象是否为某个类的实例

//语法:实例 instanceof 类 返回boolean类型
f2 instanceof (Func);
   每一个对象(包含实例对象)都有很多属性和方法,在自己堆内存中存储的都是私有的属性方法,基于__proto__原型链查找类prototype原型上的都是共有的属性方法。一个对象身上有另一个对象身上的属性或方法,这种具有的方式就叫做继承,生成的实例具有构造函数身上的属性和方法。(不光是实例可以继承构造函数,对象与对象也是可以继承的)。

  三、原型及原型链

   prototype(原型):每一个函数(es6箭头函数除外)都具备prototype属性(原型属性),它属性值是一个对象。指向了当前函数所在的引用地址。在这个对象中会存储当前类的公共属性和方法;

   constructor:在prototype的堆内存中,如果是浏览器为其默认开辟的堆内存,会存在一个内置的属性constructor,属性值就是当前类本身;

   __proto__:每一个对象都有一个内置属性:__proto__(原型链属性),属性值是当前实例所对应类的prototype。

   原型链:调用当前实例对象的某个属性(成员访问),先看自己私有属性中是否存在,存在调用的就是自己私有的;不存在,则默认按照__proto__找所属类prototype上的公有属性和方法;如果还没有,再基于prototype上的__proto__继续向上级查找,直到找到Object.prototype为止

   hasOwnProperty:检测某个属性是否为对象的私有属性

/* 
 * 函数数据类型:普通函数、类(内置类/自定义类)、箭头函数  => 都是Function的实例
 * 对象数据类型:普通对象、数组对象、正则对象、日期对象、实例对象、函数对象(函数也是一个对象,也像普通对象一样,有自己的键值对)、类的prototype也是对象  => 都是Object的实例
 */
function Fn() {
    this.x = 100;
    this.y = 200;
    this.getX = function () {
        console.log(this.x);
    }
}
Fn.prototype.getX = function () {
    console.log(this.x);
}
let f1 = new Fn();
console.log(f1.getX === Fn.prototype.getX);
console.log(f1.getX());
console.log(f1.__proto__.getX());

 技术图片

  重定向原型指向:在我们重定向原型指向的时候,之前原型上的内容会丢失,包含constructor,为了保证结构的完整性,我们一般要手动设置constructor属性。

function fun(){
    this.a=0;
    this.b=function(){
        alert(this.a);
    }
}
fun.prototype={
    b:function(){
        this.a=20;
        alert(this.a);
    },
    c:function(){
        this.a=30;
        alert(this.a)
    }
    fun.prototype.constructor = fun;
}
var my_fun=new fun();
my_fun.b();
my_fun.c();

技术图片

   内置类的原型上扩展方法:因为内置类的原型上会默认很多常用的方法,但是在真实的项目开发中这些内置的方法往往不足以完成开发要求,所以我们向内置类的原型上扩展方法,这样调用起来比较的方便。在方法执行的时候,方法中的this就是当前处理的那个实例。

  在向内置类的原型上扩展方法时我们要注意以下几点:

    1):  扩展的方法名字最好设置前缀,如MyXXX,防止自己扩展的方法替换了内置的方法;

    2):  this的结果一定是对象数据类型值,所以向基本数据类型的原型上扩展方法,方法被执行时,方法中的this不在是基本类型,但是还是按照原始的方式处理即可

    3):  如果返回的结果依然是当前类的实例,还可以继续调用当前类原型上其它的方法(如果不是自己类的实例,可以掉用其它类原型上的方法) => “链式写法”

  对于一个对象来说,它的属性方法(私有的/公有的)存在“枚举”的特点:在for in循环的时候是否可以遍历到,能遍历到的是可枚举的,不能遍历到的是不可枚举的。

String.prototype.queryURLParams = function queryURLParams(key) {
    // this -> 当前要处理解析的URL
    let obj = {};
    this.replace(/([^?&=#]+)=([^?&=#]+)/g, (_, $1, $2) => obj[$1] = $2);
    this.replace(/#([^?&=#]+)/g, (_, $1) => obj[_HASH] = $1);
    return typeof key === "undefined" ? obj : obj[key];
};
let url = "https://support.google.com/chrome/?p=help&ctx=keyboard";
let result = url.queryURLParams();
console.log(result);

String.prototype.indexOf = function indexOf() {
    return OK;
};
console.log(url.indexOf(?));
(function () {
    function handleNum(num) {
        num = Number(num);
        return isNaN(num) ? 0 : num;
    }
    Number.prototype.plus = function plus(num) {
        num = handleNum(num);
        return this + num;
    };
    Number.prototype.minus = function minus(num) {
        num = handleNum(num);
        return this - num;
    };
})();

let n = 10;
let m = n.plus(10).minus(5);
console.log(m); //=>15
// obj.__proto__=Object.prototype;
// func是可枚举的属性了
Object.prototype.func = function func() {};
let obj = {
    name: davina,
    age: 18
};
for (let key in obj) {
    // 在遍历的时候,对于原型上扩展的公共属性方法,我们过滤掉,只遍历对象中私有的属性方法(必须是可枚举的)
    if (!obj.hasOwnProperty(key)) break;
    console.log(key);
} 

   四、es6中新增class关键字创建类

    用class关键字声明一个类,首字母大写

    语法规范:

    类里面的constructor函数,存放的是类的共有属性,可以接受传递过来的参数,同时返回实例对象,只要new生成实例时,就会自动的调用这个函数,如果不写这个函数,类会自动生成这个函数;生成实例的new不能省略;创建类时,类名后面不加小括号,生成实例时,类后面加小括号;构造函数里不需要加function(类里面的所有函数都不需要添加function);多个函数方法之间不需要用逗号分隔。

    <!-- <script>
       class name[extends]{
           //calss body
       }
       extends为继承,是个可选的参数
    </script> -->

    强调的是:声明的类还是一个构造函数,共享方法直接写,它会自动的放在prototype上,共享属性需要写在constructor里

    <script>
        //es6的写法  创建一个Person类
        class Person {
            //类的共有属性放到constructor中
            constructor(name, age) {
                this.name = name;
                this.age = age;  //共享属性
            }
            say() {          //共享方法  自动放在prototype
                console.log(人可生如蚁而美如神!)
            }
        }
//利用类创建对象 const [person1, person2] = [new Person(davina, 20), new Person(lisa, 21)]; //这是两个实例 person1.sex = male; console.log( typeof Person, //function 构造函数,虽说是类,但本质上还是构造函数 Person.prototype.constructor === Person, // true person1.__proto__ === Person.prototype, // true person1 instanceof Person, //true person2.constructor == Person, ///true Object.getOwnPropertyNames(person1),// ["name", "age", "sex"] 找实例身上自己的属性   person1.hasOwnProperty(say), //false say是放在prototype里,继承而来,并不是实例身上自有的 Object.keys(Person.prototype), // [] 内置身上的方法一般是不可以被枚举 ) // class Person{} //SyntaxError: Identifier ‘Person‘ has already been declared 不能重复的声明 //构造函数和实例放在一起 const MyPerson = new class { move() { console.log(12); } } MyPerson.move(); //12 </script>

以上是关于函数——面向对象(oop&&构造函数&原型链)的主要内容,如果未能解决你的问题,请参考以下文章

Java面向对象(OOP)--面向过程 & 面向对象 & OOP详解

34JavaScript面向对象(内置构造函数&相关方法|属性|运算符&继承&面向对象)

OOP 反射&元类

constructor&object 的联系与对比

php面向对象(OOP)编程

8--oop