概念
对象是JavaScript的基本数据类型
对象是一种复合值:它将很多值(原始值或者其他对象)聚合在一起,可通过名字访问这些值。对象也可看做是属性的无序集合,每个属性都是一个名/值对。属性名是字符串,因此我们可以把对象看成是从字符串到值得映射。
JavaScript对象还可以从一个称为原型的对象继承属性。对象的方法通常是继承的属性。这种“原型式继承”(prototypal inheritance)是JavaScript的核心特征。
除了字符串、数字、true、false、null和undifined之外,JavaScript中的值都是对象。
对象的定义:
面向对象:在程序中都是用一个对象来描述现实中一个具体的东西
3大特点:封装 继承 多态
什么是对象:封装多个数据和方法的存储空间
创建对象
创建对象最简单的方式就是在JavaScript代码中使用对象直接量。对象直接量是由若干名/值对组成的映射表,
属性名可以是JavaScript标识符,也可以是字符串直接量(包括空字符串)。属性值可以是任意类型的JavaScript表达式,表达式的值(可以是原始值也可以是对象值)就是这个属性的值。
1.对象直接量
//空对象var a = {}//带属性名和值的对象var b = {x: 0, y: 1}var book={\"main title\":\"JavaScript\",//属性名字中有空格,必须用字符串表示\'sub-title\':\"The Definitive Guide\",//属性名字里面有连字符,必须用字符串表示\"for\":\"all audiences\",//for是保留字,必须用引号author:{ //这个属性的值是一个对象firstname:\"David\", //这里的属性名都没有引号surname:\"Flanagan\"}}
2.new Object();
var a = new Object()a.x = 0a.y = 1console.log(a)
3.利用构造函数反复创建相同结构的对象(构造函数:描述一类对象结构的特殊函数)
function student(name, age){this.name = namethis.age = age}var s1 = new student(\'xa\',11)console.log(s1)
this
访问当前对象自己(使用this)
this关键字:指代调用本身(当前对象)
this本质是window下唯一一个指针,指向当前正在调用方法的对象
在方法内访问当前对象自己的属性,必须使用this.属性名
注意:
this和定义在哪无关!仅和调用时使用的当前对象有关
如果无主的调用或赋值,默认this都是window
var a = 100;(function(){var a = 99console.log(this.a)})()//100//如果无主的调用或赋值,默认this都是window//上述this.a等于window.a
let a = 2function fun(){console.log(this.a)}//这之前fun的this指向windowlet b = {a: 3, fun: fun}//在这b的声明 b里面的属性fun this指向bb.fun()//3
原型对象
js中一切继承都是用原型对象实现的
原型对象:
每个函数对象都有一个原型对象
1.构造函数
封装的函数,如果通过new操作符来调用的,就是构造函数,如果没有通过new操作符来调用的,就是普通函数
function student(name, age){this.name = namethis.age = age}var s1 = new student(\'xa\',11)console.log(s1)
2.函数student(对象)有个属性prototype(指针)指向原型对象–
Person.prototype(原型对象,实质也是对象)
他有个属性constructor(指针) ,又指向 Person函数对象
function studnet(name, age){this.name = namethis.age = age}console.log(studnet.prototype)
可以看到,通过student.prototype属性访问到原型对象,
function studnet(name, age){this.name = namethis.age = age}console.log(studnet.prototype.constructor)
原型对象里又有一个constructor属性 可以访问到student对象
function student(name, age){this.name = namethis.age = age}student.prototype.sex = \'man\'let a = new student(\'xa\',12)let b = new student(\'xa2\',131)console.log(a)console.log(b)
上面可以看出通过student.prototype添加的属性 该对象的所有实例都能访问到,通过__proto__属性
那我如果通过实例改变原型的值呢
function student(name, age){this.name = namethis.age = age}student.prototype.sex = \'man\'let a = new student(\'xa\',12)let b = new student(\'xa2\',131)b.__proto__.sex = \'woman\'console.log(a)console.log(b)
可以看到实例改变原型里的值,其他实例都收影响
我们只能通过__proto__访问吗
function student(name, age){this.name = namethis.age = age}student.prototype.sex = \'man\'let a = new student(\'xa\',12)let b = new student(\'xa2\',131)console.log(a.sex)
其实直接通过属性查找,该实例会先搜索本身是否又该属性,如果没有则取原型对象中查找。
但是如果通过这种方式改变值呢
function student(name, age){this.name = namethis.age = age}student.prototype.sex = \'man\'let a = new student(\'xa\',12)let b = new student(\'xa2\',131)a.sex = \'woman\'console.log(a)console.log(b)
这样添加的属性,会在实例中声明该属性,而不会改变原型对象的值,
function student(name, age){this.name = namethis.age = age}Object.prototype.name = \'xa\'let a = new student(\'xa\',12)console.log(a.__proto__.__proto__)
可以看到实例点两个proto,取到了Object对象的prototype
任何对象沿着它开始遍历都可以追溯到 Object.prototype
那么student.prototype.__proto__是不是也能取到呢,没错也能取到。
原型对象总结:
声明一个对象,同时会产生该对象的一个原型对象,该对象的所有实例都能访问到此原型对象,通过prototype属性,原型对象又有个constructor属性指回该对象,
关于对象的实例,如果实例不存在的属性,会去原型对象中查找,也可以直接通过__proto__访问原型对象的值,如果通过__proto__改变原型对象的值,其他实例也会收到影响
所有对象最终都能追溯到 Object.prototype,Function.prototype 是所有函数的原型
继承(原型链)
继承的主要思路是利用原型链,而原型链的原理是:让一个引用类型继承另一个引用类型的属性和方法。即原型对象通过constructor指向构造函数,实例对象通过_ptoto_指向原型对象。
看不懂?
小问题
先来个简单的例子
//先声明一个A对象构造函数function A(){}//给A的原型对象给个方法sayA A的所有实例都能访问到A.prototype.sayA = function(){console.log(\'form A\')}//再声明一个对象B构造函数function B(){}//将B的原型对象指向AB.prototype = new A()console.log(B.prototype)
可以看到B的原型对象指向A的一个实例,那么怎么访问A的原型呢,没错,使用__proto__属性
//先声明一个A对象构造函数function A(){}//给A的原型对象给个方法sayA A的所有实例都能访问到A.prototype.sayA = function(){console.log(\'form A\')}//再声明一个对象B构造函数function B(){}//将B的原型对象指向AB.prototype = new A()console.log(B.prototype.__proto__)
B对象通过B.prototype.proto,访问到A的原型对象了,那我们是不是就可以使用A的原型的属性和方法了,这样就实现继承了,是不是很简单。
那B的实例怎么访问呢
//先声明一个A对象构造函数function A(){}//给A的原型对象给个方法sayA A的所有实例都能访问到A.prototype.sayA = function(){console.log(\'form A\')}//再声明一个对象B构造函数function B(){}//将B的原型对象指向AB.prototype = new A()b1 = new B()console.log(b1.__proto__.__proto__)
b的实例,首先先通过第一个__proto__访问到B的原型,而B的原型又指向A的实例,相当于第一个__proto__是访问到A的实例,而后第二个__proto__就相当于A的实例访问A的原型对象,这样我们就实现了,B的实例访问A的原型,就实现了继承了
再来看
//先声明一个A对象构造函数function A(){}//给A的原型对象给个方法sayA A的所有实例都能访问到A.prototype.sayA = function(){console.log(\'form A\')}//再声明一个对象B构造函数function B(){}//将B的原型对象指向AB.prototype = new A()B.prototype.sayB = function(){console.log(\'from B\')}var a1 = new A()var b1 = new B()b1.sayA()b1.sayB()
没有使用__proto__为什么也能呢:
b1沿着[Prototype]属性可以访问B prototype,B prototype继续沿着[Prototype]属性访问A prototype,最终在A.prototype上找到了sayA()方法,然后执行 ,整个查找过程经历的属性组成起来,称为原型链
关于对象的注意点
//先声明一个A对象构造函数function A(){var name;this.getName = function() {return name};this.setName = function(value) {name = value};}var a1 = new A()a1.setName(\'xa\')console.log(a1.name)console.log(a1.getName())
怎么通过点属性没有出来呢,因为这是错误的,在A的构造函数中,注意name不是this.name,那么这时候的name就不是A的属性了,而是A的私有变量,除了对象本身,也就是对象的作用域内,其他任何都访问不到,实例也是。但是我们可以通过闭包访问此私有变量。不懂闭包的可以取看我的另一篇博客。
ES6中的对象
class Person{constructor(name,age){this.age = agethis.name = name}toString(){return (this.name+\"的年龄\"+this.age)}}console.log(Person.prototype.constructor)let p1 = new Person(\'xa\',12)console.log(p1)console.log(p1.toString())
constructor方法是类的构造函数是默认方法,通过new命令生成对象实例时,自动调用该方法
一个类必须有constructor方法,如果没有显式定义,一个默认的constructor方法会被添加。所以即使你没有添加构造函数,也是有默认的构造函数的
一般constructor方法默认返回实例对象this。
类的方法内部如果含有this,它默认指向类的实例
class Person{constructor(name,age){this.age = agethis.name = name}toString(){return (this.name+\"的年龄\"+this.age)}}// console.log(Person.prototype)let p1 = new Person(\'xa\',12)console.log(p1.constructor)
无论是之前还是ES6后,实例的constructor指向该类
注意点:
1.静态方法
const Expression = class Expre{static getAge(){return \'12\';}getClassName(){return \" ClassName1= \" +Expre.name + \" ClassName2= \" +Expression.name;}};let exp = new Expression();console.log(exp.getClassName());//ClassName1= Expre ClassName2= Expreconsole.log(Expression.getAge());//12console.log(Expre.getAge())//error Expre is not defined
跟java一样,也有static静态方法,
但是类的名字是Expression而不是Expre,Expre只在Class的内部代码可用,指代当前类。
所以只能通过类名点静态方法
在类内部也只能通过Expre.name,点出当前类,不能通过this
静态方法不能通过实例点出来哦
2.立即执行类
let person = new class{// 构造constructor(props) {this.props = props;}getProps(){return this.props;}}(\'构造函数的参数\');console.log(person.getProps());// 构造函数的参数