参考:
前述
参考:
创建对象的方式
-
字面量或则new Object()
-
缺点:创建大量相似对象时,会产生大量重复的代码;
-
工厂模式
-
缺点:都是new Object();来的,不同工厂创建的对象无法区分是什么类;
-
构造函数
-
优点:所创建的对象和构造函数建立起了联系,因此我们可以通过原型来识别对象的类型;
-
缺点:造成了不必要的属性函数的创建;如果对象属性中如果包含函数的话,那么每次我们new时都会新建一个函数对象,浪费了不必要的内存空间,因为函数是所有的实例都可以通用的。
-
原型模式
-
优点:在原型prototype上添加公用的属性和方法,从而实现代码的复用,相对于构造函数来说,解决了属性为函数时复用的问题;
-
缺点
-
没有办法通过传入参数来初始化值
-
如果在prototype上添加一个引用类型如Array,么所有的实例将共享一个对象,一个实例对引用类型值的改变会影响所有的实例。
-
组合使用构造函数和原型
-
优点:解决了构造函数和原型模式单独使用时的缺点;因此我们可以组合使用这两种模式,通过构造函数来初始化对象的属性,通过原型对象来实现函数方法的复用。
-
因为使用了两种不同的模式,所以对于代码的封装性不够好,代码都不在一起;
-
动态原型模式
-
优点:解决了组合模式中封装性的问题
-
寄生构造函数【特殊】
-
优点: 封装性比较好,它主要是基于一个已有的类型,在实例化时对实例化的对象进行扩展。这样既不用修改原来的构造函数,也达到了扩展对象的目的。
-
缺点:和工厂模式一样的问题,不能依赖 instanceof 操作符来确定对象的类型。
-
稳妥构造函数【特殊】
-
优点:提高变量安全性,私有化属性,实例只能通过方法取得
-
缺点:和工厂函数一样,没有办法使用 instanceof 操作符来判断对象的类型;而且带来了闭包问题;
对象代码:
1.字面量
const obj = {
name:"zhangsan",
age: 18,
sayName: function(){
console.log(this.name)
}
}
2. 工厂函数
function createPerson(){
let obj = {}
obj.name = name
obj.age = age
obj.sayName = function(){
console.log(obj.name)
}
return obj;
}
function createDog(name, age) {
var obj = new Object()
obj.name = name
obj.age = age
obj.sayHello = function () {
alert('汪汪~~')
}
return obj
}
var obj1 = createPerson('猪八戒', 28)
var obj2 = createPerson('白骨精', 16)
var dog = createDog('旺财', 3)
// 缺点:人狗不分了
3.构造函数
function Person(name, age, gender) {
this.name = name
this.age = age
this.gender = gender
this.sayName = function () {
alert(this.name)
}
}
function Dog() {}
var per = new Person('孙悟空', 18, '男')
var per2 = new Person('玉兔精', 16, '女')
// 优点:人狗分开
console.log(per instanceof Person);
console.log(dog instanceof Person);
// 缺点每次都会创建新的函数,浪费内存
console.log(per.sayName === per2.sayName) // false
4.原型模式
// 缺点1:没有办法传参数
function Person(){}
Person.prototype.name = "张三"
Person.prototype.sayName = function(){
console.log(this.name)
}
Person.prototype.myList = [] // 缺点:所有实例公用,一个实例对其修改,另外的数据也变了
const p1 = new Person();
const p2 = new Person();
// 优点:每次创建都一个函数,优化内存
console.log(p1.sayName === p2.sayName) // true
5. 组合使用构造函数和原型
function Person(name,list){
this.name = name
this.myList = list
}
Person.prototype.sayName = function(){
console.log(this.name)
}
或则
Person.prototype = {
constructor: Person,
sayName: function(){
alert(this.name);
}
}
// 优点:解决构造函数和原型模式的创建对象的缺点
// 缺点:封装性不好
6.动态原型模式
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
if(typeof this.sayName !== "function" ){
Person.prototype.sayName: function(){
alert(this.name);
}
}
}
var p = new createPerson("james",9,"student");
p.sayName(); // "james"
注意在 if 语句中检查的可以是初始化后应该存在的任何属性或方法,不必要检查每一个方法和属性,只需要检查一个就行。
优点:解决了组合模式中封装性的问题
7.寄生构造函数模式
案列1
function Person(name, age, job){
var o = new Object();
o.name = name;
o.sayName = function(){
alert(this.name);
};
return o;
}
var p = new Person("james",9,"student");
console.log(p instanceof Person) // false
案列2
function SpecialArray() {
var values = new Array;
values.push.apply(values, arguments);
values.toPipedString = function() {
return this.join("|");
};
return values;
}
var colors = new SpecialArray("red", "green", "blue");
console.log(colors.toPipedString());
这个模式可以在特殊的情况下用来为对象创建构造函数。假设我们想创建一个具有额外方法的特殊数组。
由于不能直接修改 Array 构造函数,因此可以使用这个模式。
与工厂函数的区别在于这是new的,而不是 函数调用;
比如为Array扩展一些方法,如果直接在Array的原型对象上做扩展,那么就会污染其他的数组。
寄生就意味着这个新的构造函数是在Array构造函数的基础上继续修改的。
最后,加上new是为了让人清楚实例是新(被改造的)构造函数的实例,
这也是名字中带有构造函数四个字的原因。以上,寄生构造函数模式的意义。。
优点:我对这个模式的理解是,它主要是基于一个已有的类型,在实例化时对实例化的对象进行扩展。
这样既不用修改原来的构造函数,也达到了扩展对象的目的。
缺点:和工厂模式一样的问题,不能依赖 instanceof 操作符来确定对象的类型。
8.稳妥构造函数模式
function Person(name, age, job){
var o = new Object();
//可以在这里定义私有变量和函数
let myName = name
o.sayName = function(){
console.log(myName);
}
return o;
}
var p1 = Person('james', 9, 'student')
var p2 = Person('qwe', 9, 'student')
p1.sayName() // "james"
p2.sayName() // "qwe"
优点:以上面为例,除了 sayName 方法外,没有别的方法可以访问数据成员,这就是稳妥构造函数提供的安全性。
缺点:和寄生构造函数一样,没有办法使用 instanceof 操作符来判断对象的类型;
而且带来了闭包问题;
说明
理解了就非常简单啦。其实就是下面内容:
概念:
-
构造函数
-
实例对象
-
原型对象
-
显示
-
隐式
-
构造器constructor
-
内置对象
-
Object
-
Function
结果:
-
实例属性和方法
-
静态属性和方法
-
原型对象的属性和方法
构造函数
就是普通的函数的定义写法,习惯大写。被new调用就叫做构造函数。
每个 JavaScript 函数实际上都是一个 Function 的实例对象。
(function(){}).constructor === Function // true
function MyClass() {} // 构造函数
var mc = new MyClass(); // 实例对象
// 相当于
var mc = new MyClass.prototype.constructor
MyClass.prototype.constructor === MyClass // true
构造函数返回
-
如果是基本类型,还是原型那一套,返回值在实例中不起作用。
-
如果是对象,那就不是原型那一套了,就直接是这个对象了。
new的过程
-
创建一个空对象
newObject = null; -
设置原型,将对象的(隐式)原型设置为函数的 prototype 对象。
newObject = Object.create(constructor.prototype); -
将 this 指向新建对象,并执行函数;
result = constructor.apply(newObject, arguments); -
判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
function objectFactory() { let newObject = null, constructor = Array.prototype.shift.call(arguments), result = null;
// 参数判断 if (typeof constructor !== "function") { console.error("type error"); return; }
// 新建一个空对象,对象的原型为构造函数的 prototype 对象 newObject = Object.create(constructor.prototype);
// 将 this 指向新建对象,并执行函数 result = constructor.apply(newObject, arguments);
// 判断返回对象 let flag = result && (typeof result === "object" || typeof result === "function");
// 判断返回结果 return flag ? result : newObject; }
// 使用方法 // objectFactory(构造函数, 初始化参数);
原型
概述
每个、任何函数都会被环境加在一个属性prototype,指向一个原型对象。
该函数作为普通函数使用时,该原型没有啥作用。可以使用该函数的构造函数来创建一个对象实例,该实例对象中有隐藏属性指向上述的原型对象。该隐藏属性是没有办法直接获取的,一些浏览器环境给加上了__proto__来解决,但是最好不要直接用__proto__来做什么操作,可以用Object.getPrototypeOf(实例)代替。但是也是不符合规范的,最正确的做法应该是使用Ocject.create()来继承一个原型;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>测试原型对象</title>
</head>
<body>
<script>
function MyClass() {}
var mc = new MyClass();
var mc2 = new MyClass();
console.log(1,mc.__proto__ === MyClass.prototype)
console.log(2,mc2.__proto__ === MyClass.prototype)
console.log(3,MyClass.prototype);
console.log(4,MyClass.__proto__);
console.log(5,mc.__proto__);
console.log(6,mc.prototype);
console.log(7,MyClass.__proto__ === MyClass.prototype)
</script>
</body>
</html>
1 true
2 true
3 {constructor: ƒ}
4 ƒ () { [native code] }
5 {constructor: ƒ}
6 undefined
这个连接是存在与实例与构造函数的原型对象之间的,而不是存在于实例和构造函数之间的
每个函数都有一个原型对象,默认什么属性都没有,开发者可以自己添加。但是原型对象也有原型,默认指向根对象的原型对象,就是Object的原型对象,这上面有一些对象的属性和方法。
实例对象都只有__proto__,函数才有prototype,函数的__proto__是一个函数类型(Function.prototype)。
Object的(显式)原型的(隐式)原型是null。
首先:
typeof Object === 'function'
-
每个函数都有一个prototype属性,它默认指向一个空对象(即称:原型对象)
-
空对象:没有开发者自己的属性
-
每个函数的prototype都是指向一个独立的对象
-
js的内置对象指向的可不是一个空对象哦,有一些toString()、hasOwnProperty()等方法
-
原型对象中有一个属性constructuor,他指向这个构造函数本身
为原型对象添加属性
function Person(){}
// 为原型对象添加方法
Person.prototype.sayName = function(){
alert(this.name);
}
// 为原型对象添加属性
Person.prototype.name = "张三"
原型的结构(node环境)
构造函数
function Person(){}
// 为原型对象添加方法
Person.prototype.sayName = function(){
alert(this.name);
}
实例
var student = new Person();
为函数对象添加属性
相当于静态成员,与实例属性是不互通的。
function Phone(){}
Phone.name = "手机"
Phone.change = function(){
console.log("我可以改变世界")
}
Phone.prototype.size = "5.5inch"
let nokia = new Phone()
console.log(nokia.name) // undefined
console.log(nokia.size) // 5.5inch
关于Function的原型
不管了,没看懂
-
所有函数的隐式原型都是一样的,都是Function.prototype
-
这是说的是函数的隐式原型,不是实例对象哈
测试原型对象
显式隐式原型
-
每个函数function都有一个prototype,即显示原型(属性)
-
每个实例对象都有一个__proto__,可称为隐式原型(属性)
-
对象的隐式原型的值为其对应构造函数的显示原型的值
-
每个函数function也有一个__proto__属性,function是Function的实例对象
-
函数的prototype属性:在定义函数时自动添加的,默认值是一个“空”对象Object
-
实例对象的__proto__属性:创建对象时自动添加的,默认值为构造函数的prototype属性值
-
程序员能直接操作显示原型,但不能直接操作隐式原型(ES6之前)
隐式原型是对于实例对象来说的
显示原型是对于构造函数来说的
原型链
-
原型链的尽头是Object的显式原型对象,再后就是null了
-
原型链本质上是隐式原型链
-
原型链的作用:查找对象的属性
-
对象.xxx就是去查找, 是查找的隐式原型链
属性修改
注意修改实例的属性和修改原型的属性影响不同。
instanceof原理
a instanceof B
判断实例对象a的隐式原型链上 是否有 B的显示原型
总结
-
每个构造函数都有一个显示原型prototype属性,它默认指向一个Object空对象(即称:原型对象)
-
空对象:默认没有开发者自己的属性,可以手动添加
-
每个函数的prototype都是指向一个独立的对象
-
js的内置对象指向的可不是一个空对象哦,有一些toString()、hasOwnProperty()等方法
-
原型对象中有一个属性constructuor构造器,指向这个构造函数本身
-
除了Object.prototype外,原型对象都是Object的实例
-
每个实例对象都被添加了隐式原型__proto__,指向构造函数的原型对象
-
所有函数都是Function的实例对象(包括Function自己也是)
-
Object的原型对象是原型链的尽头,即Object.prototype
-
再往下就是null
-
A instanceof B 表示:B函数的显式原型对象prototype是否在A对象的原型链上
简单版:
完全完整版: