数据类型:
简单数据类型: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
(经典问题)判断一个对象是不是数组:
- value instanceof Array
- Array.isArray(value)
- 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
hasOwnProperty
和in
:
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 in
和Object.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
以上定义了两个类型:SuperType
和SubType
。每个类型分别有一个属性和方法。SubType
继承了SuperType
,而继承是通过创建SuperType
实例,并将该实例赋给SubType.prototype
来实现的(以一个新类型的实例重写原型对象)。因此,原来存在于SuperType
的实例中的所有属性和方法,现在也存在于SubType.prototype
中了。
最终:instance
指向SubType
的原型,SubType
的原型又指向SuperType
的原型
当访问实例属性时,首先在示例中搜索该属性,如果没找到该属性,则会继续搜索实力的原型,如果还没找到,就沿着原型链继续往上找。
原型链的问题:
- 原型属性会被所有实例所共享。此方法实现继承,原型实际上变成了另一个类型的实例。SuperType的属性就变成了SubType原型上的属性了,就会被SubType的实例instance1、instance2等所继承。
- 在创建子类型实例时,不能向超类型实例传递参数。
因此实际中很少单独使用原型链。
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()
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。