js原型 作用域
了解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.prototype
Human.prototype.__proto__ === Object.prototype
Person.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 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。
刚开始看是不是超级迷糊?是不是怀疑人生?但是通过例子仔细理解一下,这段话--言简意赅。
我们了解原型,肯定是想更好的使用它,在什么情况下会使用到原型?我们首先要知道原型有哪些特点,
1.根据官方定义,实现继承肯定得用这个了。但是基本上不用我们自己来实现,除非你要手动去修改对象的原型指针。
2.原型中定义的属性,是可以被所有实例共享的(为什么?因为实例化的时候已经将函数对象的prototype赋值给了实例对象的__proto__,对象会根据这个原型链查找属性最终在函数对象的prototype属性定义的属性都会通过原型链被实例找到,但是注意,这里能找到是因为通过原型链查找而不是实例对象具有这个属性,是不是想到了多个实例化对象可以共用一个内存空间,实际情况也确实是这样。这种情况下你在仔细考虑考虑,new的时候是不是其实相当于实例对象继承了原函数对象的属性和方法,JavaScript里实例化所要表达的意思其实是继承。撤多了,下面会继续说这个事),可以减少内存使用。而且还可以动态的添加方法。
经过上面两点分析也就知道了原型的使用情况,1.继承、2.多个实例对象共用一块内存、3.动态给所有实例添加方法。
继续撤上面括号中说的JavaScript中new的意义。JavaScript说万物皆对象,为什么还要通过new来创建对象,画蛇添足?仔细研究一下,JavaScript说,A instanceof B === true,那么A就是B的实例,而这个instanceof是怎么判断的?就像下面这样:
var L = A.__proto__;
var R = 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);
};
var cat = new Animal("cat"); console.log(
cat.name, //cat
cat.height //undefined
);
cat.say(); //I'm cat console.log(
Animal.name, //Animal
Animal.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]();//
data[1]();//
data[2]();//
理解了非块级作用域,这个也就很好理解了。在这里,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原型 作用域的更多相关文章
- 【学习笔记】深入理解js原型和闭包(18)——补充:上下文环境和作用域的关系
本系列用了大量的篇幅讲解了上下文环境和作用域,有些人反映这两个是一回儿事.本文就用一个小例子来说明一下,作用域和上下文环境绝对不是一回事儿. 再说明之前,咱们先用简单的语言来概括一下这两个的区别. 0 ...
- 【学习笔记】深入理解js原型和闭包(14)——从【自由变量】到【作用域链】
先解释一下什么是“自由变量”. 在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量.如下图 如上程序中,在调用fn()函数时,函数体中第6 ...
- 【学习笔记】深入理解js原型和闭包(13)——【作用域】和【上下文环境】
上文简单介绍了作用域,本文把作用域和上下文环境结合起来说一下,会理解的更深一些. 如上图,我们在上文中已经介绍了,除了全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了.而不 ...
- 【学习笔记】深入理解js原型和闭包(12)——简介【作用域】
提到作用域,有一句话大家(有js开发经验者)可能比较熟悉:“javascript没有块级作用域”.所谓“块”,就是大括号“{}”中间的语句.例如if语句: 再比如for语句: 所以,我们在编写代码的时 ...
- 【09-23】js原型继承学习笔记
js原型继承学习笔记 function funcA(){ this.a="prototype a"; } var b=new funcA(); b.a="object a ...
- js词法作用域
作用域链和原型链是JS中比较重要的2个概念, JS的是函数作用域,与C之类语言的块级作用域不同 JS的作用域还是词法作用域,或者叫静态作用域,作用域链是在语法解析时就完成的,而不是在执行时创建. 例子 ...
- 【学习笔记】深入理解js原型和闭包系列学习笔记——精华
深入理解js原型和闭包笔记: 1.“一切皆是对象”,对象是属性的集合. 丨 函数也是对象,但是使用typeof时为什么函数返回function而 丨 不是object呢,js为何要对函数做这样的区分 ...
- 【学习笔记】深入理解js原型和闭包(15)——闭包
前面提到的上下文环境和作用域的知识,除了了解这些知识之外,还是理解闭包的基础. 至于“闭包”这个词的概念的文字描述,确实不好解释,我看过很多遍,但是现在还是记不住. 但是你只需要知道应用的两种情况即可 ...
- 【学习笔记】深入理解js原型和闭包(11)——执行上下文栈
继续上文的内容. 执行全局代码时,会产生一个执行上下文环境,每次调用函数都又会产生执行上下文环境.当函数调用完成时,这个上下文环境以及其中的数据都会被消除,再重新回到全局上下文环境.处于活动状态的执行 ...
随机推荐
- php 路由实现
因为有小的业务逻辑比较复杂,orm用起来很麻烦,但是多入口又不好控制,每个页面都去包含,多了就迷了,所以就只写了个路由,加了个防注入 封装增删改查好无聊,直接封装业务逻辑,为业务而开发,业务逻辑里是面 ...
- 什么是 Web?
Web这个词刚开始显得有些泛泛,似乎“冲浪”.“网上存在”以及“主页”等等都和它拉上了一些关系. 甚至还有一种“Internet 综合症”的说法,对许多人狂热的上网行为提出了质疑.我们在这里有必要作一 ...
- 网络协议之ts---ts 流分析详解
名词缩写翻译: es:elements stream 基本流 pes:packet elements stream 打包流 pts:presentation time stamp 显示时间戳 dts: ...
- 补图BFS(hdu 5876)
题目大意: 给出一个图和起点S,求补图中S到其他点的最短距离. http://acm.hdu.edu.cn/showproblem.php?pid=5876 我自己的垃圾做法: 用线段树来维护dijk ...
- Android上传图片(PHP服务器)
原理 Android客户端模拟一个HTTP的Post请求到服务器端,服务器端接收相应的Post请求后,返回响应信息给给客户端. PHP服务器 <?php move_uploaded_file($ ...
- java-通过 HashMap、HashSet 的源码分析其 Hash 存储机制
通过 HashMap.HashSet 的源码分析其 Hash 存储机制 集合和引用 就像引用类型的数组一样,当我们把 Java 对象放入数组之时,并非真正的把 Java 对象放入数组中.仅仅是把对象的 ...
- PostgreSql Partition + Hibernate Insert
与Oracle不同.PostgreSQL须要手动控制分区规则触发器. 步骤一:创建分区 CREATE TABLE table_partition_1( CHECK partition_column c ...
- Eclipse虚拟内存不足【Eclipse中虚拟内存设置】
Eclipse最近在做J2EE项目中 发现老是出现虚拟内存不足的提示 前2天去加了根内存 问题同样存在 为了让我在写代码时 不在出现那讨厌的内存不足的提示 也为了 不让那破机器再卡住 今天找到了解决方 ...
- 爬虫实战【13】获取自己的动态代理ip池
在爬取一些比较友好的网站时,仍然有可能因为单位时间内访问次数过多,使服务器认定为机器访问,导致访问失败或者被封.如果我们使用不同的ip来访问网站的话,就可以绕过服务器的重复验证,使服务器以为使不同的人 ...
- Scrapy命令和备注
Scrapy命令和备注 1.创建一个新项目(命令行) project是项目名 scrapy startproject <project_name> 2.调试项目(pycharm) 在pyc ...