Js的国(转载)
-
Upload
leo-hui -
Category
Technology
-
view
508 -
download
1
Transcript of Js的国(转载)
JS 的国@李松峰
( 2013年 3月 7日, 360学院分享 PPT)
人类社会的四种关系
• 姻缘• 亲缘• 地缘• 业缘
:妻子、丈夫;:母子、父子;:老乡、邻居;:同事、同学。
JavaScript社会的四种关系
• 姻缘• 亲缘• 地缘• 业缘
: Function()、 Object()、 prototype
: this、 new、 __proto__、 constructor
:作用域(链)、原型(链)、闭包:库、框架、模块
JavaScript社 会 的 四 种 关 系 : 姻缘
var Person = function(living, age, gender) { this.living = living; this.age = age; this.gender = gender; };
Person.prototype.getGender = function(){ return this.gender;};
var child = new Person(true, 25, 'male');
/*Person.prototype = {constructor : Person,…}*/
JavaScript社 会 的 四 种 关 系 : 亲缘
var Person = function(living, age, gender) { this.living = living; this.age = age; this.gender = gender; };
Person.prototype.getGender = function(){ return this.gender;};
var child = new Person(true, 25, 'male');
console.log (child.getGender()); // maleconsole.log (child.__proto__ === Person.prototype);//trueconsole.log (child.constructor === Person);//true
JavaScript社 会 的 四 种 关 系 : 地缘
var x = 10;
var foo = function() { var y = 20; var bar = function() { var z = 30; console.log(z + y + x); }();}()
foo(); // 60
z 在当前函数 bar 的作用域中y 在上层函数 foo 的作用域中x 在全局作用域中
JavaScript社 会 的 四 种 关 系 : 地缘
var Person = function(){ this.bar = 'bar';}Person.prototype.foo = 'foo';
var Chef = function(){ this.goo = 'goo';}
Chef.prototype = new Person();Chef.prototype.constructor = Chef;
var cody = new Chef();
console.log(cody.goo); // 'goo' ,在实例 cody 本身console.log(cody.bar); // 'bar' ,在 Chef.prototype 中console.log(cody.foo); // 'foo' ,在 Person.prototype 中
Chapter 1 - JavaScript ObjectsChapter 2 - Working with Objects and PropertiesChapter 3 - Object()Chapter 4 - Function()Chapter 5 - The Head/Global ObjectChapter 6 - The this KeywordChapter 7 - Scope & ClosuresChapter 8 - Function Prototype PropertyChapter 9 - Array()Chapter 10 - String()Chapter 11 - Number()Chapter 12 - Boolean()Chapter 13 - NullChapter 14 - UndefinedChapter 15 - Math Function
Table of contents
顶级对象
顶级对象? JavaScript 代码本身必须包含在一个对象中。 在浏览器编程中, JavaScript 代码都包含在 window 对象中。 这个 window 对象就是“顶级对象”,也称为“全局对象”。 顶级对象封装用户编写的和 JavaScript 原生的代码。 用户编写的代码必须放到顶级对象环境下才可以执行。
var myStringVar = 'myString';var myFunctionVar = function() {};myString = 'myString';myFunction = function() {};
console.log('myStringVar' in window); // returns trueconsole.log('myFunctionVar' in window); // return trueconsole.log('myString' in window); // returns trueconsole.log('myFunction' in window); // return true
JavaScript 代码都是在顶级对象的环境下编写的。
理解顶级对象
全局函数包含在顶级对象中JavaScript 原生提供如下函数,作为顶级对象的方法:
decodeURI() decodeURIComponent() encodeURI() encodeURIComponent() eval() isFinite() isNaN() parseFloat() parseInt()
在浏览器中,可以调用 window.parseInt('500') 。
全局属性(变量)对所有作用域开放
“ 全局属性”或“全局变量”指包含在顶级对象中的值。 称之为“全局 XX” ,是因为它们位于全局作用域中,任何代码都可以(通过作用域链)访问它们:
var foo = 'bar'; /* foo 是顶级对象的属性(或全局属性) */var myApp = function() { // 函数会创建一个作用域 var run = function() { // 输出 bar , foo 的值是通过作用域链在顶级对象中找到的 console.log(foo); }();}myApp();
/*foo 现在位于 myFunction() 函数的作用域中 */var myFunction = function() {var foo = ‘bar’}; var myApp = function() { console.log(foo); /* 输出 undefined ,全局作用域中没有定义}myApp();
任何作用域都可以访问全局作用域中定义的变量和函数。 这也是称它们为“全局属性”或“全局变量”的原因。
非全局属性(变量)受作用域限制
引用顶级对象
引用顶级对象有两种方式: 直接使用顶级对象的名字; 在全局作用域中使用 this 关键字。
var foo = 'bar';
windowRef1 = window;windowRef2 = this;
console.log(windowRef1, windowRef2); // windowconsole.log(windowRef1.foo, windowRef2.foo); // 'bar', 'bar'
无须明确引用顶级对象
顶级对象是隐含的或默认的对象,不必明确引用。 window.alert() 和 alert() 在浏览器中是相同的语句。 省略顶级对象, JavaScript 会为你补上它。 顶级对象是作用域链查找的终点,因此会永远存在:
var foo = { //window 是隐含的,相当于 window.foo fooMethod: function() { alert('foo' + 'bar'); // window 是隐含的 window.alert('foo' + 'bar'); /* 明确引用,结果相同 }}
this关键字
只要创建函数, JavaScript 就会为它创建一个 this 关键字。 可以在函数作用域中访问 this ,它引用函数所在的对象。 换句话说, this 引用的是把当前函数作为方法的对象:var cody = { living : true, age : 23, gender : 'male', getGender : function() {return cody.gender;}};console.log(cody.getGender()); // 输出 'male'
在 cody 对象的 getGender 方法中,可以用 this 代替 cody :
this的基本概念
var cody = { living : true, age : 23, gender : 'male', getGender : function() {return this.gender;}};console.log(cody.getGender()); // 输出 'male'
this.gender 中的 this 引用 cody ,因为 getGender 属于cody 。 函数作用域中的 this 引用拥有它的对象,而非函数自身。(使用 new 关键字和 call() 或 apply() 的情况除外)。
this的基本概念(续)
this 的值在函数被调用时根据相应的执行环境确定:var foo = 'foo';var myObject = {foo: 'I am myObject.foo'};var sayFoo = function() { console.log(this['foo']);};// 为 myObject 定义 sayFoo 属性,并让它引用 sayFoo 函数myObject.sayFoo = sayFoo;myObject.sayFoo(); // 输出 'I am myObject.foo'sayFoo(); // 输出 'foo'
通过 myObject.sayFoo 调用 sayFoo , this 引用的是myObject 。 在全局作用域中调用 sayFoo , this 引用的就是 window 对象。
确定 this的值
在嵌套的函数中, this 引用顶级对象:var myObject = { func1: function() { console.log(this); //myObject var func2 = function() { console.log(this) //window(从这里开始……) var func3 = function() { console.log(this); //window }(); }(); }}myObject.func1();
* ECMAScript 5 纠正了这个错误。
确定 this的值(续一)
在嵌套的函数中, this 引用顶级对象:var foo = { func1:function(bar) { bar(); //嵌套函数中的 this引用 window console.log(this); // 这个 this 引用 foo }}foo.func1(function(){ console.log(this)});
* ECMAScript 5 纠正了这个错误。
确定 this的值(续二)
var myObject = { myProperty: 'I can see the light', myMethod : function(){ // 保存 this 的引用( myObject ) var that = this; var helperFunction = function() { // 因为 that=this ,所以输出 'I can see the light' console.log(that.myProperty); console.log(this); // 输出 window }(); }}myObject.myMethod();
利用作用域链保存 this的值
正常情况下, this 的值由函数被调用时的执行环境确定(使用 new 关键字时除外)。 使用 call() 或 apply() 可以控制函数被调用时 this 引用哪个对象(或者说控制用哪个对象来调用函数)。 换句话说,它们可以覆盖 JavaScript 默认确定 this 值的行为:var myObject = {};var myFunction = function(param1, param2) { this.foo = param1; this.bar = param2; console.log(this);};myFunction.call(myObject, 'foo', 'bar'); console.log(myObject) //Object {foo = 'foo', bar = 'bar'}
使用 call()和 apply()控制 this的值
使用 apply() 的区别就是传递给函数的参数要使用数组:
var myObject = {};var myFunction = function(param1, param2) { this.foo = param1; this.bar = param2; console.log(this) //Object {foo = 'foo', bar = 'bar'}};myFunction.apply(myObject, ['foo', 'bar‘]); console.log(myObject) //Object {foo = 'foo', bar = 'bar'}
总之,就是你可以无视(覆盖 / 重写) JavaScript 决定在函数作用域中 this 引用什么对象的默认行为。
使用 call()和 apply()控制 this的值(续)
通过 new 调用函数,函数中的 this 引用即将创建的实例。
在构造函数中,可以通过 this 为要创建的实例添加属性:var Person = function(name) { this.name = name || 'john doe'; // this 引用即将创建的实例}var cody = new Person('Cody Lindley');console.log(cody.name); // 'Cody Lindley'
在自定义构造函数中使用 this关键字
如果不通过 new 关键字调用 Person 函数呢?
那 this 仍然会引用调用 Person 时所在的执行环境:
var Person = function(name) { this.name = name || 'john doe'; // this 引用即将创建的实例}var cody = Person('Cody Lindley'); //未使用 new ,返回undefinedconsole.log(window.name); // 'Cody Lindley'
在自定义构造函数中使用 this关键字(续)
为构造函数的 prototype 属性增加方法时,方法中的 this 引用将来用构造函数创建的实例:var Person = function(x){ if(x){this.fullName = x};};
Person.prototype.whatIsMyFullName = function(){ return this.fullName; // this 引用 Person() 的实例}var cody = new Person('cody lindley');var lisa = new Person('lisa lindley');
// 调用继承的 whatIsMyFullName 方法,该方法通过 this 引用相应的实例console.log(cody.whatIsMyFullName(),lisa.whatIsMyFullName());
在原型对象的方法中使用 this关键字
如果实例没有 fullName 属性,还可以通过原型链查找:
var Person = function(x){ if(x){this.fullName = x};};Person.prototype.whatIsMyFullName = function(){ return this.fullName; // this 引用 Person() 的实例}
var john = new Person(); //未传入参数,因此实例中没有 fullName 属性Object.prototype.fullName = 'John Doe';//Person.prototype.fullName = 'John Doe';
console.log(john.whatIsMyFullName()); //通过原型链找到 'John Doe'
在原型对象的方法中使用 this关键字(续)
作用域和闭包
JavaScript 中的作用域就是代码执行环境,分三种:
全局作用域;
局部作用域;
eval() 作用域。
在函数中使用 var 定义的变量位于局部作用域,只对函数中的其他表达式(包括嵌套函数中的表达式)可见。
在全局作用域中定义的函数可以从任何地方访问,因为全局作用域是作用域链中的最高层次。
作用域
var foo = 0; // 全局作用域console.log(foo); // 0
var myFunction = function() { var foo = 1; // 函数的局部作用域 console.log(foo); // 1 var myNestedFunction = function() { var foo = 2; // 嵌套函数的局部作用域 console.log(foo); // 2 }();}();
eval(‘var foo = 3; console.log(foo);’); // 3 , eval() 作用域
作用域(续)
JavaScript 中的逻辑语句(如 if )和循环语句(如 for )不创建作用域 .
如下代码所示,随着程序执行,变量 foo 的值会不断改变:var foo = 1; // foo = 1if (true) { foo = 2; // foo = 2 for(var i = 3; i <= 5; i++) { foo = i; // foo = 3,4,5 console.log(foo); //3,4,5 }}
在函数中使用 var声明局部变量,可以避免命名冲突。
没有块级作用域
JavaScript 会沿作用域链逐步查找不同层次的作用域:var sayHiText = 'howdy';var func1 = function() { var func2 = function() { console.log(sayHiText); // 在全局作用域中找到 sayHiText }();}();
为查找 sayHiText 的值, JavaScript 分别在 func2 、 func1 和全局作用域中查找,最后在全局作用域中找到了它的值。
作用域链
作用域链的概念很重要,因此我们再看一个例子:var x = 10;
var foo = function() { var y = 20; var bar = function() { var z = 30; // z 在当前作用域中, y 和 z 在上层作用域中 console.log(z + y + x); }();}
foo(); // 60
变量 z 在 bar 的局部作用域中, console.log 的执行环境也是 bar 。变量 y 在 foo 函数的作用域内,而变量 x 在全局作用域内。通过作用域链,可以在 bar 函数内访问所有这些变量。
作用域链(续一)
var x = false;var foo = function() { var x = false; bar = function() { var x = true; console.log(x); // 局部作用域中的 x 会返回 }();}foo(); // true
调用 console.log 的作用域里有一个变量 x ,这个变量会遮挡或隐藏作用域链上层(或上层作用域)中定义的同名变量。
作用域链查找返回第一个值
JavaScript 中的作用域由函数定义时而非调用时的位置决定。
代码写在哪儿,作用域就在哪儿,这就是词法作用域!
换句话说,在调用函数之前,作用域链就已经确定了。
正因为如此,我们才可以通过嵌套函数来创建闭包。
比如,可以把被一个函数嵌套的函数返回给全局作用域,在全局作用域中调用这个函数仍然可以通过作用域链访问其父函数的作用域。
词法作用域
var parentFunction = function() { var foo = 'foo'; return function() { //返回匿名函数 console.log(foo); // 'foo' }}// nestedFunction 引用 parentFunction返回的匿名函数var nestedFunction = parentFunction();nestedFunction(); // 'foo'
返回的函数构成闭包,能通过作用域链访问变量 foo 。
作用域链不会因为函数被传来传去而改变。
词法作用域导致闭包
理解了作用域链和变量查找就能理解闭包。再看一个例子:var countUpFromZero = function() { var count = 0; return function() { // 返回嵌套的子函数 return ++count; // 访问作用域链中的 count };}(); //立即调用,返回嵌套函数
console.log(countUpFromZero()); // logs 1console.log(countUpFromZero()); // logs 2console.log(countUpFromZero()); // logs 3
词法作用域导致闭包(续)
原型和原型链
所有函数都由 Function() 构造函数创建,无论是直接使用Function() ,还是使用字面量形式。任何新创建的函数实例都会被赋予一个 prototype 属性,引用一个空对象。
虽然只有在把函数当作构造函数来创建实例时才会用到这个 prototype 属性,但这并不改变 Function() 构造函数会为每个函数实例赋予 prototype 属性的事实:var myFunction = function() {};console.log(myFunction.prototype); // object{}console.log(typeof myFunction.prototype); // 'object'
原型对象是所有 Function()实例的原基
默认情况下,每个函数的 prototype 属性中保存的只是一个的空对象,我们叫它“原型”。
这个对象是因为 JavaScript显式或隐式地调用了 Function()
构造函数才赋予每个函数实例的。如果你想手工实现这个过程,可以试试以下代码:var myFunction = function() {};
myFunction.prototype = {}; console.log(myFunction.prototype);
默认的 prototype属性是 Object()的实例
var myArray = new Array('foo', 'bar');console.log(myArray.join()); //'foo, bar'
数组实例 myArray 本身没有 join() 方法,但它能够访问join() 方法。这个方法是在哪里定义的?
是在 Array.prototype 属性中定义的。
按照实例的属性查找规则,在数组实例中找不到 join() ,就会继续在创建实例的构造函数 Array 的 prototype 属性中查找。
原型链的概念
JavaScript 会为每个 Function() 的实例创建一个原型对象,保存在该实例的 prototype 属性中。 每个使用 new 关键字和构造函数创建的实例都有一个链接指向构造函数的 prototype 属性。每个实例(包括原型对象本身)都有指向自己原型对象的链接,由此构成了可供众多实例共享(或继承)方法和属性的原型链。
原型链的概念(续一)
构造函数创建的实例都有指向其 prototype 属性的链接。
这个链接是一个隐含的属性,在 Firefox
2+ 、 Safari 、 Chrome 和 Android 中它是: __proto__ 。
每当有构造函数被调用, JavaScript 就会在后台为实例和构造函数的 prototype 属性创建这种链接。
这种链接关系是原型链之所以成为“链”的核心所在。
原型链的概念(续二)
Array.prototype.foo = 'foo';var myArray = new Array();console.log(myArray.__proto__.foo); // foo,
ECMA 不允许访问 __proto__ ,所以要使用 constructor 属性:
Array.prototype.foo = 'foo';var myArray = new Array();console.log(myArray.constructor.prototype.foo); // foo
原型链的概念(续三)
既然 prototype 属性是一个对象,那么原型链或原型查找的终点就是 Object.prototype 。
var myArray = [];console.log(myArray.foo) // undefined
没有定义 myArray.foo ,没有定义 Array.prototype.foo ,也没有定义 Object.prototype.foo ,所以返回 undefined 。
原型链的终点是 Object.prototype
与作用域链一样,原型链也返回匹配的第一个属性:Object.prototype.foo = 'object-foo';Array.prototype.foo = 'array-foo';var myArray = [];console.log(myArray.foo); // 'array-foo'myArray.foo = 'bar';console.log(myArray.foo) // 'bar'
Array.prototype 中的属性遮挡或隐藏了 Object.prototype 中的同名属性。
只要一找到属性,查找就结束。
原型链返回匹配的第一个值
var Foo = function Foo(){};Foo.prototype = {}; // 用空对象替换 prototype 属性var FooInstance = new Foo();console.log(FooInstance.constructor === Foo); // falseconsole.log(FooInstance.constructor); // Object()而非 Foo()
如果要替换 prototype 属性(比如在实现继承时),应该在替换之后再把新原型对象的 constructor 属性设置为原来的构造函数:Foo.prototype = {constructor:Foo};
替换 prototype对象会删除 constructor属性
原型链支持动态查找,无论什么时候对原型链进行了添加或修改,实例总能取得最新的值:
var Foo = function Foo(){};
Foo.prototype.x = 1;var FooInstance = new Foo();console.log(FooInstance.x); // 1
Foo.prototype.x = 2;console.log(FooInstance.x); // 2
从原型继承属性的实例总会取得最新的值
任何时候都可以替换 prototype 属性,然后之前创建的实例都会自动继承新原型对象的属性,对吗?
错!替换之前创建的实例与之前的原型对象已经“绑定”了,把 prototype 替换为新对象不会改变这种绑定关系:var Foo = function Foo(){};Foo.prototype.x = 1;var FooInstance = new Foo();console.log(FooInstance.x); // 1
Foo.prototype = {x:2};console.log(FooInstance.x); // 1var NewFooInstance = new Foo();console.log(NewFooInstance.x); // 2
替换 prototype属性不影响之前的实例
在 JavaScript 中,如果想让一个对象继承另一个对象,只要把被继承对象的实例作为构造函数的 prototype 属性,然后用该构造函数创建实例就可以了:var Person = function(){this.bar = 'bar'};Person.prototype.foo = 'foo';var Chef = function(){this.goo = 'goo'};Chef.prototype = new Person();Chef.prototype.constructor = Chef; //别忘了重设 constructorvar cody = new Chef();console.log(cody.foo); // logs 'foo'console.log(cody.goo); // logs 'goo'console.log(cody.bar); // logs 'bar'
实际上是利用了与原生对象一起提供的原生继承机制。
原型链接的本意就是用于实现继承
想翻译?1、访问ituring.cn2、点“图书”3、点“开放出版”4、点“诚招译者”(或直接扫下方二维码)
你会看到所有要寻找译者的计算机类、科普类、 IT人文类图书,选择你感兴趣的书,联系:
@朱小编(英语)或@图灵乐馨(日语)