创建对象四种方式:
1.调用系统的构造函数创建对象
var 变量名= new Object(); //Object 是系统的构造函数
//实例化对象
var obj = new Object();
//添加属性
obj.name = "调用系统构造函数创建的对象的属性";
//添加方法
obj.myFunction = function () {
console.log("调用系统构造函数创建的对象的方法");
};
console.log(obj.name);//获取属性
//方法的调用
obj.myFunction();
2.工厂模式创建对象
//工厂模式创建对象
function createObject(name) {
var obj = new Object();//创建对象
//添加属性
obj.name = name;
//添加方法
obj.myFunction = function () {
console.log("工厂模式创建对象,并调用方法,属性为:" + this.name);
};
return obj;//必须返回创建对象
}
//创建一个对象
var per1 = createObject("工厂模式创建对象");
per1.myFunction();
3.自定义构造函数创建对象
function Person(name,age) {
this.name=name;
this.age=age;
this.sayHi=function () {
console.log("我叫:"+this.name+",年龄是:"+this.age);
};
}
//利用自定义构造函数创建实例对象
var obj=new Person("小明",10);
console.log(obj.name);
console.log(obj.age);
obj.sayHi();
4.字面量的方式创建对象
var obj={
name:"小明",
eat:function () {
console.log("吃了");
}
};
console.log(obj.name);
obj.sayHi();
总结:
到目前为止,如果要创建对象,最好的方式是用构造函数,他的封装性、代码的可重用性最高。
解决存储空间浪费的问题,可通过原型来解决(数据共享,节省内存空间)
调用对象的方法和获取属性的方式:
对象名.属性名;
对象名.方法名();
对象名[属性名];
对象名[方法名]();
例子:
var obj={
name:"小明",
eat:function () {
console.log("吃了");
}
};
console.log(obj.name);
obj.sayHi();
console.log(obj[name]);
obj[sayHi]();
基本类型和对象类型:
基本类型(简单类型),值类型: number,string,boolean,undefined,null
复杂类型(引用类型):object,自定义对象
基本类型数据存储在栈中,值类型之间传递,传递的是值
引用类型在栈中存储地址,指向堆内存的实际数据,引用类型之间传递,传递的是地址(引用)
var num=10;//值类型,值在栈上
var obj={};//复杂类型,对象在堆,地址在栈
上述四种创建对象的优缺点:
系统构造函数创建对象(字面量方式创建对象)的缺点:创建对象和为对象添加属性方法时,代码不能很好的封装,且如果要创建多个拥有相同属性和方法的对象,重复代码太多
自定义构造函数创建对象(工厂方法创建对象):代码有了较好的封装,创建多个对象时,也只需多new(调用工厂方法)几次而已,但和上面2种方法一样,有存储空间上的浪费,比如这个构造函数中有1个方法,那么这1个方法在实例化对象的时候,都会被分别存储到每个对象的堆内存中。
在java中,对象变量在栈中保存引用,指向堆中的空间;而方法保存在方法区中,供所有对象进行调用,即对象能共享类的方法。JS中可通过原型来添加方法,解决数据共享的问题
原型:
function Person(name,age) {
this.name=name;
this.age=age;
}
//通过原型来添加方法,解决数据共享,节省内存空间
Person.prototype.eat=function () {
console.log("吃凉菜");
};
var p1=new Person("小明",20);
console.dir(p1);//控制台打印对象
console.dir(Person);
console.log(p1.__proto__==Person.prototype);//true
实例对象p1中有__proto__这个属性,他就是一个原型对象,但这个属性是给浏览器使用,不是标准的属性
构造函数中有prototype这个属性,他也是一个原型对象,这个属性是给程序员使用,是标准的属性。
即实例对象中有一个属性叫__proto__,他是一个原型对象;构造函数中有一个属性叫prototype,他也是一个原型对象;构造函数同时也是对象(函数对象),因此构造函数也有一个叫__proto__的原型对象属性。
实例对象、构造函数、原型对象三者的关系:
* 构造函数中有一个属性叫prototype,指向的是构造函数的原型对象
* 原型对象中有一个constructor构造器,这个构造器指向的就是自己所在的原型对象所在的构造函数(即原型对象和构造函数是相互引用的)
* 实例对象中有一个叫__proto__的属性,指向的是该构造函数执行的原型对象,即实例对象的原型和构造函数的原型指向的是同一个原型对象。
* 构造函数的prototype属性指向的原型对象中的方法是可以被实例对象直接访问的
什么样的数据是需要写在构造方法中?什么样的数据是需要写在原型中?
不需要共享的数据写在构造函数中,需要共享的数据写在原型中。原型中的方法,可以相互调用,也可以通过this调用构造函数中的属性或方法
原型示例:
//构造函数
function Student(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
//为类型指定原型属性
Student.prototype = {
//手动修改构造器的指向 如果没有这个,则构造方法和原型对象就不是相互引用的关系了
constructor:Student,
height: "188",
weight: "55kg",
study: function () {
console.log("学习好开心啊");
}
};
//为原型添加方法 一般建议将所有原型方法都写在上面的原型属性对象中
Student.prototype.eat = function () {
console.log("我要吃好吃的");
};
var stu=new Student("test1",20,"男");
stu.eat();
stu.study();
console.dir(Student);
console.dir(stu);
实例对象查找名字时,优先在实例对象中查找属性或者方法,找到了则直接使用,找不到则,去实例对象的__proto__指向的原型对象prototype中找,找到了则使用,找不到则报错
利用实例对象,我们可以为JS内置对象添加原型方法
String.prototype.sayHi=function () {
console.log(this+" hello world!");
};
//字符串就有了打招呼的方法
var str2="dcz";
str2.sayHi();//dcz hello world!
如何将局部变量转换成全局变量:
把局部变量给window就可以了,例子:
var myFunction=function(){
var test="hello";
window.myTest=test;
}
myFunction();//必须要调用方法,否则输出的是undefined
console.log(myTest);
原型链
原型链是一种关系,实例对象和原型对象之间的关系,关系是通过原型(__proto__)来联系的。
如果存在继承关系,那么子类实例对象的__proto__会指向父类的原型对象,而父类的原型对象的__proto__会指向性爷爷类的原型对象,一直到Object对象的原型。这在继承中会再次说到。
原型的指向是否可以改变?
原型指向可以改变。实例对象的原型__proto__指向的是该对象所在的构造函数的原型对象,构造函数的原型对象(prototype)指向如果改变了,实例对象的原型(__proto__)指向也会发生改变。
//人的构造函数
function Person(age) {
this.age=10;
}
//人的原型对象方法
Person.prototype.eat=function () {
console.log("人在吃东西");
};
//学生的构造函数
function Student() {
}
Student.prototype.sayHi=function () {
console.log("学生之间打招呼的方式可能比较特殊");
};
//学生的原型,指向了一个人的实例对象
Student.prototype=new Person(10);
var stu=new Student();
stu.eat();
stu.sayHi();//报错,说stu对象没有sayHi这个方法。但如果包sayHi的方法移动到修改原型指向代码的后面,则不会报错了
原因:因为sayHi是学生构造函数原型对象中的方法,而学生构造函数的原型发生了改变,指向了Person的原型对象了,因此整个原型链中是找不到这个方法,所以就报错了,而在这个原型链中,可以找到eat的方法,所以调用eat方法是可以的(改变原型指向,感觉就像java中的继承了,Student成了Person的子类)。
在这个例子中:
stu.__proto__--->Person.prototype--->Person.prototype.__proto__--->Object.prototype-->Object.prototype没有__proto__,所以,Object.prototype中的__proto__是null
可以看成Student是Person的子类,Person是Object的子类,Object没有父类了。
继承:
继承是一种关系,类(class)与类之间的关系,JS中没有类,但是可以通过构造函数模拟类,然后通过原型来实现继承。
继承也是为了数据共享,js中的继承也是为了实现数据共享。
原型作用之一:数据共享,节省内存空间
原型作用之二:为了实现继承
通过改变原型指向实现的继承:
function Person(name,age,sex,weight) {
this.name=name;
this.age=age;
this.sex=sex;
this.weight=weight;
}
Person.prototype.sayHi=function () {
console.log("您好:"+this.name);
};
function Student(score) {
this.score=score;
}
//把student的原型指向了Person实例,实现了继承,但同时也直接初始化了父类的属性,继承过来的属性的值都是一样的了,所以,这就是问题
Student.prototype=new Person("小明",10,"男","50kg");
var stu1=new Student("100");
console.log(stu1.name,stu1.age,stu1.sex,stu1.weight,stu1.score);
stu1.sayHi();
var stu2=new Student("120");
stu2.name="张三";
stu2.age=20;
stu2.sex="女";
console.log(stu2.name,stu2.age,stu2.sex,stu2.weight,stu2.score);
stu2.sayHi();
var stu3=new Student("130");
console.log(stu3.name,stu3.age,stu3.sex,stu3.weight,stu3.score);
stu3.sayHi();
借用构造函数实现继承:
function Person(name, age, sex, weight) {
this.name = name;
this.age = age;
this.sex = sex;
this.weight = weight;
}
Person.prototype.sayHi = function () {
console.log("您好");
};
function Student(name,age,sex,weight,score) {
//借用构造函数,类似java中的super(name,age,sex,weight)
Person.call(this,name,age,sex,weight);
this.score = score;
}
var stu1 = new Student("小明",10,"男","10kg","100");
console.log(stu1.name, stu1.age, stu1.sex, stu1.weight, stu1.score);
var stu2 = new Student("小红",20,"女","20kg","120");
console.log(stu2.name, stu2.age, stu2.sex, stu2.weight, stu2.score);
var stu3 = new Student("小丽",30,"妖","30kg","130");
console.log(stu3.name, stu3.age, stu3.sex, stu3.weight, stu3.score);
这种方式解决了属性继承,并且值不重复的问题,但父级中的方法没有被继承。
组合继承:即用上面2种方式进行结合,实现继承
function Person(name, age, sex, weight) {
this.name = name;
this.age = age;
this.sex = sex;
this.weight = weight;
}
Person.prototype.sayHi = function () {
console.log("人类的打招呼的方法,您好:"+this.name);
};
function Student(name,age,sex,weight,score) {
//借用构造函数,类似java中的super(name,age,sex,weight)
Person.call(this,name,age,sex,weight);
this.score = score;
}
//修改student的原型指向person实例
Student.prototype=new Person();//不传值
Student.prototype.display=function(){
console.log("学生类的显示数据的方法",this.name, this.age, this.sex, this.weight, this.score);
}
var stu1 = new Student("小明",10,"男","10kg","100");
stu1.display();
stu1.sayHi();
var stu2 = new Student("小红",20,"女","20kg","120");
stu2.display();
stu2.sayHi();
var stu3 = new Student("小丽",30,"妖","30kg","130");
stu3.display();
stu3.sayHi();
这种方式既能实现属性的继承,又能实现方法的继承。
如何通过代码找出对象间的继承关系:
1、看构造函数中是否有 父类构造函数名.call(this,其他变量列表)或者父类构造函数名.apply(this,[其他变量列表]);如Person.call(this,name,age,sex,weight);
2、看构造函数的原型指向是否改变,子类构造函数名.prototype=new 父类构造函数();如Student.prototype=new Person();//不传值
函数声明和函数表达式的区别:
示例代码1:
//函数声明
if(true){
function f1() {
console.log("第一个");
}
}else{
function f1() {
console.log("第二个");
}
}
f1();
//在谷歌和火狐中输出的是 第一个,但IE8中是 第二个 原因:函数声明提升后,后面的重名函数会覆盖前面声明的函数。
//函数表达式
var ff;
if(true){
ff=function () {
console.log("第一个");
};
}else{
ff=function () {
console.log("第二个");
};
}
ff();//输出的都是第一个
实例代码2:
//函数声明
f1();//正常输出
function f1() {
console.log("函数声明");
}
//函数表达式
ff();//报错,说ff不是一个方法
var ff;
ff=function () {
console.log("函数表达式");
};
原因:函数提升问题,函数声明提升不提升没有影响,但函数表达式提升后,只是一个变量,因此报错说他不是一个方法
函数中this指向的问题:
普通函数调用 中的this是谁?-----window,因为页面中的所有对象都属于window
定时器方法调用 中的this是谁?----window,因为页面中的所有对象都属于window
对象.方法调用 中的this是谁?----实例对象,即谁调用方法this就是谁,如 p.sayHi();那么this就是p
构造函数 中的this是谁?-----实例对象,new操作结果赋值给谁就是谁,如 var p=new Person();那么this就是p
原型对象方法 中的this是谁?---实例对象,即谁调用方法this就是谁,如 p.sayHi();那么this就是p
函数调用的方式:
函数分为普通函数、函数表达式、构造函数、对象的方法
//普通函数
function f1() {
console.log("我是普通函数");
}
f1();//普通函数的调用
//函数表达式
var ff=function(){
console.log("我是函数表达式");
}
ff();
//构造函数
function F1() {
console.log("我是构造函数");
}
//通过new 来调用,创建对象实例
var f=new F1();
//对象的方法
function Person() {
this.play=function () {
console.log("我是对象的方法");
};
}
var per=new Person();
per.play();//对象的方法,实例.方法();
函数也是一种数据类型:
既然函数可以作为参数也可以作为返回值,还可以将函数保存到数组中,那么可以确定函数也是一种数据类型,函数同时也是实例对象。
function f1() {
console.log("我是函数");
}
var array=[f1];
var num=10;
console.log(typeof num);//number类型
console.log(typeof f1);//function类型
console.dir(f1);//f1中有prototype属性,说明f1函数也是构造函数,f1中也有__proto__,说明f1也是一个实例对象
apply、call、bind
作用:改变方法内部this的指向。不管是普通函数、构造方法、对象的方法,内部都可以使用this关键字
对于普通方法,this为window,页面中的所有对象都是window的。
构造函数,this为new出来的实例对象。
对象方法,this为调用对象方法的实例对象
只要是想使用别的对象的方法,并且希望这个方法是当前对象的,那么就可以使用apply、call或者bind的方法改变this的指向
函数名字.apply(对象,[参数1,参数2,...]);返回值为函数对应的返回值
方法名字.apply(对象,[参数1,参数2,...]);返回值为函数对应的返回值
call的使用语法
函数名字.call(对象,参数1,参数2,...);返回值为函数对应的返回值
方法名字.call(对象,参数1,参数2,...);返回值为函数对应的返回值
bind的使用语法
函数名字.bind(对象,参数1,参数2,...);返回值是复制之后的这个函数
方法名字.bind(对象,参数1,参数2,...);返回值是复制之后的这个方法
例子:
function f1(x, y) {
console.log((x + y) + ":=====>" + this.age);
}
function Person(age) {
this.age=age;
}
Person.prototype.play=function () {
console.log(this+"====>"+this.age);
};
function Student(age) {
this.age=age;
}
var per=new Person(10);
var stu=new Student(20);
var ff1=f1.apply(per,[10,20]);//相当于执行 f1(10,20);只是f1方法内部的this不再是window而是per实例对象 输出30===>10
var ff2=per.play.apply(stu);//相当于执行per.play();只是per.play方法内部的this不再是per实例对象而是stu实例对象 输出stu==>20
var ff3=f1.call(per,100,200);//相当于执行 f1(10,20);只是f1方法内部的this不再是window而是per实例对象 输出30===>10
var ff4=per.play.call(stu);//相当于执行per.play();只是per.play方法内部的this不再是per实例对象而是stu实例对象 输出stu==>20
//复制了一份
var ff1=f1.bind(per,1000,2000);//var ff1=f1.bind(per);也可以写成这样,那么在调用ff1时就需要传实参了,即ff1(1000,2000);
ff1();//输出3000:=====> 10(per实例对象的年龄)
//复制了一份
var ff2=per.play.bind(stu);
ff2();//输出20,stu实例对象的年龄
函数中的几个成员:
函数中有一个name属性----->函数的名字,name属性是只读的,不能修改
函数中有一个arguments属性--->实参伪数组
函数中有一个length属性---->函数形参的个数
函数中有一个caller属性---->调用者(f1函数在f2函数中调用的,所以,此时调用者就是f2)