clas 保存数据 rip .com lac 根据 意思 console
了解 JavaScript 原型链之前首先肯定要知道什么是原型。
JavaScript 中,原型是一个对象,通过原型可以实现属性的继承。既然原型是一个对象,那么任何一个对象都可以称为原型吗?是, 记住它。什么对象有原型?任何对象 (undefined,null,boolean,number,string 是主类型,不是对象) 默认情况下都有一个原型,但是原型也是一个对象,所以对象的原型也有原型,记住,下面有用。
js 中的对象中都包含一个指向原型对象的指针,但是是不能被直接访问的,为了方便的看到原型,chrome 和 Firefox 提供了__proto__属性,但这只是浏览器提供的,换个浏览器就有可能不提供这个属性,ECMA 引入了标准的原型访问器 Object.getPrototype(object)。这里又说到了 ECMA 解释一下和 JavaScript 的区别。ECMAScript 是一种标准,而 JavaScript 可以理解成一种集合,各个浏览器基于这个标准实现各自的 JavaScript(不同的浏览器对于同一段代码可能执行不通的结果),JavaScript 其实是由三个不同的部分组成:核心 (ECMAScript)、文档对象模型 (DOM)、浏览器对象模型 (BOM)。不多撤了。
查看一个对象的原型,在浏览器控制台上输入
- function Human(name) {
- this.name = name;
- this.getName = function() {
- console.log("i am " + this.name);
- };
- }
- var h = new Human("fzk");
- h;
会看到__proto__属性,可以看到 h 有一个原型属性,这个原型有一个 constructor 属性,上面说了,原型也是一个对象,所以也有一个__proto__原型对象。constructor 是一个函数类型对象,是对象实例化时的函数。对象实例化(new)到底是一个什么过程?其实就是干了四件事
- new Human("fzk") = {
- var obj = {};
- obj.__proto__ = Human.prototype;
- var result = Human.call(obj,"fzk");
- return typeof result === 'obj'? result : obj;
- }
(1)创建一个空对象 obj;(2)把 obj 的__proto__ 指向 Human 的原型对象 prototype,此时便建立了 obj 对象的原型链:obj->Human.prototype->Object.prototype->null(3)在 obj 对象的执行环境调用 Human 函数并传递参数 "fzk"。 相当于 var result = obj.Human("fzk")。当这句执行完之后,obj 便产生了属性 name 并赋值为 "fzk"。(4)考察第 3 步返回的返回值,如果无返回值或者返回一个非对象值,则将 obj 返回作为新对象;否则会将返回值作为新对象返回。
上面再创建的过程中,会把构造函数指向原型的 constructor。
了解上面的过程,下面的代码就很好理解了。
- h.__proto__ === Human.prototypeHuman.prototype.__proto__ === Object.prototypePerson.prototype.constructor === Person
这里又看到了 prototype,这里说明一下 prototype 和__proto__1. 所有的对象都有一个__proto__属性 2. 对于函数对象 (Function,由 function(){} 结构定义出来)都另外含有一个 prototype 属性。
JavaScript 中,万物皆对象。Function 函数对象也是一个对象,它也有原型。这里 Human(Function 对象) 的作用域链是什么样的
可以看出: Human->Function.prototype->Object.prototype->null
总之最终都会指向 Object。上面说到过函数对象才会有 prototype 属性,为什么这里(不止这里,上面也用到了)Object 也有 prototype 属性。
- typeof Object
- > "function"
控制台上敲一下上面的代码,会发现 Object 是一个 function。
最终看一下:
解释了这么多,最终看一下官方定义:ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。刚开始看是不是超级迷糊?是不是怀疑人生?但是通过例子仔细理解一下,果然 ECMAScript 说出来的话就是言简意赅。JavaScript 也就是利用原型链实现了继承。我们了解原型,肯定是想更好的使用它,在什么情况下会使用到原型?我们首先要知道原型有哪些特点,1. 根据官方定义,实现继承肯定得用这个了。但是基本上不用我们自己来实现,除非你要手动去修改对象的原型指针。2. 原型中定义的属性,是可以被所有实例共享的(为什么?因为实例化的时候已经将函数对象的 prototype 赋值给了实例对象的__proto__,对象会根据这个原型链查找属性最终在函数对象的 prototype 属性定义的属性都会通过原型链被实例找到,但是注意,这里能找到是因为通过原型链查找而不是实例对象具有这个属性,是不是想到了多个实例化对象可以共用一个内存空间,实际情况也确实是这样。这种情况下你在仔细考虑考虑,new 的时候是不是其实相当于实例对象继承了原函数对象的属性和方法,JavaScript 里实例化所要表达的意思其实是继承。撤多了,下面会继续说这个事),可以减少内存使用。而且还可以动态的添加方法。
经过上面两点分析也就知道了原型的使用情况,1. 继承、2. 多个实例对象共用一块内存、3. 动态给所有实例添加方法。
继续撤上面括号中说的 JavaScript 中 new 的意义。JavaScript 说万物皆对象,为什么还要通过 new 来创建对象,画蛇添足?仔细研究一下,JavaScript 说,A instanceof B === true,那么 A 就是 B 的实例,而这个 instanceof 是怎么判断的?就像下面这样:
- varL = A.__proto__;
- varR = B.prototype;
- if(L === R)
- return true;
是不是感觉好巧,就是这么巧,在实例化 h 的时候,就是将 Human 的 prototype 给了 h 的__proto__。h 又继承了原函数对象的属性,因此,new 的作用是为了让新做出来的对象具有原对象的属性和方法。没理解?就是让 {}.__proto__ = Human.prototype,这时{} 是一个对象并且也是 Human 的实例,但是现在 {} 什么属性也没有。通过 new 出来的对象 h 就具有了 Human 的属性和方法,并且 h 还是 Human 的实例。所以说,new 存在 JavaScript 的意义不是穿件一个对象,而是为了实现 JavaScript 的继承。
原型链理解的差不多了,看一下下面的输出是否全能理解
- function Animal(name){
- this.name = name;
- }
- Animal.color = "black";
- Animal.prototype.say =function(){
- console.log("I'm " +this.name);
- };
- varcat =newAnimal("cat");
- console.log(
- cat.name, //catcat.height//undefined
- );
- cat.say(); //I'm cat
- console.log(
- Animal.name, //AnimalAnimal.color//back
- );
- Animal.say(); //Animal.say is not a function
看完原型链,接下来看一下作用域链。JavaScript 的作用域是非块级作用域,是函数级作用域。什么意思?也就是说,if、for、while.... 后面的大括号并不能隔离作用域,这些大括号里定义的属性与在大括号外面的定义的属性是一个性质的。看一下经典的例子
- var data = [];
- for (var i = 0; i < 3; i++) {
- data[i] = function() {
- console.log(i);
- } // data[0](); 三次分别打印 0、1、2
- }
- data[0](); // 3
- data[1](); // 3
- data[2](); // 3
理解了非块级作用域,这个也就很好理解了。在这里,i 就相当于全局作用域的变量,data 数组里的数据就是指向打印 i 的函数指针,函数的功能是要打印 i,循环结束后,i 已经变成了 3,这时在打印 i 当然打印出来的是 3。这里又引出了一个全局作用域的概念,与之对应的是局部作用域。全局作用域是指对象可以在代码的任何地方都可以访问,最外层定义的变量、没有 var 修饰的变量、window 对象的属性 都是全局作用域的变量。局部作用域中定义变量只有在函数内能使用。
说到作用域,肯定就要说闭包。什么是闭包,所谓 "闭包",指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。我的理解就是一个函数内可以使用另一个函数中定义的属性,这些属性就会被闭包。
- function count() {
- var x = 0;
- return {
- increase: function increase() {
- return++x;
- },
- decrease: function decrease() {
- return--x;
- }
- };
- }
- c = count();
- c.increase();
闭包和作用域链就有关系了。每个执行环境都有用来存放变量函数参数等信息的 VO,当从当前执行环境找不到变量的时候会向上继续查找。这样就形成了一个作用域链,这个闭包的属性就是那些从当前环境没找到这个变量,从上级的某层中找到这个变量,但是这个变量还不是全局作用域的变量。正常的没有闭包的函数在函数执行完后,所有的属性变量等会被销毁,但是闭包中的变量在函数执行完后是不会被销毁的。所以用闭包就需要注意内存泄漏的问题。
闭包应用的场景:1、保护函数内的变量安全。x 只能通过 count() 访问到,此外,再也没有办法能访问到了。2、保存数据。这个应用的地方就多了,计数器、数据收集。。。
为什么写了这么一大骗文章,就是想说最后一个事情,JavaScript 在既有继承又有闭包的情况下查找变量时是怎么找的。首先,会通过作用域链,找到这个属性是属于哪个对象的,然后在通过作用域链向上查找。
参考:
http://www.jb51.net/article/78709.htm
http://www.cnblogs.com/wilber2013/p/4909459.html
http://www.cnblogs.com/wilber2013/p/4924309.html
js 原型 作用域
来源: http://www.bubuko.com/infodetail-2085088.html