粗略记录一下,欢迎各位大佬纠正(ORZ)html
2019.9.5 修改了对象有constructor 属性的错误,正确的是对象的constructor 属性是来自构造函数的原型对象的(fn.prototype.constructor )设计模式
咱们知道,JS基础数据类型是 number,string,boolean,undefined和null,而引用类型就object,
以前看的时候,我很奇怪为何像var str = "";这个str明明的类型是String,为何它却能够引用String.prototype原型对象的属性和方法呢,而且它确实有对象才有的__proto__数组
var str = ''; console.log(str.__proto__ === String.prototype) //true console.log(str.constructor === String) //true console.log(str instanceof String); //false,前面两个都符合了,这个居然是返回false,不是String的实例 console.log(String.prototype.isPrototypeOf(str)); //false,跟instanceof功能是同样的
后面百度查了一下,缘由是闭包
在读取字符串的时候会建立一个对象,可是这个对象只是临时的,因此咱们称它为临时对象,学术名字叫包装对象,说它临时,是由于咱们在读取它的属性的时候,js会把这个string字符串经过new String()方式建立一个字符串对象,一旦引用结束,这个对象就被销毁了。函数
因此说就是像读取对象那样读取属性的时候,暗地里帮我new String()了,不读取的时候,就是基础类型,因此判断是否是实例才返回了falsepost
str.name = 'nihao'; //能够这样写不报错,由于暗地里帮我对象化了, str.name //能够点name出来,可是是undefined,没错
总结:目前发现除了undefined和null不能这样搞,其余类型都是有__proto__,因此说,JS几乎全部值都为对象this
首先,要明确两点的是spa
好了,那明确这两点以后,再说说这三者有什么关系,先放图吧.net
先说一下这个,按照这个图的意思,有一个构造Person函数,这个函数默认就会有prototype属性,这个属性指向的值是一个对象,咱们叫作原型对象,而后呢,Person.prototype这个原型对象,它也会有一个constructor属性,这个属性默认指回构造函数,也就是Person函数,原型对象的name,age这些就是咱们本身往这个对象加的,Person.prototype.name = 'xxx',就像这样,而后
两个实例,person1,person2这两个对象,有__proto__属性对吧,它指向是构造函数的原型对象,就是Person.prototypeprototype
//构造函数Person function Person(){} //往原型对象加值 Person.prototype.name = 'mychirs'; Person.prototype.age = 29; Person.prototype.job = 'Software Engineer'; Person.prototype.sayName = function(){ alert(this.name); }; //两个实例 var person1 = new Person(); var person2 = new Person(); console.log(Person.prototype.constructor === Person) //true console.log(person1.constructor === Person.prototype.constructor) //true console.log(person1.__proto__ === Person.prototype) //true console.log(person2.__proto__ === Person.prototype) //true
好了,说完上面那个图已经差很少了,再放多一张图
这个图其实补充了两点,
第一,原型链的尽头是Object.prototype.__proto__,值为null;
第二,Function.constructor这个值,正常来讲,应该是指向实例Function这个函数的更上一个构造函数的原型对象的constructor,可是这张图已经没了,由于Function这个已是最高的构造函数了,Function.constructor仍是Function.prototype.constructor
//构造函数Person function Person(){} var obj = {} var person1 = new Person() console.log(Person.constructor.constructor.constructor === Function) //true,一直点下去都是这样 console.log(obj.constructor === Object.prototype.constructor ) //true console.log(obj.constructor.prototype.__proto__=== null) //true,原型链尽头,null
JavaScript 语言的继承不经过 class(ES6 引入了class 语法),而是经过“原型对象”(prototype)实现,通常来讲,
若是属性和方法在实例里找不到的话,会经过实例,也就是对象的__proto__,属性,找到构造函数的原型对象(fn.prototype),在这里面找属性和方法,若是再找不到的的话,原型对象也是对象是吧,因此它就会经过fn.prototype.__proto__,找到对象构造函数的原型对象,也就是(Object.prototype)这里找,若是再找不到,就到尽头拉,由于Object.prototype.__proto__会返回null了。
下面介绍几种常见继承方式。
//父类型 function Person(name, age) { this.name = name, this.age = age, this.play = [1, 2, 3] this.setName = function () { } } Person.prototype.setAge = function () { } //子类型 function Student(price) { this.price = price this.setScore = function () { } } Student.prototype = new Person() // 子类型的原型为父类型的一个实例对象 var s1 = new Student(15000) var s2 = new Student(14000) console.log(s1,s2)
分析以前,先大概说这个new 关键字在实例对象的时候作了什么操做,其实就三步。
var obj = {}; obj.__proto__ = F.prototype; F.call(obj);
基于上面介绍,那咱们如今就重点看看 Student.prototype = new Person() 这句代码就好了,能够分为两步:
{ name:undefined, age:undefined, play:[1, 2, 3], setName:function () { }, __proto__:Person.prototype }
2.而后这个值赋给了Student.prototype, 后面,当咱们访问Student的实例的时候,它先会在自身属性找对应属性和方法,找不到就会去Student.prototype这里找,由于咱们Student.prototype已经赋值为new Person,因此当找不到的话,会再沿着Student.prototype.__proto__指向的Person.prototype上面找
这里有一个知识点要补充一下,注意!原型对象上面的引用数据类型会共享,基础数据类型不会
var s1 = new Student() var s2 = new Student() s1.play.push(4) console.log(s1.play) //[1, 2, 3, 4] console.log(s2.play) //[1, 2, 3, 4]
总结:
要点:子类的原型赋值为父类的一个实例对象。
缺点:
1.父类的属性和方法都往子类的原型对象上面加,若是这时候父类属性有引用数据类型的话将会共享
2.建立子类实例时,没法向父类构造函数传参
function Person(name, age) { this.name = name, this.age = age, this.play = [1,2,3] this.setName = function () {} } Person.prototype.setAge = function () {} function Student(name, age, price) { Person.call(this, name, age) // 至关于: this.Person(name, age) this.price = price }
这种虽然能够传参了,引用类型也不会相互影响了,可是咱们也很明显的发现,它其实只把Person里面的this.name那些所有搬到了Student里面而已,这里还会有一个问题,就是函数没有复用,每个对象里面都会再写一次函数,尽管代码是如出一辙的。最后就是没有动过原型链,因此Person.prototype的属性和方法是一个也拿不到的
var s1 = new Student('Tom', 20, 15000) var s2 = new Student('Tom', 20, 15000) s1.play.push(4) s1.play //[1,2,3,4] s2.play //[1,2,3] s1.setAge //undefined
总结:
要点:在子类构造函数中通用call()调用父类构造函数。
缺点:
1.只能继承父类的实例属性和方法,不能继承原型属性和方法
function Person(name, age) { this.name = name this.age = age this.play = [1,2,3] this.setAge = function () {console.log('我是person类实例函数') } } Person.prototype.plays = [9,9,9] Person.prototype.setAges = function () { console.log("我是person原型对象的函数") } function Student(name, age, price) { Person.call(this,name,age) this.price = price this.setScore = function () { } } Student.prototype = new Person() //Student.prototype.constructor = Student//组合继承也是须要修复构造函数指向的 Student.prototype.sayHello = function () { }
从代码能够看出,在子类用了call,而后也把子类的原型对象赋值为父类的实例,把上面两个结合在一块儿用而已,咱们再看看上面代码,我故意在Person的构造函数加了一个setAge的方法,在Person原型对象加了一个数组,其实我想说的是,这种方法是能够解决上面那两个方式的问题,可是这个前提是在你遵循必定的规范,好比不要在构造函数加方法,不要在原型对象加引用类型的数据,否则同样仍是有问题
var s1 = new Student('Tom', 20, 15000) var s2 = new Student('Tom', 20, 15000) //在构造函数里面的不会有影响 s1.play.push(4) s1.play //[1,2,3,4] s2.play //[1,2,3] //原型对象上面的会共享 s1.plays //[9,9,9] s1.plays.push(9) s2.plays //[9, 9, 9, 9] s1.setAge == s2.setAge //false,在构造函数里方法没有复用 s1.setAges == s2.setAges //true,原型对象上面的方法就是复用
总结:
要点:在子类构造函数中通用call()调用父类型构造函数。而后又把子类的原型对象赋值为父类的实例
缺点:
1.调用了两次构造函数
上面咱们说到,构造加原型链继承的组合继承会执行两次new操做,下面这个方式就是为了解决这个调用两次的缺点所诞生的,也算目前最合适的方案。
function Person(name, age) { this.name = name this.age = age this.play = [1,2,3] this.setAge = function () {console.log('我是person类实例函数') } } Person.prototype.names= '父类原型名字' Person.prototype.plays = [9,9,9] Person.prototype.setAges = function () { console.log("我是person原型对象的函数") } function Student(name, age, price) { Person.call(this,name,age) this.price = price this.setScore = function () { } } Student.prototype = Object.create(Person.prototype) //就是这里不同 //Student.prototype.constructor = Student//组合继承也是须要修复构造函数指向的 Student.prototype.sayHello = function () { }
咱们看看代码,这个方法惟一的不一样就是把Student.prototype = new Person()换成了Student.prototype = Object.create(Person.prototype)我先大概说一下Object.create(),
object.create() 接收两个参数:
//这个对象用来作原型对象 var person = { name: '我是原型name', plays:[1,2,3] } var s1 = Object.create(person) s1 //{}空对象 s1.__proto__ === person //true,原型对象此时就是person s1.name //'我是原型name' 自身属性没有,拿原型对象里面的 var s2 = Object.create(person,{ name:{ value: '我本身的name' } }) s2 //{name: "我本身的name"} s2.__proto__ === person //true,原型对象此时就是person s2.name //'我本身的name' 自身属性就有 s1.plays.push(4) s1.plays //[1, 2, 3, 4],原型对象的引用数据类型是会共享的 s2.plays //[1, 2, 3, 4],原型对象的引用数据类型是会共享的
介绍完这个以后,咱们就能够回头看看这个语句Student.prototype = Object.create(Person.prototype),
这句话,把咱们的Student的原型对象的.__proto__ 指向了Person的原型对象,这样,当咱们访问Student的实例,好比s1.xxx,它会访问自身,若是没有,这时候s1.__proto__ 指向Student.prototype,若是Student.prototype又没有,这时Student.prototype.__proto__ 指向Person.prototype,因此就会去到Person.prototype上面找。
咱们知道Student.prototype = new Person(),这句话其实跑了两个做用,第一个做用跟Object.create同样,调整了__proto__ 的指向,第二个做用,其实它也把Person构造函数的this.name这些也往Student.prototype这上面加了,只是咱们在访问实例属性的时候,因为实例里面已经有,(用了call嘛)因此才不会读到原型对象上面的,因此这也是这个方案的优点。
总结:
要点:用Object.create(),控制子类的原型对象的__proto__ 指向父类的原型对象
缺点:
1.暂无
我只写几个常见的,由于我百度了一下好像有好多种 = =
这个模式就是保证一个类只有一个实例,实现的方法通常是先判断实例存在与否,若是存在直接返回,若是不存在就建立了再返回,这就确保了一个类只有一个实例对象。(我们平时用的window就是一个单例)
function Person(name,age){ this.name = name; this.age= age; } var getPerson = (function(){ let ins = null; return function(name,age){ if(!ins){ ins = new Person(name,age) } return ins; } })()
注意看看getPerson 这个函数就好了,因为函数里面又返回了一个函数,因此造成了一个闭包,ins 这个变量不会被销毁。
var p1 = new getPerson('你好',100) var p2 = new getPerson('好你',99) p1 //{name: "你好", age: 100} p2 //{name: "你好", age: 100} p1 === p2 //true
由于p1实例化的时候,ins变量已经有值,因此当p2也实例化的时候,getPerson只会直接返回p1那个实例,不会进行第二次new操做,因此这两个是相等的
工厂模式是指提供一个建立对象的接口而不保留具体的建立逻辑,能够根据输入类型建立对象。让子类自行决定实例化哪种工厂类,实际的建立对象过程在子类中进行。咱们下面上代码解释一下
let UserFactory = function (role) { function SuperAdmin() { this.name = "超级管理员", this.viewPage = ['首页', '通信录', '发现页', '应用数据', '权限管理'] } function Admin() { this.name = "管理员", this.viewPage = ['首页', '通信录', '发现页', '应用数据'] } function NormalUser() { this.name = '普通用户', this.viewPage = ['首页', '通信录', '发现页'] } switch (role) { case 'superAdmin': return new SuperAdmin(); break; case 'admin': return new Admin(); break; case 'user': return new NormalUser(); break; default: throw new Error('参数错误, 可选参数:superAdmin、admin、user'); } } //调用 let superAdmin = UserFactory('superAdmin'); let admin = UserFactory('admin') let normalUser = UserFactory('user')
UserFactory就是一个简单工厂,在该函数中有3个构造函数分别对应不一样的权限的用户。当咱们调用工厂函数时,只须要传递superAdmin, admin, user这三个可选参数中的一个获取对应的实例对象
暂告一段落
参考连接:
挺好的原型对象说明文章
原型链说明文章
JavaScript常见的六种继承方式
JS原型链与继承别再被问倒了
JavaScript 单例模式
从ES6从新认识JavaScript设计模式(二): 工厂模式