1 JavaScript对象与实例
1.1 原型对象的属性
JavaScript是一种面向对象的语言,但它没有采用Java那种基于类( class-based
:总是先有类,再从类去实例化一个对象,类与类之间又可能会形成继承、组合等关系)的方式来实现,而是采用基于原型(prototype-based
:通过“复制原型”的方式来创建新对象,其实不是真的复制,而是建立一个对原型的引用,被复制的母体称为原型对象)的方式,所以JavaScript中的原型对象有着更复杂的属性。
原型对象提供以下八种属性:
数据属性:
1、value:就是属性的值,默认为undefined
;
2、writable:决定能否修改属性的值,默认为true
;
3、enumerable:决定能否通过for-in循环返回属性,默认为true
;
4、configurable:决定该属性能否被删除或者改变特征值,默认为true
。
备注:可以使用内置函数Object.getOwnPropertyDescripter
来查看数据属性:
var o = { a: 1 }; // {}是 new Object()的简写。
o.b = 2; // a和b皆为数据属性
Object.getOwnPropertyDescriptor(o,"a") // {value: 1, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor(o,"b") // {value: 2, writable: true, enumerable: true, configurable: true}
访问器属性(这个属性不包含数据值,包含的是一对get和set方法,在读写访问器属性时,就是通过这两个方法来进行操作处理的):
1、Get:在读取属性时调用的函数,默认为undefined
;
2、Set:在写入属性时调用的函数,默认为undefined
;
3、enumerable:决定能否通过for-in循环返回属性,默认为false
;
4、configurable:决定该属性能否被删除或者改变特征值,默认为false
。
注意:如果我们要想改变属性的特征,或者定义访问器属性,要通过Object.defineProperty()
方法,这个方法有三个参数:属性所在的对象,属性名,一个描述符对象,例子如下:
var o = {
get a() {
return 1
}
};
console.log(o.a); // o.a每次都得到1
// 访问器属性修改:
var o = {
a: 1
};
Object.defineProperty(o, "a", {
get: function() {
return this.a;
}
// 若只指定get方法,不指定set方法,那么默认该属性是只读
set: function(newValue) {
if (newValue !== this.a) {
this.a = newValue + 1;
}
}
});
console.log(o.a); // 1
o.a = 2;
console.log(o.a); // 3
//其他两个特性configurable,enumerable的修改可以参照数据属性
1.2 原型对象与实例对象的关系 ★
1.2.1 原型链
当我们去访问一个object
的属性(普通属性或者方法)时,运行的过程如下:
1、如果当前的object
中有这个属性,就返回它的值,访问结束;
2、如果当前object
中没有这个属性,就往当前object
的__ proto __
属性指向的object
(prototype object
)里找,如果找到,就返回它的值,访问结束;
3、如果还没找到,就不断重复上一步骤的动作,直到没有上级prototype
,此时返回undefined
这个值,实际运行中,是一直找到Object.prototype
这个object
的,因为它的__ proto __
等于null
;
console.log(typeof Object.prototype); // "object"
console.log(Object.prototype.__proto__ === null); // true
这个过程和Java中“在类继承树上一直往上找,直到Object类中都找不到为止”很相似。
1.2.2 __ proto __、constructor和prototype属性
1、__ proto __
所指向的对象,真正将它的属性分享给它所属的对象(即它的原型对象),所有的对象都有__ proto __
属性,它是一个内置属性,被用于继承。
2、constructor
这个属性的含义就是将当前对象指向其关联的构造函数,所有函数最终的构造函数都指向Function
(最终Boss
)。
3、prototype
是一个只属于function函数
的属性,也并不是构造函数专有,它的含义是函数的原型对象。当使用new
方法调用该构造函数的时候,它被用于构建新对象的__ proto __
。另外它不可写,不可枚举,不可配置。
4、①__ proto __
和constructor
属性是对象所独有的;②prototype
属性是函数所独有的,但因为函数也是一种对象,所以函数也拥有__ proto __
和constructor
属性;③这三个属性值都是对对象或者函数的引用,而不是一个包含名称的字符串。
例子如下:
function Person() {
}
var person1 = new Person();
//__proto__和prototype属性:
console.log(person1.__proto__ === person.prototype); // true
//constructor属性
console.log(person1.constructor === Person); // true
console.log(Person.prototype.constructor === Person); // true
console.log(Person.constructor === Person); // false
console.log(Person.constructor === Function); // true
为什么上面的person1
和Person.prototype
的constructor
属性都指向Person函数
?
因为Person.prototype
就是原型对象,也就是实例对象person1
的原型,原型对象是实例对象的母体,它们的构造函数都是一样的。
如何去判断一个属性是存在于实例对象中,还是原型对象中?
1、Object.hasOwnProperty()
方法(属性只有存在于当前对象中才会返回true
):
function Person() {
}
var person1 = new Person();
person1.name = 'ringoD';
console.log(person1.hasOwnProperty('name')); // true
2、in操作符
(in则会遍历原型链上的所有属性,不管是实例对象上的,还是原型对象上的):
function Person() {
}
var person1 = new Person();
person1.name = 'ringoD';
console.log('name' in person1); // true
console.log('age' in person1); // true
3、Object.keys()方法
(此方法可以获取指定对象可枚举的属性的名字)
var keys = Object.keys(Person.prototype)
console.log(keys) // ['age']
4、Object.getOwnPropertyNames()
方法(此方法可以获取指定对象所有属性的名字,包括不可枚举的)
1.2.3 call和construct
1、call()
方法:foo.call(this, arg1,arg2,arg3)
,这里的this
是指所填入实例的上下文环境,代码在该this
内生效;
function Dog(name) {
this.name = name;
}
Dog.prototype.say = function(message) {
console.log(this.name + ' say ' + message);
}
let dog = new Dog('小黄');
dog.say('汪汪'); //小黄 say 汪汪
function Cat(name) {
this.name = name;
}
let cat = new Cat('小喵');
dog.say.call(cat, '喵喵'); //小喵 say 喵喵
//此时dog.say的this的指向已经通过call()方法改变了,指向的是Cat,this.name就是小喵,传进的参数是喵喵
2、延伸:函数对象的定义是:具有[[Call]]
私有属性的对象,而构造器对象的定义是:具有[[Construct]]
私有属性的对象;
3、延伸2:任何对象只需要实现[[call]]
,它就是一个函数对象,可以去作为函数被调用。而如果它能实现[[construct]]
,它就是一个构造器对象,可以作为构造器被调用。
1.2.4 new 操作符
1.2.4.1 ES5
function Person() {
this.name = name
}
// 给原型对象加个describe函数
Person.prototype.describe = function() {
console.log('Hello, my name is ' + this.name + '.');
}
var person1 = new Person('ringoD');
person1.describe() // Hello, my name is ringoD.
1.2.4.1 ES6
class Person {
constructor(name) {
this.name = name;
}
describe() {
console.log('Hello, my name is ' + this.name + '.');
}
}
var person1 = new Person('ringoD');
person1.describe() // Hello, my name is ringoD .
继承:
class Person {
constructor(name) {
this.name = name;
}
describe() {
console.log('Hello, my name is ' + this.name + '.');
}
}
class Man extends Person {
constructor(name, age) {
super(name);
this.age = age;
// 在这里,super()作用是,代表调用了父类的构造方法,函数返回了子类的实例;
// 因为子类的构造方法是根据父类构建的,因此this关键字必须在调用super()函数之后使用,否则会报错。
// 并且,ES6 要求子类构造函数constructor() 内必须调用super()方法。
}
describe() {
console.log('Hello,I am a man,my name is ' + this.name + ',I am' + this.age + 'years old.');
}
}
var person1 = new Man('ringoD', 18);
person1.describe(); // Hello ,I am a man, my name is ringoD,I am 18years old.
静态方法:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
static say() {
console.log('say everyone');
}
}
People.say(); // 'hello everyone'
var people1 = new Person;
people.say(); // TypeError: people.say is not a function
虽然class
和extends
实质上是作为语法糖,用来统一了程序员对基于类的面向对象的模拟,但是确确实实让JS在对象写法上更加清晰,new
运算符和class
搭配使用,让function
回归原本的函数语义,值得一提的是,在ES6之后用 =>
语法创建的函数仅仅是函数,它们无法被当作构造器使用。
2.1 JavaScript中的对象分类
在1.x章节中出现的所有对象都是内置对象中的普通对象(由{}语法、Object构造器或者class关键字定义类创建的对象,它能够使用原型继承方法)。
所有对象分类:
1、宿主对象(host Objects):由JavaScript宿主环境提供的对象,它们的行为完全由宿主环境决定。
2、内置对象(Built-in Objects):由JavaScript语言提供的对象。
2、1固有对象(Intrinsic Objects ):由标准规定,随着JavaScript运行时创建而自动创建的对象实例。
2、2原生对象(Native Objects):可以由用户通过Array、RegExp等内置构造器或者特殊语法创建的对象。
2、3普通对象(Ordinary Objects)。
宿主对象详解:
所有非内置对象都是宿主对象(host object),即由 ECMAScript 实现的宿主环境提供的对象,所有 BOM 和 DOM 对象都是宿主对象。我们最为熟悉的,就是在浏览器环境中的全局对象window
,window
上又有很多属性,如document
,宿主对象也分为固有的和用户可创建的两种,比如document.createElement
就可以创建一些 DOM 对象。
固有对象详解:
固有对象在任何JS代码执行前就已经被创建出来了,它们通常扮演者类似基础库的角色。Object和Function其实就是固有对象的一种。
原生对象详解:
JavaScript的标准中提供了30多个原生构造器,我们把通过这些构造器构造出的对象称作原生对象,它们也无法用class
/extend
语法来继承,所有这些原生对象都是为了特定能力或者性能,而设计出来的“特权对象。
特殊行为的对象:
除了上面介绍的对象之外,在固有对象和原生对象中,有一些对象的行为跟正常对象有很大区别,
它们常见的下标运算(就是使用中括号或者点来做属性访问)或者设置原型跟普通对象不同:
1、Array
:Array
的length
属性根据最大的下标自动发生变化;
2、Object.prototype
:作为所有正常对象的默认原型,不能再给它设置原型了;
3、String
:为了支持下标运算,String
的正整数属性访问会去字符串里查找;
4、Arguments
:arguments的非负整数型下标属性跟对应的变量联动;
5、模块的namespace
对象:特殊的地方非常多,跟一般对象完全不一样,尽量只用于import吧;
6、类型数组和数组缓冲区:跟内存块相关联,下标运算比较特殊;
7、bind()
后的function
:跟原来的函数相关联。