二、this和对象原型
1、关于this
(1)、this到底是什么?
this是在运行时进行绑定的,并不是在编写是绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数调用方式。
this既不指向函数自身也不指向函数的词法作用域。它指向声明完全取决于函数在哪里被调用。
2、this全面解析
(1)、调用位置
调用位置最重要的是要分析调用栈,调用位置就在当前正在只想函数的前一个调用中。
通过代码看看到底声明是调用栈和调用位置:
function baz(){
//当前调用栈是:baz
//因此,当前调用位置是全局作用域
console.log("baz");
bar();
}
function bar(){
//当前调用栈是baz ->bar
//因此,当前调用位置在baz中
console.log("bar");
foo();//<--foo的调用位置
}
function foo(){
//当前调用栈是baz ->bar->foo
//因此,当前调用位置在foo中
console.log("foo");
}
baz();//<--baz的调用位置
(2)、绑定规则
(a)、默认绑定(独立函数调用)
如下代码:
function foo(){
console.log(this.a);//
}
var a=2;
foo();//2 此时foo()是直接使用不带任何修饰的函数引用进行调用的,因此this是默认绑定,this指向全局变量。
(b)、隐式绑定
隐式绑定规则会吧函数调用中的this绑定到这个上下文对象。它的落脚点指向调用的对象。
如:
function foo(){
console.log(this.a);
}
var obj2 = {
a:42,
foo:foo
}
var obj1 = {
a:2,
obj2:obj2
}
obj1.obj2.foo();// 42 此时this 指向最后一层调用他的对象obj2.所以输出42
另外一个常见的this绑定被隐式绑定的函数丢失绑定对象,会应用默认绑定,从而指向了全局对象,如果是严格模式指向undefied
如:
function foo(){
console.log(this.a);
}
var obj ={
a:2,
foo:foo
}
var bar = obj.foo();
var a = "opps,global";
bar();//"opps,global" bar 是obj.foo的一个引用,但实际上,它引用了foo函数本身。此时bar()等价于直接调用foo()函数,所以this应用了默认绑定规则,指向全局变量。
(c)、显示绑定
直接指定this的绑定对象,称之为显示绑定。通过call(..)或者apply(..)实现显示绑定。
如:
function foo(){
console.log(this.a);
}
var obj={
a:2
}
foo.call(obj);//2 通过foo.call(..),强制把它的this绑定到obj上。
(d)、new 绑定
使用new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
1、创建(或者说构造)一个全新的对象。
2、这个新对象会被执行[[原型]]连接。
3、这个新对象会绑定到函数调用的this.
4、如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
考虑下面的代码:
function foo(a){
this.a=a;
}
var bar = new foo(2);
console.log(bar.a);//2
使用new 来调用foo(..)时,我们会构造一个新对象并把它绑定到foo(..)调用中的this上。
(3)、判断this的优先级,我们可以按照如下顺序来进行判断:
(a)、函数是否在new中调用(new绑定)?如果是的话,this绑定的是新创建的对象。
var bar = new foo(); //this 是bar对象
(b)、函数是否通过call,apply(显示绑定)?如果是的话,this绑定的是指定的对象。
var bar = foo.call(obj2); //this是obj2对象
(c)、函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。
var bar = obj1.foo(); //this是obj1对象
(d)、如果都不是的话,使用默认绑定,如果在严格模式下,就绑定到undefined,否则绑定到全局对象
var bar =foo(); //this 绑定到全局对象
3、对象
3.1 语法
对象可以通过两种形式定义:声明(文字)形式和构造形式。
(1)、对象的文字语法:
var myobj = {
key :value,
//
}
(2)、构造形式:
var myObj = new Object();
myObj.key = value;
两种方式生成的对象是一样的。
3.2 类型和内置对象
javascript 六种主要类型(语言类型):string、number、boolean、null、undefined、object
javascript 内置对象有:String、Number、Boolean、Object、Function、Array、Date、RegExp、Error
对于string、number、boolean 引擎自动会把字面量转成相应的对象,可以访问其属性和方法。
如:42.359.toFixed(2);引擎把数字自动转成new Number(42.359).
null和undefined没有对应的构造形式,他们只有文字形式。
3.3 对象内容
对象的内容是由一些存储在特定命名位置的(任意类型的)值组成的,我们称之为属性。
对象内容的访问可以通过“属性访问”或“键访问”方式。
如:
var myObject = {
a:2
}
myObject.a;//2 属性访问
myObject["a"];//2 键访问
两种语法的区别在于:.操作符要求属性名满足标识符的命名规范,而[".."]键访问语法可以接受任意UTF-8/Unicode字符串作为属性名。
3.4 可计算属性名
可计算属性名是只属性名是动态的。可以在文字形式中使用[]包裹表达式来当做属性名:
如下代码:
var prefix = "foo";
var myObject = {
[prefix+"bar"]:"hello",
[prefix+"baz"]:"world"
}
myObject["foobar"];//hello
myObject["foobaz"];//world
3.5 属性描述符
ES5开始所有的属性都具备了属性描述符。如下代码:
var myObject = {a:2}
Object.getOwnPropertyDescriptor(myObject,"a");
//{value: 2, writable: true, enumerable: true, configurable: true}
普通对象属性还包括另外三个特性:writalbe(可写)、enumerable(可枚举)和configurable(可配置)
我们也可以使用Object.defineProperty(..)来添加或修改一个已有属性。
如:
var myObject={};
Object.defineProperty(myObject,"a",{
value:2,
writable:false,//不可写!
configurable:true,
enumerable:true
});
myObject.a=3;//写属性false,这里不能重新赋值
myObject.a;//2
(1)、禁止扩展
禁止一个对象添加新属性并且保留已有属性,可以使用Object.preventExtensions(..)
(2)、密封
Object.seal(..)会创建一个“密封”的对象,不能添加也不能配置或删除任何现有属性。
(3)、冻结
Object.freeze(..)会创建一个冻结对象,这样无法修改他们的值。
属性可以是可枚举或者不可枚举的,这决定了它们是否会出现在for .. in循环中。Object.keys返回一个对象属性值的数组。
4、原型
4.1 [[Prototype]]
javascript 中的对象有一个特殊的[[Prototype]]内置属性,其实就是对于其他对象的应用。
如果要访问对象中并不存在的一个属性,[[Get]]操作就会查找对象内部[[Prototype]]关联的对象,这个关联关系实际上定义了一条“原型链”,在查找属性时会对它进行遍历。
所有普通对象都有内置的Object.prototype,指向原型链的顶端,如果在原型链中找不到指定的属性就会停止。toString()、valueOf()和其他一些通用的功能都存在于Object.property对象上,因此语言中所有的对象都可以使用它们。
关联两个对象最常用的方法是使用new 关键词进行函数调用,在调用中会创建一个关联其他对象的新对象。
使用new 调用函数时会把新对象的.prototype属性关联到“其他对象”。带new的函数调用通常称为“构造函数调用”,尽管他们实际上和传统面向累语言中的类构造函数不一样。
对象之间关系不是复制而是通过内部的[[Prototype]]链关联,是一种委托。
5、行为委托
5.1 面向委托的设计
行为委托认为对象之间是兄弟关系,互相委托,而不是父类和子类的关系。