其他OOP语言都是先有类 再实例化对象,而js里是直接有对象。
一、js对象
1、js对象
js对象是一种复合数据类型,它可以把多个(不同类型的)数据集中在一个变量中,并且给每个数据起名字。
需要注意的是,对象的属性名 其实最终都被转换为字符串:
var o = {
a:1,
'b':2,
45:4545,
['aa'+'bb']:4545
};
for(key in o){
console.log(key + ':' + typeof key);
}
输出:
也可以从in操作符中看出 属性最终都是字符串:
var o = { a:1, 'b':2,};
console.log('a' in o); //true
console.log(a in o); //报错
2、对象与数组
对象的每个数据有对应的名字(属性名),我们通过叫名字访问具体哪个数据;
数组的每个数据没有名字,我们只能通过编号来访问具体哪个数据。
从本质讲,数组就是对象的特殊形式,数组的每个索引实质就是特殊化的 对象属性名。举个例子:
var a = [0,1,2,3];
a['me'] = 1;
a[-1] = '负数'; //负数 转换为 字符串
a[1.23] = '小数'; //小数 转换为 字符串
a['2.00'] = 'dad';//字符串 无法转换为 整数,所以仍为字符串
a['4'] = 'dad'; //字符串 转换为 整数
console.log(a); //[0, 1, 2, 3, "dad", me: 1, -1: "负数", 1.23: "小数", 2.00: "dad"]
//因为 a['4'] = 'dad';,所以length值加1
console.log(a.length); //5
//上面例子说明数组的length属性,仅计算有 整数索引 的值的个数
3、创建对象
- 字面量{}
- new Object()
- Object.create()
- Object.assign()
- 类
这里我们就说前两种:
(1)var o = new Object();
(2)var o = {};
(3)var o = new Object({x:0,y:0,radius:2});
(4)var ciclr = {x:0,y:0,radius:2};
对象 通过 构造器 生成:
- object(对象):下图左边的红圈(o、a、d···)
- constructor(构造器):下图的右边的红圈(Object、Array···)
注:Object、Array等这些都是系统自带的构造器,我们也可以自定义构造器(eg:下面深入使用的 构造函数)
并不是所有函数都当构造器使用
eg: var o = new Math.min();
4、读/写 属性
var book = new Object();
book.title = "math";
book.author = "bty";
book.chapter1 = {
title:"math 简介",
};
5、删除属性
(1)delete book.chapter1; //彻底删除
(2)book.chapter1 = null; //仅仅置空
delete删除成功或删除的属性不存在返回true.
- 数组:delete后 不是彻底删除,length不变
- 对象:delete后 是 彻底删除,没有length属性
//数组
var a = [0,1,1,2];
console.log(a.length);//4
delete a[1];
console.log(a.length);//4
console.log(a); //[0, empty, 1, 2]
console.log(a[1]); //undefined
//对象
var b = {aaa:1,bbb:2};
console.log(b); //{aaa: 1, bbb: 2}
delete b['aaa'];
console.log(b); //{bbb: 2}
console.log(b.length); //undefined //说明对象没有length属性
6、枚举对象
//key是obj里的属性名
for(var key in obj){
console.log(obj[key]);
//console.log(obj.key);则会输出undefined
}
如果是数组的话,还能用some(),every(),map(),forEach(),filter()等方法遍历。
具体用哪个应场景而定。
7、空对象
空对象 {} 并不是真正意义上的空,因为它包含着指向 Object.prototype 的链接。为了创建一个真正的空对象,我们可以使用 Object.create(null) 。它会创建一个没有任何属性的对象。这通常用来创建一个Map。
8、冻结对象
Object.freeze() 冻结一个对象。属性不能被添加、删除、更改。对象会变成不可变的。
"use strict";
let book = Object.freeze({
title : "Functional-Light JavaScript",
author : "Kyle Simpson"
});
book.title = "Other title";//Cannot assign to read only property 'title'
Object.freeze() 实行浅冻结。要深冻结,需要递归冻结对象的每一个属性。
9、类
class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
say(){
return (this.name + ' and ' + this.age);
}
}
var a = new Person('bty','12'); //类源码里其实是个构造函数,所以实例化也要用new
console.log(a.say());//bty and 12
console.log(typeof Person);//function
二、相关知识
1、this
详情请见这篇文章:this 使用的八大情况
看完以后就别再问我this的问题了。
3、原型与原型链
function Teacher(){
this.courses = [];//相当于私有变量
}
Teacher.prototype = { //相当于public
constructor:Teacher,
job:'teacher',
setName = function(name){
this.name = name;
},
addCourse = function(course){
this.courses.push(course);
}
}
var bill = new Teacher();
bill.setName('Bill');
bill.addCourse('math');
var tom = new Teacher();
tom.setName('Tom');
tom.addCourse('physics');
上面代码原型链如下:
bill和tom都有一个隐式指针_ proto_,都指向Teacher.proptotype。
又因为Teacher是一个函数对象,而函数对象又是通过new Function()来建立的,Function也有一个protoptye。Function.prototype属性在js中是内置属性。
Teacher的_ proto_是个对象,同理推之如图。
bill和tom是以Teacher.proptotype为原型,而Teacher.proptotype又以Object为原型。(这种原型继承的方式就叫原型链)
原型链的操作
- (1)属性查找
- (2)属性修改
- (3)属性删除
我们继续用上面的例子
(1)属性查找:
- 访问tom.toString()
首先js先从tom中找toString方法,发现没有;然后再沿着原型链往上找,到Teacher.prototype中查找toString,发现也没有,然后再到object.prototype中查找,发现有,然后调用toString方法
(2)属性修改:
- tom.name = ‘bty’;
首先js先在tom对象上查找有没有name属性,发现有,则直接将原来的’Tom’值改为’bty’。 - tom.job = ‘assistant’;
首先js在tom对象上查找有没有job属性,发现没有,则直接在tom对象中添加job属性并赋值为’assistant’
若想修改原型上的属性,则通过tom.prototype.job = 'assistant'修改
,但这样会让所有Teacher创建出的对象job值都做修改。
(3)属性删除
- delete tom.job;
假设tom.job = ‘assistant’已经执行。delete tom.job后会直接删除tom上的job属性,但不会删除原型上的job属性,此时再访问tom.job,值为teacher - delete tom.job; delete tom.job;
道理结果同上
判断对象是否有该属性:
tom.hasOwnProperty(‘job’);
请参考
原型的继承
原型的继承有两种方式:
- 1、用构造器(具体参见 三、深入使用)
- 2、用Object.create(); (低版本的ie浏览器不兼容)
下面就详细说说Object.create(prototype, descriptors)
详细参见此处
var teacher = {
job:'teacher',
courses:[],
setName = function(name){
this.name = name;
},
addCourse = function(course){
this.courses.push(course);
}
};
var bill = Object.create(teacher);
bill.setName('Bill');
bill.addCourse('math');
var bill = Object.create(teacher)即创建一个以teacher对象为原型的bill对象。
Object.create做的就是将bill的隐式指针_ proto_指向teacher,而不是指向teacher.prototype,这是一种新的原型继承的方式。如下图:
三、深入使用
1、构造函数
创建构造器的三种形式:
- function Person(){}
- var Person = function(){}
- var Person = new Function();
例:
//为了和普通函数区分,构造函数首字母要大写
function Rect(w,h){
this.width = w;this.height = h;
this.area = function(){return this.width * this.height};
}
var r = new Rect(5,10);
alert(r.area()); //50
//过程:new一个对象出来,然后将对象交给了Rect这个函数;
//new出来的这个对象在Rect中就叫this
//换句话说,this指向的是新创建的对象
至于为什么不用下面代码,
function Rect(w,h){
this.area = function(){return w * h};
}
var r = new Rect(5,10);
alert(r.area()); //50
是为了方便后续像r.width = 10;这样修改数值
注意:
function Person(name,age){
this.name = name;
this.age = age;
return {};
///结尾加了个return {},此时new对象时就会返回空对象,所以下面打印undefined
}
var my = new Person('bty','12');
console.log(my.age); //undefined
2、原型对象
(1)简单使用
function Person(){
Person.prototype.name = "Nicholas";
Person.prototype.age = 20;
Person.prototype.sayName = function(){
alert(this.name);
}
}
var person1 = new Person();
var person2 = new Person();
person1.sayName(); //Nicholas
person2.sayName(); //Nicholas
alert(person1.sayName === person2.sayName);//true
person1.name = "Greg";
person1.sayName(); //sam//from instance
person2.sayName(); //bty//from prototype
(2)原型的问题
如果我们对原型属性修改的是简单变量类型(eg:数值或字符串),ok,没问题。但如果修改是对象(eg:数组)就会出现问题。
function Person(){}
Person.prototype = {
//这些相当于 public 变量
constructor:Person,
name:"bty",
age:20,
friends:["amy","sam"]
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("ssh");
alert(person1.friends);//amy,sam,ssh
alert(person2.friends);//amy,sam,ssh
我们发现对person1.friends进行操作,person2.friends也跟着改变。说明他们是共享friends的(public变量),现在我们怎么将其变为 私有变量 呢?继续往下看。
3、组合原生和构造方法
function Person(name,age){
//这些是每个对象拥有的属性,相当于 private 变量
this.name = name;
this.age = age;
this.friends = ['amy','sam'];
}
Person.prototype = {
//这些相当于 public 变量
constructor:Person,
sayName:function(){
alert(this.name);
}
};
var person1 = new Person("bty",20);
var person2 = new Person("ssh",12);
person1.friends.push('zsn');
alert(person1.friends); //amy,sam,zsn
alert(person2.friends); //amy,sam
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true
四、全局对象(浏览器)
1、浏览器的全局对象是window
2、浏览器中所有全局变量 实际就是 全局对象window的成员(属性)
var value = 12;
alert(window.value);//12
3、window.document表示浏览器窗口中的HTML页面
4、document.write()将内容写入html页面
for(x in window.document){
document.write(x)
}
//for(x in document){```}也行
五、面向对象编程
- 全局变量
- 封装
- 继承
1、全局变量
定义方法:
(1)在全局环境中 var a = ‘hhh’;
(2)window.a = ‘hhh’;
(3)(function(){ a = ‘hhh’})() //变量声明提升
注意:
//这里test为全局变量。a为局部变量
function todo(){
var a = test = 'hhh';
}
//这里test2为全局变量,a2、b2为局部变量(注意看标点符号)
function todo2(){
var a2 = 'hhh',
b2 = 'hhh';
test2 = 'hhh2';
}
2、封装
通过prototype封装public变量;通过直接在Aaa上声明,封装私有变量,然后我们通过添加 this.XXX 方法,来访问私有变量。
function Aaa(){
//相当于私有属性
var _config = ['A','B','C'];
this.getConfig = function(){
return _config;
}
}
//相当于公有属性
Aaa.prototype = {
_step1:function(){},
_step2:function(){},
api:function(){}
};
var my = new Aaa();
//外部无法访问config,必须通过this.XXX 方法以类似API的形式才能访问config
console.log(my.config);//undefined
console.log(my.getConfig());//["A", "B", "C"]
3、继承
- 原型继承(JS固有的继承)
-原型的继承又两种方式:Object.create()和构造器 - 类继承(模拟C、JAVA的继承)
(1) 类继承(模拟C、JAVA的继承)
我们现在模拟:B继承A
//(function(){})()是个闭包
(function(){
function ClassA(){
var classMethod = function(){};
}
ClassA.prototype = {
api:function(){}
};
function ClassB(){
ClassA.apply(this,arguments);
}
ClassB.prototype = new ClassA();
ClassB.prototype = {
constructor:ClassB,//因为之前constructor是ClassA,所以要改成B
api:function(){
ClassA.prototype.api.apply(this,arguments);
}
};
var b = new ClassB();
b.api();
})()
(2)原型继承(JS固有的继承)
//
(function(){
var myproto = {
action1:function(){},
action2:function(){},
};
//obj的隐式指针_ proto_直接指向了myproto
var obj = Object.create(myproto);
})();
由于IE低版本浏览器不支持Object.create(),所以我们可以写个函数模拟下,如下:
var clone = (function(){
var F = function(){};
return function(proto){
F.prototype = proto;
return new F();
}
})();
解释:我们创建函数F,再为其指定原型,而F的原型就是传入的proto对象的原型。最后在new F(),即最后创建了个新的对象,而这个对象是以F.proto为原型,即以传入对象proto为原型。