首先是对“对象”的理解:
对象是一组没有特定属性的值,对象的每一个属性或方法都有一个名字,而每一个名字都映射到一个值,其中值可以是数据或函数。每一个对象都是基于一个引用类型创建的,这个引用类型可以是原生类型,也可以是开发人员自定义的类型。——高程
(好的,这里说的比较不容易理解)
(不急,接下来再看)
JavaScript中,一切都是对象,函数也是对象,数组也是对象,但是数组是对象的子集,而对于函数来说,函数与对象之间有一种“鸡生蛋蛋生鸡”的关系。所有的对象都是由Object继承而来,而Object对象却是一个函数。对象都是由函数来创建的。
比如,在控制台中
输入 typeof Object 结果是"function",
输入 typeof Function 结果还是"function".
(好的,是不是更懵逼了,不急,现在先看一下怎么创建对象以及各种方法的孰优孰劣)
创建对象 0. 最基本的模式:var box=new Object(); //创建一个 Object 对象 box.name="Lee"; //创建一个 name 属性并赋值 box.age= 100; //创建一个 age 属性并赋值 box.run= function(){ //创建一个run()方法并返回值 return this.name + this.age; }; console.log(box.run()); //输出属性和方法的值
优缺点:
优点:简单
缺点:产生大量代码,封装性差
1.工厂模式:fuction creatPerson(name,age,job){ var o = new Object(); //创建对象 o.name = name; //添加属性 o.age = age; o.job = job; o.sayName = function(){ //添加方法 console.log(this.name); } return o; //返回对象引用 } var person1 = creatPerson("Nicholas",29,"engineer");//实例化 var person2 = creatPerson("Mike",28,"teacher");
优缺点:
优点:解决了创建多个相似对象的问题.
缺点:但却没有解决对象识别问题,即怎样知道一个对象的类型。也就是,因为根本无法搞清楚他们到底是哪个对象的实例(这个可以和下面的构造函数模式作对比)
2.构造函数模式function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ console.log(this.name); } } //或 function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = sayName;//注意这里不要写括号,要实现引用地址一致 } function sayName(){ console.log(this.name); }//这里是在外面写一个,但这种方法会有作用域问题,十分不推荐 var person1 = new Person("Nicholas",29,"Engineer");//一般这样实例化 var o = new Object;; Person.call(o,"Kkresten",25,"Nurse");//对象冒充法实例化
区别:
没有写出new Object,但是后台会自动 var obj = new Object,而this就相当于obj
没有 renturn 语句,在后台返回
规范
函数名和实例化构造名相同且大写(非强制,主要是为了和普通函数区分开来)
通过构造函数创建对象,必须使用 new 运算符
优缺点
优点: 创建自定义的构造类型意味着将来可以将它的实例标识为一种特定的类型,(可以用instanceof 来验证),即可识别(这里就可以和上面工厂模式作对比了,这也是比工厂模式更强的地方)
缺点:每个方法都要在每个实例上创建一遍,大可不必(当函数在内部时),
全局作用域中定义的函数只能被某个对象调用,这让全局作用域有点名不副实,而且,如果对象需要定义很多方法,那么就要定义很多个全局函数,于是,这个自定义的引用类型就丝毫没有封可言了(函数定义在外部时)。(所以函数在内部和外部都有缺点)
(好的,学到这里,你已经大体掌握了怎么创建一个对象,接下来将开始学习创建对象高大上的方法和概念)
3. 原型模式我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,(属性值是对象)而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。:prototype 通过 调用构造函数而创建的那个对象的原型对象。使用原型的好处可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息 添加到原型中。———高程
(好的,又回到了懵逼的状态了,不急,先通过例子和图来了解一下先)
function Person(){//构造函数 } Person.prototype.name = "Nicholas";//添加原型属性 Person.prototype.age = 29; Person.prototype.job = "software Engineer"; Person.prototype.sayName = function() {//添加原型方法 console.log(this.name); } var person1 = new Person(); //实例化 person1.sayName(); //“Nicholas” var person2 = new Person(); //实例化 person2.sayName();//"Nicholas"
如下图:
而我自己画了个图来加深一下认识(结合高程里的那段话)
(是不是有点懂了,接下来再逐个仔细分析)
1.对于[[Prototype]]
每一个对象都有一个这样的隐藏属性,它引用了创建这个对象的函数的prototype原型对象,我们来看一张图:
注意:函数也是对象,自然它也有__proto__。
在控制台中,我们发现:
即函数的__proto__是函数类型。(也就说函数的原型对象是函数,而函数也是对象,所以函数的原型还是对象)(这里听着有点绕,但是可以先跳过)
还要注意一个特例,如下图:
这里,一切对象继承自Object,而我们又知道Object.prototype是它的原型对象,是一个对象,但是这个对象的__proto__却为null,是否说明构建Object对象的函数没有原型对象,因为对象都是由函数创建的
(对于函数与对象的关系和涉及到的原型链的相关知识,还挺大挺深的,将多带带作为一个话题来讨论。如果这里有点看得晕,可以先只是知道prototype是什么就可以了)
注意: __proto__这个指针没有标准的方法访问,IE 浏览器在脚本访问[[Prototype]]会不能识别,火狐和谷歌浏览器及其他某些浏览器均能识别。虽然可以输出,但无法获取内部信息。([[Prototype]] 也可写为__proto__)虽然无法访问到,但是可以通过: Object.isPrototypeOf(person1)判断这个实例对象是否指向它的原型对象 ;而我们也知道Person.prototype就是Object类型,即一个原型对象
//承接上面的代码 Person.prototype.isPrototypeOf(person1);//true Person.prototype.isPrototypeOf(person2);//true
2.对于原型模式的执行流程:
①先检查这个对象自身有无这个属性;如果有,直接使用它。
②如果无法在对象自身找到需要的属性,就会继续访问对象的[[Prototype]]链,找到则直接使用,不再查找下去;如果一直找不到,最后就会返回undefined
3.可以通过 hasOwnProperty()方法检测属性是否存在实例中,也可以通过 in 来判断 实例或原型中是否存在属性;可以通过Object.keys()方法或Object.getOwnPropertyNames()来得到实例属性,具体见高程。
4.优缺点:每添加一个属性和方法就要敲一遍Person.prototype,而且视觉上说封装性不够好。当然优点就是解决了上面构造函数的问题。
5.更简单的原型模式
function Person(){ } Person.prototype = { //将 Person.prototype 设置为等于一个以对象字面量形式创建的新对象 name : "Nicholas", age: 29, job: "software Engineer", sayName : function() { console.log(this.name); } } //(但constructor属性不再指向Person了,而是指向Object构造函数) //但可以这样手动设置: function Person(){ } Person.prototype = { constructor : Person,//手动设置 name : "Nicholas", age: 29, job: "software Engineer", sayName : function() { console.log(this.name); } } //因为按上面的方式会导致它的[[Enumerable]]特性被设置为true,所以还可以像下面这样 function Person(){ } Person.prototype = { name : "Nicholas", age: 29, job: "software Engineer", sayName : function() { console.log(this.name); } } Object.definePrototype(Person.prototype,"constructor"),{ enumerable : false; value : Person } }
6.原型的动态性:
//承接上面的Person构造函数 var friend = new Person(); Person.prototype.sayhi = function(){ alert("hi"); }; friend.sayhi(); //"hi"没有问题,虽然是在实例之后添加的属性,但是根据原型模式的搜索机制,会找到原型中的这个方法,原因:实例与原型是松散连接的
//但是:如果是这样: function Person(){ } var friend = new Person(); Person.prototype = { name : "Nicholas", age: 29, job: "software Engineer", sayName : function() { console.log(this.name); } } friend.sayName();//Uncaught TypeError: friend.sayName is not a function,虽然有将重写的原型的指针指向Person原型对象,但是很实际上却如下图:
6.优缺点:
优点:解决了构造函数出现的问题(强大的类型识别)
缺点:共享了引用类型的值,这个就是很少有人多带带使用原型模式的原因。比如下面:
function Person(){ } Person.prototype = { constructor : Person, name : "Nicholas", age: 29, job: "software Engineer", friend:["Mike","Jeny"], sayName : function() { console.log(this.name); } } var person1 = new Person(); var person2 = new Person(); person1.friend.push("Van"); console.log(person1.friend);//"Mike,Jeny,Van" console.log(person2.friend);//"Mike,Jeny,Van"4.组合使用构造函数和原型模式
function Perosn(name,age,job){ this.name = name; this.age = age; this.job = job; this.friends = ["Shelby","Court"]; } Person.prototype = { constructor : Person, sayName : function(){ console.log(this.name); } } var person1 = new Person("Nicholas",29," Engineer");
优缺点:
优点:解决了引用类型实例共享的问题
缺点:封装性不够好
## 5.动态原型模式 ##
function Person(name,age,job){ //属性 this.name = name; this.age = age; this.job = job; //方法 if(typeof this.sayname != "function"){ Person.prototype.sayname = function(){ console.log(this.name);//只有在sayName方法不存在的情况下才会被添加到原型中 } } //这段代码在初次调用构造函数时才会执行,此后,原型已经初始化 var friend = new Person("Nicholas",29,"Engineer");
优缺点:
优点:既得到了封装,又实现了原型方法共享,并且属性都保持独立。可以说是非常完美了,其实说白了这种方法就是解决上面构造函数的方法不需要每次都创建一遍的问题。
缺点:不能使用对象字面量重写原型,会使之前定义的原型对象的方法失效。
(好了,学到这里,大概常用的创建对象的方法就已经掌握了,接下来还有两种不常用的方法可以了解一下)
6.寄生构造函数模式function Person(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ console.log(this.name); } return o; } var friend = new Person("Nicholas",29,"Software Engineer"); function SpecialArray(){ //创建数组 var values = new Array(); //用push方法初始化数组的值 values.push.apply(values,arguments); //添加方法 values.toPipedString = function(){ return this.join("|"); } //返回数组 return values; } var colors = new SpecialArray("red","blue","green"); console.log(colors.toPipedString()); //"red|blue|green"
优缺点:
构造函数返回的对象与在构造函数外部创建的对象没有什么不同,为此不能依赖instanceof操作符来确定对象的类型:
console.log(friend instanceof Person) // false
因此,可以使用其他模式的情况下不使用此类型
7.稳妥构造函数模式function Person(name,age,job){ //创建要返回的对象 var o = new Object(); //可以在这里定义私有变量和函数 //添加方法 o.sayName = function(){ console.log(name); } //返回对象 return o; } var friend = Person("Nicholas",29,"Software Engineer"); friend.sayName();
区别:
不引用this的对象
不使用new操作符
优点:安全
(好了,js对象的创建就大概有这几种方法,其实最常用的貌似还是构造函数的模式,但是原型相关的东西也是必须要掌握的)
最后,欢迎大家围观指正!
参考:《javascript高级程序设计》
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/95166.html
摘要:要用作原型的对象。函数对象可以创建普通对象,这个我们上面讲过了回顾一下这是一个自定义构造函数普通对象没法创建函数对象,凡是通过创建的对象都是函数对象,其他都是普通对象通常通过创建,可以通过来判断。 关于js的原型和原型链,有人觉得这是很头疼的一块知识点,其实不然,它很基础,不信,往下看要了解原型和原型链,我们得先从对象说起 创建对象 创建对象的三种方式: 对象直接量 通过对象直接量创建...
摘要:所以觉得把这个执行的详细过程整理一下,帮助更好的理解。类似的语法报错的如下图所示三预编译阶段代码块通过语法分析阶段之后,语法都正确的下回进入预编译阶段。另开出新文章详细分析,主要介绍执行阶段中的同步任务执行和异步任务执行机制事件循环。 一、概述 js是一种非常灵活的语言,理解js引擎的执行过程对于我们学习js是非常有必要的。看了很多这方便文章,大多数是讲的是事件循环(event loo...
摘要:所以觉得把这个执行的详细过程整理一下,帮助更好的理解。类似的语法报错的如下图所示三预编译阶段代码块通过语法分析阶段之后,语法都正确的下回进入预编译阶段。另开出新文章详细分析,主要介绍执行阶段中的同步任务执行和异步任务执行机制事件循环。 一、概述 js是一种非常灵活的语言,理解js引擎的执行过程对于我们学习js是非常有必要的。看了很多这方便文章,大多数是讲的是事件循环(event loo...
摘要:在基于原型的面向对象方式中,对象则是依靠构造函数和原型构造出来的。来看下面的例子优点与单纯使用构造函数不一样,原型对象中的方法不会在实例中重新创建一次,节约内存。 我们所熟知的面向对象语言如 C++、Java 都有类的的概念,类是实例的类型模板,比如Student表示学生这种类型,而不表示任何具体的某个学生,而实例就是根据这个类型创建的一个具体的对象,比如zhangsan、lisi,由...
摘要:对象创建的三种方式字面量创建方式系统内置构造函数方式自定义构造函数构造函数原型实例之间的关系实例是由构造函数实例化创建的,每个函数在被创建的时候,都会默认有一个对象。 JS 对象创建的三种方式 //字面量创建方式 var person= { name:jack } //系统内置构造函数方式 var person= new Object(); person.name = jack; ...
摘要:执行上下文作用域链和内部机制一执行上下文执行上下文是代码的执行环境,它包括的值变量对象和函数。创建作用域链一旦可变对象创建完,引擎就开始初始化作用域链。 执行上下文、作用域链和JS内部机制(Execution context, Scope chain and JavaScript internals) 一、执行上下文 执行上下文(Execution context EC)是js代码的执...
阅读 669·2021-11-18 10:02
阅读 2208·2021-11-15 18:13
阅读 3084·2021-11-15 11:38
阅读 2880·2021-09-22 15:55
阅读 3628·2021-08-09 13:43
阅读 2414·2021-07-25 14:19
阅读 2433·2019-08-30 14:15
阅读 3424·2019-08-30 14:15