前端相关|JS中的类,函数,对象、原型


文章目录

    • 01.JS中的数据类型概述
      • JS中的数据类型
      • 基本数据类型和引用数据类型的区别
    • 02.JS中对象的概述
      • 对象的分类
      • 创建对象的方式
      • 给对象中添加属性
      • 读取对象中的属性
      • 删除对象中的属性
      • 对象中的方法
    • 03.函数概述
      • 创建函数的方式
      • 调用函数的方式
      • 函数的返回值
      • 函数的传参的注意事项
      • 变量的作用域
      • 变量和函数的声明提前
    • 04.函数和对象的对比
      • 什么是类
      • 什么是构造函数
      • JS对象的由来
      • 构造函数和普通函数有什么区别
    • 05.JS中构造函数的两种语法
      • ES5构造函数语法
      • ES6构造函数语法
      • 对比分析
    • 06.原型(prototype)

01.JS中的数据类型概述 JS中的数据类型
JS的数据类型分类:基本数据类型引用数据类型
基本数据类型:
  • String(字符串)、Number(数值),Boolean(布尔类型),Null(空值)、Undefined(未定义)
引用数据类型:
  • Object(对象):对象是一种复合数据类型,在对象中可以保存多个不同数据类型的属性
基本数据类型和引用数据类型的区别
基本数据类型保存在栈中,特点是值与值之间独立存在,修改一个变量的值不会影响另外一个
var a=123; var b=a; // 此处是把a的值123直接赋给b,后面改变a的值不会影响b的值 a++; console.log(a); // 124 cinsole.log(b); // 123

引用数据类型(对象)保存在堆中,特点是每次创建一个对象都会在堆内存中开辟一段空间,变量保存的是对象的地址(对象的引用),如果两个变量保存的是同一个对象的引用,通过一个变量修改属性时,另一个也会受影响。
对比基本数据类型:两个变量中存放的是同一个地址,我们修改的时候不是修改这变量的值(地址),而是修改该地址指向的内存中的值,也就是无论怎么修改,两个变量中的保存的地址值都没变,类似于同一个变量。
var obj=new Object(); // 此处变量中保存的值是,对象的地址。 obj.name="AISMALL"; var obj2=obj; // 本质就是两个变量指向一个地址,修改的时候是修改该地址指向的内存中的值。 obj.name="SMALL"; console.log(obj.name); // SMALL cinsole.log(obj2.name); // SMALL

注意:比较两个变量时,对于基本数据类型,比较的就是值,对于引用数据类型比较的是地址,地址相同才相同。
02.JS中对象的概述 对象的分类
  • 内建对象:由ES标准中定义的对象,在任何ES的实现中都可以使用,如:Math 、String、Number、Boolean、Function,Object等。
  • 宿主对象:由JS的运行环境提供的对象,目前来讲主要指浏览器提供的对象,如,BOM、DOM。
  • 自定义对象:由开发人员创建的对象
创建对象的方式
方式一:使用new关键字创建对象
var obj=new Object();

方式二:使用对象字面量创建对象
var obj={ };

  • 使用字面量创建对象时可以直接指定对象中的属性
var obj={ name:"AISMALL", age:29, gender:"男" }

  • 注意:对字面量创建对象的方式,属性名可以加引号也可以不加,建议不加,如果使用特殊名字,必须加引号。
var obj={ "name01":"AISMALL01", "name02":"AISMALL02", age:29, gender:"男" }

方式三:使用工厂方式创建对象
// fun为创建对象的工厂 function fun(name,age){ // 创建的对象 var obj=new Object(); // 向对象中添加属性 obj.name=name; obj.age=age; obj.sayName=function(){ console.log("name="+name+" "+"age="+age); } //将该对象返回 return obj; } // 使用工厂创建对象 var obj01=fun("AISMALL",18); obj01.sayName();

给对象中添加属性
由于JS中对于属性的命名没有任何要求,不需要遵守标识符的规范,但是在开发中,尽量按照标识符的要求去写。
语法:
  • 对象.属性名 = 属性值;
  • 对象["属性名"] = 属性值; 如果使用的变量名特殊,可以使用这种方式给对象添加属性。
注意:属性值可以任意数据类型,可以使基本数据类型,也可以是引用数据类型(对象里面套对象)
读取对象中的属性
语法:
  • 对象.属性名;
  • 对象["属性名"];
注意:如果读取的属性对象中没有,并不会报错,而是返回undefined
如何检查一个对象中是否含有某个属性?in运算符
  • 语法:"属性名" in 对象
    • 如果在对象中含有该属性,则返回true,没有则返回false。
删除对象中的属性
语法:
  • delete 对象.属性名;
  • delete 对象["属性名"];
对象中的方法
当一个对象的属性是一个函数时,我们称这个函数是该对象的方法。
调用方式:对象.方法名();
定义在对象中的函数(方法)和生成对象的函数(构造函数),还有有点区别的:
  • 可以这样理解:构造函数是对象的模板(类),定义在构造函数中的函数为定义在对象中的函数。
03.函数概述 函数中可以封装一些逻辑代码,在需要的时候可以去调用函数来执行这些代码。
创建函数的方式
创建函数有两种方式:
  • 方式一:函数声明的形式,此种方式是常规的使用方法。
function 函数名([形参1,形参2...形参N]){ 逻辑代码... }

  • 方式二:函数表达式的形式,此种方式是声明一个匿名该函数,然后使用一个变量接收这个函数,不如第一种用的多。
var 函数名 = function([形参1,形参2...形参N]){ 语句... };

调用函数的方式
语法:函数名([实参1,实参2…实参N]);
当我们调用函数时,函数中封装的代码会按照编写的顺序执行。
函数的返回值
返回值:就是函数执行的结果,使用return 来设置函数的返回值。
语法:return 值;
可以通过一个变量来接收返回值,如果返回的值是一个字符串,举例如下:
// 定义函数 function fun1(){ return "Hello Javascript"; } // 调用函数并用变量接收它的返回值 var str=fun1();

注意:
  • return后边的代码都不会执行,一旦执行到return语句时,函数将会立刻退出。
  • return后可以跟任意类型的值,可以是基本数据类型,也可以是一个对象。
  • 如果return后不跟值,或者是不写return则函数默认返回undefined
函数的传参的注意事项
调用函数时,可以在括号中传递实参,传递的实参会赋值给对应的形参,调用函数时JS解析器不会检查实参的类型个数,可以传递任意数据类型的值。
如果实参的数量大于形参,多余实参将不会赋值。
如果实参的数量小于形参,则没有对应实参的形参将会赋值undefined。
变量的作用域
变量的作用域简单来说就是一个它们的作用范围。
在JS中作用域分成两种,全局作用域(全局变量)和函数作用域(局部变量)。
全局作用域:
  • 直接在script标签中编写的代码都运行在全局作用域中,全局作用域在打开页面时创建,在页面关闭时销毁。
  • 全局作用域中有一个全局对象window,该对象由浏览器提供,可以在页面中直接使用,它代表的是整个的浏览器的窗口。
  • 在全局作用域中创建的变量都会作为window对象的属性保存。
  • 在全局作用域中创建的函数都会作为window对象的方法保存。
  • 在全局作用域中创建的变量和函数可以在页面的任意位置访问。
  • 在函数作用域中也可以访问到全局作用域的变量。
  • 注意:尽量不要在全局中创建变量,不好维护。
函数作用域:
  • 函数作用域是函数执行时创建的作用域,每次调用函数都会创建一个新的函数作用域,函数作用域在函数执行时创建,在函数执行结束时销毁。
  • 在函数作用域中创建的变量,不能在全局中访问,当在函数作用域中使用一个变量时,它会先在自身作用域中寻找,如果找到了则直接使用,如果没有找到则到上一级作用域中寻找,如果找到了则使用,找不到则继续向上找。
变量和函数的声明提前
变量的声明提前:
  • 在全局作用域中,使用var关键字声明的变量会在所有的代码执行之前被声明,但是不会赋值。所以我们可以在变量声明前使用变量,但是不使用var关键字声明的变量不会被声明提前。
> console.log("a="+a); //输出不会报错,输出为a=undefined console.log("b="+b); //输出会报错 var a=10; b=20;

  • 在函数作用域中,使用var关键字声明的变量会在函数所有的代码执行之被声明.
函数的声明提前
  • 在全局作用域中,使用函数声明创建的函数,会在所有的代码执行之前被创建,也就是我们可以在函数声明前去调用函数,但是使用函数表达式创建的函数没有该特性。
> fun1(); //不会报错 fun2(); //报错 function fun1(){ console.log("函数声明创建函数"); } var fun2=function(){ console.log("函数表达式创建函数"); }

  • 在函数作用域中,使用函数声明创建的函数,会在所有的函数中的代码执行之就被创建好了。
04.函数和对象的对比 什么是类
类(class)这个概念来源于OOP(Object Oriented Programming),也就是面向对象编程,OOP是一种计算机编程架构,其有着封装,继承,多态三种特性。
  • 类在OOP中是实现信息封装的基础。
  • 类是一种用户定义类型,也称类类型。
  • 每个类包含数据说明和一组操作数据或传递消息的函数。
  • 类的实例称为对象
什么是构造函数
构造函数是专门用来创建对象的函数,在JavaScript中一个构造函数我们也可以称为一个类,通过一个构造函数创建的对象,我们称该对象是这个构造函数的实例,通过同一个构造函数创建的对象,我们称为一类对象
JS对象的由来
在JS中可以通过构造函数创建对象,这个构造函数可以类比于Java中类中的构造函数。
function CrtObj(name,age,sex,addr){ // 在构造函数中,使用this,来指代对象 this.name = name; this.age = age; // 定义方法 this.funNameAge = function(){ console.log(this.name,this.age) } } // 使用new关键字创建对象 const obj1 = new CrtObj('张三',18); // 调用对象/实例化对象中的方法 obj1.funNameAge();

构造函数和普通函数有什么区别
实际上并不存在创建构造函数的特殊语法,其与普通函数唯一的区别在于调用方法,对于任意函数,使用new关键字调用,那么它就是构造函数,不使用new操作符调用,那么它就是普通函数。
按照惯例,我们约定构造函数名以大写字母开头,普通函数以小写字母开头,这样有利于显性区分二者,例如,new Array(),new Object()。
使用new操作符调用构造函数时,会经历4个阶段(new关键字干了什么)
  • 1、创建一个新对象;
  • 2、将构造函数作用域赋给新对象(使this指向该新对象);
  • 3、执行构造函数代码;
  • 4、返回新对象;
05.JS中构造函数的两种语法 ES5构造函数语法
  • ES5构造函数语法:function关键字,函数形式声明
// 在构造函数中,使用大驼峰命名法与普通函数区分 function CrtObj1(name,age){ // 定义属性 this.name = name; this.age = age; this.funNameAge1 = function(){ console.log(this.name,this.age) } }// 通过构造函数来生成对象必须要和new关键词一起使用 const obj1 = new CrtObj1('张三',18); // 调用对象/实例化对象中的方法 obj1.funNameAge1();

ES6构造函数语法
ES6中,新增了class类语法形式,它的作用和原理与ES5语法是完全相同的,只是语法形式不同而已。
  • ES6构造函数语法 :class关键字,类形式声明
class CrtObj2{ constructor(name,age){ this.name = name; this.age = age; } funNameAge2(){ console.log(this.name , this.age); } }const obj2 = new CrtObj2('李四',20); obj2.funNameAge2();

对比分析
ES6语法的特点:
  • 1、声明构造函数是通过class类声明的。
  • 2、传参与定义属性是写在constructor构造器中的。
  • 3、方法不需要写上prototype单词,甚至不需要写function关键词,然而这只是ES6语法帮我们给补上了而已,本质还是一样的。
  • 4、最后一样通过new关键词生成实例化对象。
06.原型(prototype) 引出:
function CrtObj(name,age){ this.name = name; this.age = age; // 定义方法 this.funNameAge = function(){ console.log(this.name,this.age) } }const obj1 = new CrtObj('张三',18,'男','北京'); const obj2 = new CrtObj('张三',18,'男','北京'); obj1.funNameAge(); console.log( obj1.funNameAge == obj2.funNameAge ); //false

【前端相关|JS中的类,函数,对象、原型】注意:
  • 通过同一个构造函数创建的对象中,都添加了一个funNameAge方法。
  • 方法也是在构造函数内部创建的,也就是每执行一次构造方法,就会创建一个新的funNameAge方法,即所有实例的funNameAge方法都是唯一的,不相同的。
明明是同一个方法,却会分别占用内存,如何让所有对象共享同一个方法?
  • 可以通过原型对象来解决,具体操作如下:
// 在构造函数中,使用大驼峰命名法与普通函数区分 function CrtObj(name,age){ this.name = name; this.age = age; // 定义方法 this.funNameAge = function(){ console.log(this.name,this.age) } }//通过原型对象的方式添加一个方法 CrtObj.prototype.fun = function(){ console.log("通过原型对象的方式添加一个方法"); }const obj1 = new CrtObj('张三',18,'男','北京'); const obj2 = new CrtObj('张三',18,'男','北京'); console.log( obj1.funfunNameAge == obj2.funNameAge ); //false console.log( obj1.fun == obj2.fun ); // true ,这里的输出结果是true,内存地址是相同的了,这里可以类比Java中静态方法和普通方法 console.log( obj1.__proto__==CrtObj.prototype ); // true 实例化对象的原型属性指向创建它的构造函数的原型对象

解释一下原型属性(prototype)原型对象
  • 每一个函数,天生有一个prototype属性,称为原型对象,是一个专门用来存储数据、函数的空间。
  • 每一个对象,天生有一个_prto_属性,称为原型属性,我们实例化对象的原型属性指向创建它的构造函数的原型对象。
  • 因此上述代码中我将方法存储在构造函数的原型对象中,那么它生成的所有实例化对象调用方法时都会通过_proto_找到这个构造函数prototype属性中存储的方法。从而解决了上述问题。
总结:
  • 原型对象就相当于一个公共的区域,凡是通过同一个构造函数创建的对象他们通常都可以访问到相同的原型对象,我们可以将对象中共有的属性和方法统一添加到原型对象中,这样我们只需要添加一次,就可以使所有的对象都可以使用。
  • 当试图访问一个对象的属性时,首先会在该对象自身中寻找,如果有就直接使用,如果没有还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

    推荐阅读