一.原型链机制
1. 原型链的本质
只要是对象,一定有原型对象,就是说只要这个东西是个对象,那么一定有proto属性。(错的)
我们想实例的原型对象也是一个对象,那么我们迫切的想看看这个原型对象的原型对象.如果这理论没有问题的话,这个原型对象应该也有proto指向他的原型对象吧.
function People(){
}
var xiaoming = new People();
// 原型对象的原型对象
console.log(xiaoming.__proto__.__proto__);
// 原型对象的原型对象的构造函数是谁
console.log(xiaoming.__proto__.__proto__.constructor); //Object
// 那我们看看还能不不能再向上查原型对象
console.log(xiaoming.__proto__.__proto__.__proto__);
// 结果为null
通过上面的示例, 发现开始的一句话其实是错误的.
JS的世界中只有一个对象没有原型对象,这个对象就是Object.prototype。
现在就能想明白一些事情,一个对象天生带一些属性和方法
比如
function People(){
}
var xiaoming = new People();
// 小白马上可以调用toString()方法
console.log(xiaoming.toString());
// 打印 "[object Object]"
// 这个方法是小明原型对象的原型对象身上的方法,我们可以看下
console.log(xiaoming.__proto__.__proto__);
Object是一个函数,是系统内置的构造函数,用于创造对象的。Object.prototype是所有对象的原型链终点。
所以,当我们在一个对象上打点调用某个方法的时候,系统会沿着原型链去寻找它的定义,一直找到Object.prototype。
function AA(){
console.log('我是函数');
}
var aa = new AA;
console.log(aa.__proto__); //{}
console.log(aa.__proto__.__proto__); //原型链最后一个对象{}
console.log(aa.__proto__.__proto__.__proto__); //原型链最后一个对象的原型就是null
console.log(aa.toString());//aa没有他这个方法,aa原型上有toString方法[object Object]
console.log(aa.__proto__.__proto__.constructor);//创造对象的构造函数
console.log(aa.__proto__.__proto__.constructor.prototype) //所有对象的原型链终点
Object.prototype是所有对象原型链的终点,现在我强行给它添加一个属性
Object.prototype.sayHello = function(){
alert("你好")
}
// 现在小明能sayHello
xiaoming.sayHello();
// 岂止xiaoming能sayHello 数组也能sayHello
var arr = [1,2,3];
arr.sayHello();
// 然后世界上一切都能sayHello了
"么么哒".sayHello();
Object.prototype是所有对象的原型链的终点,所以我们直接给Object.prototype增加一个方法,那么世界上所有的对象都能调用这个方法:
2. 引用类型的构造函数
所有的引用类型值,都有内置构造函数。比如
new Object()
new Array()
new Function()
new RegExp()
new Date()
我们来看看数组的情况:
// 现在是一个数组字面量
var arr = [66,4343,23];
// arr.haha = 23;
// console.log(arr.haha); //23
console.log(arr.__proto__); // 不用管,直接看constructor
console.log(arr.__proto__.constructor);
// 寻找原型链的终点,发现是Object.prototype
console.log(arr.__proto__.__proto__.constructor)
函数也是对象。JavaScript中函数是一等公民,函数是对象。函数也是对象,只不过自己能()执行。
function People(){
}
People.haha = 25;
//People.haha++;
//People.haha++;
console.log(People.haha); //27
var xiaoming = new People();
console.log(xiaoming.haha); // undefined
console.log(People.__proto__); // 不用管,直接看constructor
console.log(People.__proto__.constructor);
// 我们之前说过构造函数没有__proto__,只有prototype,只有构造函数的实例才用__proto__,那是为了让你们区分类和实例之间的区别
// 现在这个函数本身就是一个对象
示例:
我们可以利用原型链的机制,给数组对象增加方法
//数组
var arr = [45,34,23,45,65,76,45];
//我们可以利用原型链的机制,给数组对象增加方法
Array.prototype.max = function(){
var max = -Infinity;
for(var i = 0 ; i < this.length ; i++){
if(this[i] > max){
max = this[i];
}
}
return max;
}
//任何数组都能调用这个方法了:
console.log(arr.max());
3. 基本类型的包装类
基本类型值,也有包装类型。所谓包装类型,就是它的构造函数。
new Number()
new String()
new Boolean()
字符串
var str = new String("你好啊");
console.log(str); //String {"你好啊"} ,类数组
console.log(str[0])
console.log(str.toString()); //"你好啊"
// 这是神经病,还不如用字面量方式创建你,new String()这个方式没人用
// 不管怎么样,我今天就是告诉你任何一个字符串字面量,它不是一个对象,但它有一个包装类
var str = "你好啊"
// 它有一个内部机制是通过上面方式出来的
// 但是js就是拧巴
console.log(str.__proto__.constructor);
console.log(str.__proto__.__proto__.constructor);
// 字符串有个包装类,你可以理解"你好啊"是包装类new出来的,没有实际意义
// 但是你不能说字符串时对象,但是它确实有__proto__
var str = "wuwei";
console.log(str.length)
字符串转成包装类才能调用方法和属性
var str = "abcdefghijklmnopqrstuvwxyz"
console.log(new String(str));
var ap =document.querySelector('p');
ap.innerHTML = (str.bold()).fontsize(30);//直接将样式插到HTML中,现在已经废弃
console.log(str.__proto__) //str原型对象是String对象
console.log(str.__proto__.__proto__) //str原型对象的原型对象是Object对象
数值
var num = 123;
console.log(num.__proto__.constructor);
布尔值
var a = true;
console.log(a.__proto__.constructor);
null,undefined
var b = null;
console.log(b.__proto__.constructor); //报错
// Uncaught TypeError: Cannot read property '__proto__' of null
var c = undefined;
console.log(c.__proto__.constructor); //报错
// Uncaught TypeError: Cannot read property '__proto__' of undefined
//JS中所有函数原型的构造器一览 =====>如下所示
Object.prototype.constructor === Object
Number.prototype.constructor === Number
Array.prototype.constructor === Array
//所有函数原型的原型都是Object.prototype,如下所示
Function.prototype.__proto__ === Object.prototype
Number.prototype.__proto__ === Object.prototype
Array.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null
//JS中所有函数的构造器都是Funtion,如下所示�
Object.constructor === Function
Number.constructor === Function
Array.constructor === Function
//所有函数的原型都是Function.prototype,如下所示
Object.__proto__ === Function.prototype
Number.__proto__ === Function.prototype
Array.__proto__ === Function.prototype
二. 对象与属性
判断属性是否存在
1. 对象直接打点验证某个属性是否存在
对象打点调用属性,我们之前的课程已经讲过,遍历原型链。所以就可以看出来属性是否在自己身上或原型链上。如果在,就返回值;如果不在就返回undefined.
var obj = {
a : 1,
b : 2,
c : 3
// m: undefined
}
obj.__proto__ = {
d : 4
}
console.log(obj.m); //undefined
console.log(obj.a); //1
console.log(obj.b); //2
console.log(obj.c); //3
console.log(obj.d); //4
有个误会,比如obj.m值就是undefined,那么obj.m还是返回undefined。不知道m属性存在不存在。此时就需要下面的方法解决了
2. in 运算符
返回一个布尔值,表示这个属性是不是对象的属性。
var obj = {
a : 1,
b : 2,
c : false
}
console.log("a" in obj); //true
console.log("b" in obj); //true
console.log("c" in obj); //true
console.log("d" in obj); //false
// 如果原型上有方法
obj.__proto__ = {
d: 20
}
console.log("d" in obj); //true
in不仅仅检测是对象自己有没有这个属性,如果原型链上有这个属性,那么也会返回true。整个原型链如果没有这个属性,就返回false。也就是说,in操作符会进行原型链查找。
for in 这个循环,会把原型链上所有的可枚举的属性列出来:
var obj = {
a: 1,
b: 2,
c: false
}
obj.__proto__ = {
d: 20
}
for (var key in obj) {
console.log(key);
}
什么是可枚举,系统默认的属性(比如constructor)都是不可枚举的。for in循环能够把自己添加的属性罗列出来,罗列的不仅仅是自己身上的属性,还有原型链上的所有属性,不方便区分。
3. hasOwnProperty方法(是不是自己的属性)
这个方法定义在了Object.prototype对象上面,所以任何一个Object都能够拥有这个方法。
这个方法返回true、false。表示自己是否拥有这个属性,不考虑原型链。就看自己身上有没有这个属性,不进行原型链查找。
var obj = {
a : 1,
b : 2,
c : 3
}
obj.__proto__ = {
d : 4
}
console.log(obj.hasOwnProperty("a")); //t
console.log(obj.hasOwnProperty("b")); //t
console.log(obj.hasOwnProperty("c")); //t
console.log(obj.hasOwnProperty("d")); //f
for……in考虑原型链,所以我们可以内嵌一个判断,把自己身上的属性输出:
for(var k in obj){
obj.hasOwnProperty(k) && console.log(k);
}
最可靠的还是这两个结合在一起的方式
定义属性Object.defineProperty() 一个对象定义属性
js引擎允许对属性操作的控制,需要使用方法Object.defineProperty()来实现。这个方法接收三个参数:属性所在的对象、属性的名字、描述符对象。
var obj = {
name: "张三"
}
// defineProperty 对象属性的描述 ,defineProperty挂载到Object身上的
// 第一个参数:给哪个对象添加属性
// 第二个参数:添加的属性名 要加引号
// 第三个参数:属性的描述对象
Object.defineProperty(obj, 'age', {
// value:1111, //表示属性值,不要与get set一起设置,控制台中age的值是浅色的
//get set都是函数
get: function () { //获取
console.log("你要获取age属性", arguments); //arguments没有实参
return 123; //return决定获取的值
},
set: function (value) { //设置
console.log("你现在正在给age赋值", arguments);
}
})
obj.age = "777777" //给obj.age设置值,就会调用set函数了
console.log(obj);//此时控制台中age的是(…)颜色是浅色的,说明值是动态获取的
// console.log(obj.age); //获取obj.age的值,实际上走了get方法
configurable:表示能否通过 delete 删除属性从而重新设置属性
var obj = {
name: "张三"
}
Object.defineProperty(obj, 'age', {
configurable: true,//设置了configurable为true,就可以删除age属性了
get: function () {
console.log("你要获取age属性", arguments);
return 123;
},
set: function (value) {
console.log("你现在正在给age赋值");
}
})
console.log(obj);
var obj = {
name: "张三",
sex: "女"
}
Object.defineProperty(obj, 'name', {
configurable: false //name之前是可以删除的,设置configurable:false后,不可以删除了
})
console.log(delete obj.name);
console.log(obj);
是否可以利用defineProperty方法重新定义属性(正常情况下是可以反复使用该方法重新设置属性的)。默认为true。
js自带的属性都是灰色的,enumerable默认为false,不能通过for in遍历。
var obj = {
name: "张三",
sex: "女"
}
Object.defineProperty(obj, 'age', {
enumerable:true, //age在控制台中是灰色的,不会被for(var key in obj)遍历到,深色的属性才会遍历到,想要遍历到添加enumerable:true变成深色的
get(){
return 34;
}
})
console.log(obj);
for(var key in obj){ console.log(key)}
writable:给属性重新赋值。是否允许配置
var obj = {
name: "张三",
sex: "女"
}
Object.defineProperty(obj, 'age', {
value:1111,
writable:true
})
obj.age =2222; // 此时不能赋值,添加writable:true就可以给age赋值了
console.log(obj);
value:属性值,读一个对象的属性值时先读的是这个值。默认值为 undefined。
定义多个属性Object.defineProperties()
var book = {};
Object.defineProperties(book, {
_year: {
value: 2004
},
edition: {
value: 1
},
year: {
get: function () {
return this._year;
},
set: function (newValue) {
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
}
});
读取属性的描述对象Object.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptor(obj,"name")
4. instanceof 运算符
类在英语里面叫做class,实例在英语里面叫做instance。
instaceof运算符:
function AA() { }
var aa = new AA()
var bb = {}
console.log(aa instanceof AA);//true 是
console.log(bb instanceof AA);//false 不是
验证A对象是不是B类的实例。
举例:
//类,构造函数
function Dog(){
}
//实例化一个对象
var d = new Dog();
//验证d是不是Dog的一个实例
console.log(d instanceof Dog); // true
这里要注意一个事儿:“HelloKitty是狗”:
//类,构造函数
function Dog(){
}
function Cat(){
}
Cat.prototype = new Dog(); //继承
var hellokitty = new Cat(); //通过cat来实例一个
console.log(hellokitty.constructor); //Dog
console.log(hellokitty instanceof Cat); //true
console.log(hellokitty instanceof Dog); //true
instanceof 运算符的机理: 遍访hellokitty这个对象的原型链上的每个原型对象,如果遍访到这个原型对象,是某个构造函数的prototype,那么就认为hellokitty是这个构造函数的实例,返回true。
检查数组:
一个数组用typeof检测的时候,返回object
var arr = [];
console.log(typeof arr);
用instanceof运算符能够轻松解决数组的识别:
var arr = [];
console.log(arr instanceof Array);
ECMAScript5标准中新增了一个API验证数组:
Array.isArray(arr)
var arr=[20,30];
arr.add="加"
console.log(arr);
console.log(Array.isArray(arr));//静态方法,调用主体永远不会发生变化
IE9开始兼容。
总结一下,A instanceof B, 不能证明A是new B()出来的,因为可能是继承。
5. 检测数据类型的方法
Object.prototype.toString.call(),如下图所示
Object.prototype.toString.call(123); //"[object Number]"
Object.prototype.toString.call("123"); //"[object String]"
Object.prototype.toString.call(true); //"[object Boolean]"
Object.prototype.toString.call(undefined); //"[object Undefined]"
Object.prototype.toString.call(null); //"[object Null]"
Object.prototype.toString.call(function(){}); //"[object Function]"
Object.prototype.toString.call([]); //"[object Array]"
Object.prototype.toString.call({}); //"[object Object]"
封装判断数据类型
function getTypeof(val){
var str = Object.prototype.toString.call(val)
var aa = str.split(' ')[1]//按空格切割,要后面的部分
return aa.slice(0,aa.length-1)
}
console.log(getTypeof(123));
console.log(getTypeof('ss'));
console.log(getTypeof(true));
console.log(getTypeof(undefined));
三. 继承
1. 原型链继承
将父类的实例作为子类的原型
function People(name){
this.name = name;
}
People.prototype.sayHello = function(){
alert("你好我是" + this.name);
}
// var xiaoming = new People("小明");
// xiaoming.chifan();
function Student(name,xuehao){
this.name = name;
this.xuehao = xuehao;
}
//核心语句,继承的实现全靠这条语句了:
Student.prototype = new People('大明');
//Student.prototype.sayHello = function(){
// alert("你好我是小学生,我的学号是" + this.xuehao);
//}
Student.prototype.study = function(){
alert("好好学习,天天向上");
}
var xiaohong = new Student("小红",1001);
xiaohong.sayHello();
子类可以覆盖父类的一些方法,父类的方法不冲突,因为我们子类追加的方法,追加到了父类的实例上。
但是父类新增原型方法/原型属性,子类都能访问到,父类一变其它的都变了
2. 构造函数继承
使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function People(name,age){
this.name = name;
this.age = age
//这个函数this默认指向window,name属性默认在window身上。想让this指向调用这个函数的对象,显示的绑定调用就可以实现People.call(this,name)
}
function Student(name,xuehao,age){
// 核心语句
People.call(this,name,age)//显示的绑定调用
this.xuehao = xuehao;
}
var xiaohong = new Student("小红",1001,19);
for(var key in xiaohong){
console.log(key);
}
console.log(xiaohong);
方法都在构造函数中定义, 只能继承父类的实例属性和方法,不能继承原型属性/方法,无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
3. 组合继承
就是将原型链继承和构造函数继承组合在一起;继承两个优点
通过调用父类构造,继承父类的属性并保留传参的优点,
然后再通过将父类实例作为子类原型,实现函数复用
function People(name){
this.name = name;
}
People.prototype.sayHello = function(){
alert("你好我是" + this.name);
}
function Student(name,xuehao){
People.call(this,name);
this.xuehao = xuehao;
}
//核心语句,继承的实现全靠这条语句了:
Student.prototype = new People('大明');
Student.prototype.study = function(){
alert("好好学习,天天向上");
}
var xiaohong = new Student("小红",1001);
xiaohong.sayHello();
调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
4. 寄生组合继承
通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
function People(name){
this.name = name;
}
People.prototype.sayHello = function(){
alert("你好我是" + this.name);
}
function Student(name,xuehao){
People.call(this,name);
this.xuehao = xuehao;
}
//核心语句,继承的实现全靠这条语句了:
function Fn(){}
Fn.prototype = People.prototype
Student.prototype = new Fn();
Student.prototype.study = function(){
alert("好好学习,天天向上");
}
var xiaohong = new Student("小红",1001);
xiaohong.sayHello();
5. 圣杯模式
圣杯模式就是讲借用的构造函数封装一下
function inherit(People,Student){
function Fn(){}
Fn.prototype = People.prototype
Student.prototype = new Fn();
Student.prototype.constructor = Student;
Student.prototype.parent = People;
}
inherit(People,Student )