当前位置: 首页 > news >正文

JS基础之数据类型、对象、原型、原型链、继承

数据类型:

简单数据类型:Undefined、Null、String、Number、Boolean、Symbol
复杂数据类型:Object 
// Undefined:声明,但未初始化
// Null:空对象指针

typeof操作符(检测基本数据类型):

typeof的返回值有哪些:
1. undefined  // 声明和未初始化的变量,使用typeof都会返回Undefined
2. boolean
3. string
4. number
5. object  // 当是object、null、array时
6. function // 函数是对象,不是一种数据类型,因为特殊,typeof把它从对象中区分出来。typeof 正则也返回function)

typeof 的用途是检测基本数据类型,检测引用类型数的值时,用instanceof

instanceof操作符(确定实例和原型之间关系):

如果变量是给定引用类型的实例instanceof操作符就会返回true
例如:

person instanceof Object  
arr instanceof Array  
pattern instanceof RegExp  

(经典问题)判断一个对象是不是数组:

  1. value instanceof Array
  2. Array.isArray(value)
  3. Object.prototype.toString.call(value) // [object Array]

创建对象:

1. 工厂模式

function createPerson(name, age) {
    var o = new Object()     // 显示地创建对象
    o.name = name
    o.age = age
    o.getName = function () {
        console.log(this.name)
    }
    return o      // 最后需要return
}

其实就是写了一个函数,每次创建对象就是调用这个函数。

优点:工厂函数解决了创建多个类似对象的问题
缺点:没有解决对象识别问题(即怎样知道一个对象的类型 constructor)

2. 构造函数模式

构造函数可以创建特定类型的函数,像Object、Array这样的原生构造函数。我们可以创建自定义的构造函数

function Person(name, age) {
    this.name = name
    this.age = age
    this.getName = function() {
        console.log(this.name)
    }
}

var person1 = new Person('zhangsan', 18)
var person2 = new Person('lisi', 20)
new 操作符做了什么:
1.创建一个新对象
2.将构造函数的作用于赋给新对象(因此this就只想新对象)
3.执行构造函数中的代码(为这个新对象添加属性)
4.返回新对象  

person1和person2分别保存着Person的一个不同实例,这两个对象都有一个constructor(构造函数)属性,该属性指向Person

person1.constructor == Person  // true
person2.constructor == Person  // true

对象的constructor属性最初是用来标识对象类别的。检测对象类型还是用instanceof更靠谱(++确定实例和原型之间关系++)

person1 instance Person //true        
person1 instance Object //true

构造函数还可以在另一个对象的作用域中调用

var o = new Object()
Person.call(o, 'xiaoming', 12)  // 在o的作用于调用Person构造函数,o就拥有了Person所有的属性和方法。
o.getName()   // 'xiaoming'

call()apply()的第一个参数是谁,就是在谁的作用于中调用构造函数。

优点:(解决了对象识别问题)创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型(Person类),这也是构造函数模式胜过工厂模式的地方
缺点:构造函数的每个方法,都要在每个实例上重新创建一遍。因此不同实例上的同名函数不相等。
person1.getName == person2.getName   // false

3. 原型模式

先理解一些概念:
我们创建的每个函数(例如构造函数)都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象(函数的原型对象),这个对象包含所有实例共享的属性和方法prototype就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。
例如:给构造函数Person的原型添加属性和方法,那实例也会共享这些属性和方法。

function Person() {}
Person.prototype.name = 'zhangsan'
Person.prototype.getName = function() {console.log(this.name)}
var person1 = new Person()
person1.getName()   // 'zhangsan'

每个函数都有一个prototype属性,指向该函数的原型对象。而原型对象又有一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针。

例如:Person.prototype.constructor 指向 Person    

当调用构造函数创建一个实例,该实例的内部将包含一个指针_proto_,指向构造函数的原型对象。这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例和构造函数之间。

Person.prototype.constructor-->Person      
实例person1._proto_-->Person.prototype
Person.prototype.isPrototypeOf(person1)  //true
Object.getPrototypeOf(person1) == Person.prototype  //true

hasOwnPropertyin:

hasOwnProperty检测属性存在于实例,还是原型上。只有属性值存在于实例时,才返回true
in操作符无法检测属性存在于实例还是原型上。只要通过对象能访问到属性值,就返回true

function Person() {}
Person.prototype.name = 'zhangsan'
var person1 = new Person()
person1.sex = '男'

person1.hasOwnProperty('name')  //false
person1.hanOwnProperty('sex')  //true
name in person1  //true
sex in person1  // true

判断属性仅存在于原型:

function(obj, name) {
    return !obj.hasOwnProperty(name) && (name in obj)
}

for inObject.keys

for in // 实例和原型上所有可枚举的属性(返回的是所有能够通过对象访问的,可枚举的属性)   
Object.keys // 仅实例上可枚举的属性 

重写原型(重新设定constructor):

Person.prototype = {
    constructor: Person,
    name: 'zhangsan',
    getName: function() {
        console.log(this.name)
    }
}
优点:共享函数,不需要每次创建实例都重新创建同名函数。
缺点:属性的共享

4. 组合使用构造函数模式和原型模式(认可度最高的模式)

创建自定义类型最常用的方式,使用最广范、认可度最高
集两种模式之长构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。这样每个实例都有自己的一份实例属性副本,但同时又共享着对方法的引用,最大限度地节省了内存。

5. 动态原型模式
6. 寄生构造函数模式
7. 稳妥构造函数模式

创建对象总结

在没有类的情况下,可以采用以下方式创建对象。

  • 工厂模式:使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。这个模式后来被构造函数模式所取代。
  • 构造函数模式:可以创建自定义引用类型,可以像创建内置对象实例一样使用new操作符。不过构造函数模式也有缺点,即他的每个成员都无法得到复用,包括函数。由于函数可以不局限于任何对象,因此没有理由不在多个对象间共享。
  • 原型模式:使用构造函数的prototype属性来指定那些应该共享的属性和方法。组合使用构造函数模式和原型模式时,使用构造函数定义实例属性,使用原型定义共享的属性和方法

继承:

主要依靠原型链来实现继承

1. 原型链

原型链的主要思想:利用原型让一个引用类型继承另一个引用类型的属性和方法

先回顾下构造函数、原型和实例的关系:
每一个构造函数都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针(constructor),而实例都包含一个指向原型对象的内部指针。
那么,假如我们让原型对象等于另一个类型的实例:

function SuperType() {
    this.type = true
}
SuperType.prototype.getSuperValue = function() {
    return this.type
}
function SubType() {
    this.subtype = false
}
// 继承了SuperType
SubType.prototype = new SuperType()   //实例赋值给原型的方式
SubType.prototype.getSubValue() {
    return this.subtype
}
var instance = new SubType()
console.log(instance.getSuperValue())  // true

以上定义了两个类型:SuperTypeSubType。每个类型分别有一个属性和方法。
SubType继承了SuperType,而继承是通过创建SuperType实例,并将该实例赋给SubType.prototype来实现的(以一个新类型的实例重写原型对象)。因此,原来存在于SuperType的实例中的所有属性和方法,现在也存在于SubType.prototype中了。
最终:instance指向SubType的原型,SubType的原型又指向SuperType的原型

当访问实例属性时,首先在示例中搜索该属性,如果没找到该属性,则会继续搜索实力的原型,如果还没找到,就沿着原型链继续往上找。

原型链的问题:

  1. 原型属性会被所有实例所共享。此方法实现继承,原型实际上变成了另一个类型的实例。SuperType的属性就变成了SubType原型上的属性了,就会被SubType的实例instance1、instance2等所继承。
  2. 在创建子类型实例时,不能向超类型实例传递参数。

因此实际中很少单独使用原型链。

function SuperType() {
    this.colors = ['red', 'blue']
}
function SubType() {}

SubType.prototype = new SuperType()  // 继承了SuperType

var instance1 = new SubType()
instance1.colors.push('black')
console.log(instance.coloes)  // 'red', 'blue', 'black'

var instance2 = new SubType()
console.log(instance2.colors)  // 'red', 'blue', 'black'

2. 借用构造函数(经典继承)

基本思想: 在子类型构造函数内部调用超类型构造函数

函数只不过是在特定环境中执行代码的对象,因此通过使用apply()和call()方法也可以在(将来)新建的对象上执行构造函数。
function SuperType() {
    this.colors = ['red', 'blue']
}
function SubType() {
    // 继承了SuperType
    SuperType.call(this)
}
var instance1 = new SubType()
instance1.push('black')
console.log(instance1.colors)  //'red', 'blue', 'black'

var instance2 = new SubType()
console.log(instance2.colors)  //'red', 'blue'

通过使用call()方法或者apply()方法,我们实际上是在(将来)新创建的SubType实例的环境下调用SuperType构造函数

传递参数:

function SuperType(name) {
    this.name = name
}
function SubType() {
    // 继承了SuperType,同时还传递了参数
    SuperType.call(this, 'zhangsan')
    this.age = 20
}
var instance = new SubType()
console.log(instance.name)   // zhangsan
console.log(instance.age)    // 20
缺点:和构造函数模式存在一样的问题,函数无法复用。

3. 组合继承(最常用的继承模式)

原型链和借用构造函数的技术结合到一块,发挥两者之长的继承模式

基本思想:使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。这样既通过在原型上定义方法实现了函数的复用,又能够保证每个函数都有自己的属性。

function SuperType(name) {
    this.name = name
    this.color = ['red', 'blue']
}
SuperType.prototype.getName = function() {
    console.log(this.name)
}

function SubType(name, age) {
    SuperType.call(this, name)  // 继承属性
    this.age = age
}
SubType.prototype.getAge = function() {
    console.log(this.age)
}

SubType.prototype = new SuperType()  // 继承方法
SubType.prototype.constructor = SubType

var instance1 = new SubType('zhangsan', 18)
instance1.colors.push('black')
console.log(instance1.colors)  // 'red', 'blue', 'black'
console.log(instance1.getName)  // 'zhangsan'
console.log(instance1.getAge)   // 18

var instance2 = new SubType('lisi', 20)
console.log(instance2.colors)   //  'red', 'blue'
console.log(instance2.getName)  // 'lisi'
console.log(instance2.getAge)  // 20
4. 原型式继承
Object.create()
5. 寄生式继承

继承总结

javascript主要是通过原型链实现继承。原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的。这样,子类型能够访问超类型的所有属性和方法。原型链的问题是对象实例共享所有继承的属性和方法,因此不适合单独使用。解决这个问题的技术是借用构造函数,即在子类型构造函数的内部调用超类型构造函数。这样就可以做到每个实例都有自己的属性,同时还能保证只是用构造函数模式来定义类型。使用最多的继承模式是组合继承,这种模式使用原型链继承共享的属性和方法,而通过借用构造函数继承实例属性。

ES6:

1.创建对象

    1. class关键字
    2. 定义属性:constructor(xxx) { this.xxx = xxx }
    3. 定义方法,方法之间不需要“;”
class Person{                   // 使用class,而不是function
    constructor(name, age=18) { // 类的传参
        this.name = name        // 定义此类的属性
        this.age = age
    }
    introduce() {                // 定义方法
        return `我叫${this.name},今年${this.age}岁`
    }
    sayName() {
        console.log(this.name)
    }
    sayAge() {
        console.log(this.age)
    }
}
const me = new Person('张三', 20)
console.log(me.introduce())

2.继承

class Coder extends Person{
    constructor(name, age, job="Html") {  // 继承父类属性,并新加属性
        super(name, age) // 必须传参;子类必须在constructor中调用super,否则报错(因为子类没有自己的this对象,而是继承父类的this对象并对其加工,如果不调用super,子类得不到this对象)
        this.job = job
    }
    showJob() { // 子类的新方法
        console.log(this.job)
    }
}
// 调用
const coder1 = new Coder('李四', 22, 'js')
coder1.sayName()
coder1.showJob()

相关文章:

  • 大数据时代用技术力量缓解春运难题
  • 第0次作业 4班卢炳武
  • 如何有效地管理测试用例
  • 【多线程】将大批量数据插入多张表,怎么知道多张表都插成功了
  • 微软发布Asp.Net Core 1.1的第一个预览版本
  • IP地址和子网划分学习笔记之《子网掩码详解》
  • 物联网发展势头强劲 智能化融合成新趋势
  • JS笔记四:作用域、变量(函数)提升
  • 物联网的致命弱点是什么?
  • Tomcat中startup.bat启动无效
  • 《编译与反编译技术实战》——1.3 语法分析生成器YACC
  • 《微信公众平台应用开发实战(第2版)》一第1章 微信公众平台介绍
  • 入职第二天:使用koa搭建node server是种怎样的体验
  • 用JS获取地址栏参数的方法
  • QA 应该更新的测试工具
  • [deviceone开发]-do_Webview的基本示例
  • [译]Python中的类属性与实例属性的区别
  • 【React系列】如何构建React应用程序
  • 5分钟即可掌握的前端高效利器:JavaScript 策略模式
  • JavaScript对象详解
  • Js基础知识(四) - js运行原理与机制
  • Ruby 2.x 源代码分析:扩展 概述
  • spring + angular 实现导出excel
  • vue+element后台管理系统,从后端获取路由表,并正常渲染
  • 汉诺塔算法
  • 回流、重绘及其优化
  • 强力优化Rancher k8s中国区的使用体验
  • 驱动程序原理
  • 如何用vue打造一个移动端音乐播放器
  • 入门到放弃node系列之Hello Word篇
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • ​二进制运算符:(与运算)、|(或运算)、~(取反运算)、^(异或运算)、位移运算符​
  • ​学习一下,什么是预包装食品?​
  • !$boo在php中什么意思,php前戏
  • #laravel 通过手动安装依赖PHPExcel#
  • #多叉树深度遍历_结合深度学习的视频编码方法--帧内预测
  • #我与Java虚拟机的故事#连载14:挑战高薪面试必看
  • (MATLAB)第五章-矩阵运算
  • (二十五)admin-boot项目之集成消息队列Rabbitmq
  • (附源码)springboot“微印象”在线打印预约系统 毕业设计 061642
  • (接口自动化)Python3操作MySQL数据库
  • (转)fock函数详解
  • (转)http协议
  • .NET DevOps 接入指南 | 1. GitLab 安装
  • .net web项目 调用webService
  • .NET 跨平台图形库 SkiaSharp 基础应用
  • .NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件
  • .NET/C# 如何获取当前进程的 CPU 和内存占用?如何获取全局 CPU 和内存占用?
  • .net和php怎么连接,php和apache之间如何连接
  • .net开源工作流引擎ccflow表单数据返回值Pop分组模式和表格模式对比
  • .Net中的集合
  • @Repository 注解
  • [ vulhub漏洞复现篇 ] Celery <4.0 Redis未授权访问+Pickle反序列化利用
  • [.NET]桃源网络硬盘 v7.4
  • [Android Pro] AndroidX重构和映射