函数——面向对象(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);
三、原型及原型链
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详解