面向对象
定义
对于什么是面向对象呢?在js中对象就是将一组数据属性和函数进行封装的引用类型;在js中有两种数据类型:基本数据类型和引用类型,对象就是引用类型的一种,其实都是数据的封装;
创建对象
一、字面量:
var obj = {};//其实对于{}之间的是代码块,就是一块区间,也就是一组数据的封装
var obj = {
name:"zhangshang",
age:13,
sex:"男"
}
//对于这样的定义对像的话只可以每次定义一次,数据都是固定的,比较麻烦
二、利用函数的return的返回值{}创建对象,这个可以传参数,进行属性和函数的改变;
这样的可以达到创建一批的对象,就是所谓的工厂模式
function createPerson(name, age, sex) {
return {
name: name,
age: age,
sex: sex
}
}
// 想要分辨两个对象各自的类型
// console.log(Object.prototype.toString.call(p)); // [object Object]
// console.log(Object.prototype.toString.call(c)); // [object Object]
// 结论:无法分辨
三、通过构造函数创建对象;
规则一般是首字母大写,不是强求的语法;
// function Person(name, age, sex) {
// // 函数中的this是本次构造的实例
// this.name = name;
// this.age = age;
// this.sex = sex;
// // return 1;
// // return "1";
// // return true;
// // return undefined;
// // return null;
// // return [];
// // return {};
// }
// var p = new Person("张三", 13, "男");
// console.log(p);
// console.log({})
当我们创建构造函数的时候,为了让每个实例对象可以访问共享属性和方法,this就是个比较关键的元素的了,对于this的指向谁,就是构造函数在创建对象的时候,this就会和对象绑定在一起;
new:对于new关键字就是在堆内存开辟一个空间给对象实例用,通过构造函数传进来的实参,一一对应的给实例附上属性和函数方法,但是需要注意的当通过原型设置的属性函数在构造函数内部的原型中,并不在对象实例中,对象中没有原型对象的,只是有个暗属性__proto__这个是保存了构造函数的原型对象的地址,但是不是原型;
构造函数的创造对象的顺序
一、首先用过new在内存中开辟一个空间
二、把这个空间与this进行绑定
三、然后执行这个函数,创建实例的属性和方法
四、将this与对象实例进行绑定,就可以调用
数据类型
基本数据类型就不多讲了
引用类型也可以讲的是对象,其实对象也是一组有规则的数据的封装;
jsp类型的判断
对于数据类型就可以typeof函数进行判断
但是对于对象来说都是object类型,但是细分的话还可以分很多种
// 分辨对象的精确类型: Object.prototype.toString.call(分辨的目标) => [object 类型]
// console.log(Object.prototype.toString.call([]))
// console.log(Object.prototype.toString.call(/a/))
// console.log(Object.prototype.toString.call(new Date()))
// console.log(Object.prototype.toString.call({}))
// console.log(Object.prototype.toString.call("123"))
// console.log(Object.prototype.toString.call(1))
// console.log(Obsject.prototype.toString.call(true))
// console.log(Object.prototype.toString.call(undefined))
// console.log(Object.prototype.toString.call(null))
// function demo() {
// console.log(this);
// }
// demo.call(undefined)
// demo.call(null)
构造函数
定义
其实来说啊,构造函数就是一个函数而已,但是对于jsp可以利用new关键字让函数创建一个对象而已;通过构造函数创建的对象,就是构造函数的一个实例
构造函数的定义
// 定义构造函数
// var Animal = function(eye, weight) {
// this.eye = eye;
// this.weight = weight;
// this.eat = function() {
// console.log("吃东西");
// }
// this.sleep = function() {
// console.log("zzzzzzzzzzzzzzz...")
// }
// }
// var a1 = new Animal("眼睛", 100);
// a1.eat();
// a1.sleep();
// var a2 = new Animal("眼睛1", 200);
// a2.eat();
// a2.sleep();
// console.log(a1.eat === a2.eat); // false
// 问题: 既然是同样的方法 最好就一个 能够被每一个实例复用
/* ********************************************************************** */
// 定义构造函数
// var Animal = function(eye, weight) {
// this.eye = eye;
// this.weight = weight;
// this.eat = eat;
// this.sleep = sleep;
// }
// // 之所以每一次都定义两个新函数 是因为代码放在了里面
// // 现在将代码抽取到函数的外部 这样就只会定义一次 不论初始化多少Animal的实例 都共用同一个方法
// var eat = function() {
// console.log("吃东西");
// }
// var sleep = function() {
// console.log("zzzzzzzzzzzzzzz...")
// }
// var a1 = new Animal("眼睛", 100);
// a1.eat();
// a1.sleep();
// var a2 = new Animal("眼睛1", 200);
// a2.eat();
// a2.sleep();
// console.log(a1.eat === a2.eat); // true
// 思考: 之所以每一个实力的方法的地址不同 是因为每一次执行构造函数的时候 都定义了两个新函数
// 结论:虽然将代码抽取到外部 可以解决复用的问题 但是污染了外部的作用域 也就意味着 定义一个构造函数 还要定义一堆变量 定义一堆函数 这不合适
/* ********************************************************************** */
// var Animal = function (eye, weight) {
// this.eye = eye;
// this.weight = weight;
// this.eat = prototype.eat;
// this.sleep = prototype.sleep;
// }
// // 为了解决外部环境的污染问题 我们就定义一个变量 保存一个对象 将所有的函数都挂载在该对象身上
// var prototype = {
// eat: function () {
// console.log("吃东西");
// },
// sleep: function () {
// console.log("zzzzzzzzzzzzzzz...")
// }
// }
// var a1 = new Animal("眼睛", 100);
// a1.eat();
// a1.sleep();
// var a2 = new Animal("眼睛1", 200);
// a2.eat();
// a2.sleep();
// console.log(a1.eat === a2.eat); // true
// 现在只剩下一个变量 可是这一个也不想要 所以可以挂载到函数本身
// 而该对象 默认就是每一个函数的属性 它身上的内容都可以被构造函数的实例访问到
// var Animal = function (eye, weight) {
// this.eye = eye;
// this.weight = weight;
// }
// console.log(Animal.prototype); // 每一个函数天生有一个属性,叫做prototype。该属性的值时一个对象 该对象身上有一个默认属性 constructor 该属性的值就是构造函数自己
// console.log(Animal.prototype.constructor === Animal); // true
// 该对象的作用就是共享内容给所有的Animal的实例
// Animal.prototype.eat = function() {
// console.log('eat');
// }
// Animal.prototype.sleep = function() {
// console.log('sleep');
// }
// var a1 = new Animal("a", "B");
// var a2 = new Animal("ba", "B");
// a1.eat();
// a2.eat();
// console.log(a1.eat === a2.eat);
// console.log(a1);
// console.log(a2);
// 原型查找机制: 当一个对象 调用属性时, 会先查找自身是否具备 如果有 就用 如果没有 会查找自身的构造函数的原型(原型对象)是否具备 如果有就用 如果没有 就继续向上查找(因为原型对象也是对象 查它的属性又触发了它的原型查找机制) 直到Object.prototype为止 因为再往上就是null
instanceOf属性
对于这个其实就是一个关键字,就是判断一个对象是不是一个构造函数的实例
// 注: 所有引用类型 都是Object的实例
// console.log(c instanceof Object);
// console.log(d instanceof Object);
// console.log(Dog instanceof Object);
// console.log(Cat instanceof Object);
// console.log(Object instanceof Object);
// Function 是所有的函数的构造函数
// 小题目:请说出以下代码的输出
console.log([] instanceof Array); // true
console.log({} instanceof Array); // false
console.log([] instanceof Object); // true
console.log(Array instanceof Object); // true
console.log(Function instanceof Object); // true
console.log(Object instanceof Function); // true
console.log(Object instanceof Object); // true
console.log(Function instanceof Function); // true
原型链
定义:
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个对象(原型对象),它的用途是包含可以由特定类型的所有实例共享的属性和方法。逻辑上可以这样理解:prototype通过调用构造函数而创建的那个对象的原型对象。使用原型的好处可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。
一、对于所有的函数都是由Function()创建的,但是在jsp中每个函数都有一个原型对象,
二、对于原型对象是怎么来的就是由Object()对象构造函数创建来的,但是对于Object函数中的原型是本身自带的吧!所以Object其中的原型就是所有的原型的老祖宗,他不是任何一个原型的实例,上面的原型为null
三、但是Object函数还是由Function()创建的,但是Function()中的原型是Object函数中的原型的一个实例;
总结:就是对象的老祖宗是Object函数中的原型对象,函数的老祖宗就是Function,分别的关键字是new和function进行触发的
对于原型声明和构造函数声明的区别
1、就是通过构造函数声明对象的话,在构造函数内部添加属性和方法,当创建对象实例的时候,构造函数会创造两个地址空间,所以两个对象的地址不一样,不可以共享方法和属性;
2、对于在原型中添加属性和方法的方式,再来创建对象实例的话,所有实例都可以共享属性和方法;
如下图:
通过在构造函数中添加属性和方法
在原型中添加方法:
在原型模式声明中,多了两个属性,这两个属性都是创建对象时自动生成的,__proto__属性是实例指向原型对象的一个指针,它的作用是指向构造函数的原型属性constructor.通过这两个属性,就可以访问到原型里的属性和方法了。PS:IE(10及以前的)浏览器在脚本访问__proto__会不能识别,火狐和谷歌浏览器及其他某些浏览器均能识别。虽然可以输出,但无法获得内部信息。
isPrototypeOf()
判断一个对象是否指向了该构造函数的原型对象,可以使用isPrototypeOf()方法来测试。
alert(Box.prototype.isPrototypeOf(box));//只要实例化对象,即都会指向
我们可以通过对象的实例获取原型对象中的属性和方法,但是我们不可以通过实例进行修改
var box1=new Box(); alert(box1.name);//原型里的值
box1.name=”zhang”; alert(box1.name);//就近原则
var box2=new Box();
alert(box2.name);//原型里的值,没有被box1修改
如果想要box1也能在后面继续访问到原型里的值,可以把构造函数里的属性删除即可。具体如下:
delete box1.name; alert(box1.name);
hasOwnProperty()
obj.hasOwnProperty(prop)
hasOwnProperty() 方法会返回一个布尔值,指示对象是否具有指定的属性作为自身(不继承)属性。
// 定义类
var Dog = function(name, age) {
this.name = name;
this.age = age;
}
Dog.prototype.sayHello = function() {
}
Dog.prototype.num = 10;
var d = new Dog("小白", 13);
console.log(d.name);
console.log(d.age);
console.log(d.num);
console.log(d.hasOwnProperty("name")); // 检测name是否是d的自身属性
console.log(d.hasOwnProperty("age")); // 检测age是否是d的自身属性
console.log(d.hasOwnProperty("num")); // 检测num是否是d的自身属性
propertyIsEnumerable()
obj.propertyIsEnumerable(prop)
propertyIsEnumerable() 方法返回一个布尔值,表明指定的属性名是否是当前对象可枚举的自身属性。
枚举就是用for in可以罗列出来的属性
var o={};
var a=[];
o.prop="属性";
a[0]=3;
console.log(o.propertyIsEnumerable("prop"));
console.log(a.propertyIsEnumerable(0))
constructor
属性,返回一个指向创建了该对象原型的函数引用。需要注意的是,该属性的值是那个函数本身,而不是一个包含函数名称的字符串。对于原始值(如1,true 或 “test”),该属性为只读。
var o={};
console.log(o.constructor===Object);
var a=[];
console.log(a.constructor===Array);
var n=4;
console.log(n.constructor===Number);
function Tree(name) {
this.name=name;
}
var theTree=new Tree("big");
console.log(theTree.constructor)
function fn1() {};
var f1 = new fn1();
console.log(f1.constructor === fn1); //true
//替换默认原型
function fn2() {};
fn2.prototype = {};
var f = new fn2();
console.log(f.constructor === fn2); //false
console.log(f.constructor === Object); //true
字面量方式为什么constructor会指向Object?因为Box.prototype={};这种写法其实就是创建了一个新对象。而每创建一个函数,就会同时创建它prototype,这个对象也会自动获取constructor属性。所以,新对象的constructor重写了Box原来的constructor,因此会指向新对象,那个新对象没有指定构造函数,那么就默认为Object.原型的声明是有先后顺序的,所以,重写的原型会覆盖之前的原型。
原型查找机制
对于一个实例来说当查询一个属性和方法的时候如果自身的空间中没有,就会通过__proto__属性查找它的构造函数中的原型中的属性和函数,如果还没有找到的话,就会通过构造函数中的__proto__查找Object中的原型中的属性和方法,如果没有就是undefined的
// 定义构造函数
// var Dog = function(name, age) {
// this.name = name;
// this.age = age;
// }
// var d = new Dog("小白", 23);
// 每一个对象都有一个属性 __proto__ 这个属性是浏览器给每一个对象添加的 不属于ECMAScript的范围 有一些浏览器不叫作这个"__proto__"名称
// 该属性指向构造函数的原型
// console.log(d.__proto__ == Dog.prototype); // true
// 所以 对象在访问原型上的内容的时候 等价于 是从 实例.__proto__身上获取到的
// 实例又是对象 所以如果实例.__proto__身上没有属性 会从 实例.__proto__.__proto__身上获取 直到终点 Object.prototype
// 定义构造函数
var Dog = function(name, age) {
this.name = name;
this.age = age;
}
Dog.prototype.sayHello = function() {
// console.log("大家好,我的名字是" + this.name + "我今年" + this.age + "岁了");
console.log(`大家好,我的名字是${this.name}我今年${this.age}岁了`);
}
var d = new Dog("小白", 23);
console.log(d.__proto__);
console.log(d.toString()); // 查询顺序 1 查看d自身有没有toString方法 2 查询 d.__proto__身上是否有toString方法 3 查询d.__proto__.__proto__ 查询到 调用
// 这种查询顺序 叫做原型链查找
// 继承的简单介绍
// Dog的实例 居然访问到了Object的方法 这种现象: 你Object拥有的 我Dog全有 你Object没有的 我Dog也有 叫做继承
// Object是父类(也叫做超类) Dog子类(派生类)
// 子类的实例 同时也是父类的实例
// instanceof的查找机制 就是沿着原型链查找 只要某个函数能够在原型链上找得到 就认为是实例