javascript作用域链

2014年09月22日

关于function

javascript中没有类的概念,都是函数。类有一个很重要的特性,就是可以根据它的构造函数来创建以它为模板的对象。在javascript中,函数就有2个功能:
第一、 作为一般函数调用
第二、 作为它原型对象的构造函数,即new()

new()做了什么

1,生成一个新的对象object,类型是一个简单的object

2,把构造函数的外部,可访问的,prototype对象设置到这个新对象的内部

3,执行构造函数,在有this的地方统统指向这个新对象

4,返回这个新对象,除非返回值是非原始类型。如果是非原始类型,就返回该值

举个例子访问:

function Person(){
  this.name = "";
}
Person.prototype={
  say:'hello'
}
var p = new Person();

//相当于
var p = {}; //var p = new Object()也正确
p.__proto__ = Person.prototype;
Person.apply(p); 

关于prototype与__proto__

上面的代码中,如果我们在

	function Person(){
  		this.name="";
	}
	Person.prototype={
  		say:'hello'
	}

	var p = new Person();
	console.log(p.say); //hello
	console.log(p.__proto__ === Person.prototype); //true
	
	//------分隔线我们换种写法看看结果
	function Person(){
  		this.name="";
	}
	Person.prototype={
  		say:'hello'
	}
	var p = {};
	p.__proto__ = Person.prototype;
	Person.call(p);
	console.log(p.say); //hello

一个对象的__proto__属性,是在对象出生时,由构造函数的prototype属性决定的。那么__proto__和prototype是否可以修改呢?

function Person(){
  		this.name="";
}
Person.prototype={
  		say:'hello'
}

var p = {};
p.__proto__ = Person.prototype;
Person.call(p);
console.log(p.say); //hello

Person.prototype.say = "hello world";
console.log(p.say); //hello world
console.log(p.__proto__.say); //hello world
p.__proto__.say = "你好";
console.log(p.say); //你好
console.log(Person.prototype.say); //你好
//由此可见prototype属性和__proto__是可以修改的,而且两者互相影响

那么问题就来了,什么时候修改prototype?什么时候修改__proto__?修改他们会造成什么影响?

function Person(){
  		this.name="";
}
Person.prototype={
  		say:'hello'
}

var p = {};
p.__proto__ = Person.prototype;
Person.call(p);

Person.prototype.say = "hello world";

console.log("-------2333我是一条分割线")
var p1 = new Person();
console.log(p1.say); //hello world


function Person(){
  		this.name="";
}
Person.prototype={
  		say:'hello'
}

var p = {};
p.__proto__ = Person.prototype;
Person.call(p);

p.__proto__.say = "你好";
console.log(Person.prototype.say);  //你好

console.log("-------2333我是一条分割线")
var p1 = new Person();
console.log(p1.say); //你好 从上面两个例子我们可以发现,修改一个对象的\_\_proto\_\_和修改其构造函数的prototype表面上的效果是一样的(即\_\_proto\_\_能够修改一个对象的原型继承链),改了\_\_proto\_\_等于改了prototype,同时影响到了其他由此构造函数生成的对象(当修改了对象的\_\_proto\_\_后,就相当于修改了对象的整个继承结构),我们应当保证原型链继承的稳定。还有我们要记住的一点是,不是所有的js执行环境都支持\_\_proto\_\_的(IE), 且按照标准来说\_\_proto\_\_属性应当是一个私有属性不应该被公开,只是firefox的引擎将其暴露出来罢了。

关于是否应该修改__proto__我还没有深入了解,暂且参考Effective JavaScript Item 32 绝不要修改__proto__

js中的类

js里的类就是构造函数,Object,Function,Array,Date,这些大写字母开头的东西,都是函数而已 也就是说,Object,Function,Array。。(他们都是需要new的)等等,它们的类型都是Function

console.log(Object.__proto__ === Function.prototype); //true
console.log(Function.__proto__ === Function.prototype); //true
console.log(Array.__proto__ === Function.prototype); //true
console.log(Date.__proto__ === Function.prototype); //true JS中还有一些内置的对象,如Math,Json等,他们是以**内置对象**的形式存在的,**无需new**。所以他们的\_\_proto\_\_是Object.prototype

console.log(Math.__proto__ === Object.prototype); //true
console.log(JSON.__proto__ === Object.prototype); //true

上述表现说明了所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身。所有构造器都继承了Function.prototype的属性及方法。如length、call、apply、bind(ES5)

Function.prototype也是唯一一个typeof XXX.prototype为 “function”的prototype,其他的构造器的prototype都是一个对象。

console.log(typeof Function.prototype === "function");  //true
alert(Function.prototype); //function() {} //一个空对象
console.log(Object.prototype.__proto__ === null ); //true 到顶了

constructor 设置原型的两种方式

我们知道原型可以有两张写法。

var Person = function () {
};

//修改原型
Person.prototype.say = function () {
	console.log("hello");
}
var p = new Person();
console.log(p.__proto__ === Person.prototype); //true
console.log(p.__proto__ === p.constructor.prototype); //true
//每个对象都有constructor属性,且p.__proto__ === Person.prototype === p.constructor.prototype

然后我们看第二种写法

var Person = function () {
};

//相当于重写了原型
Person.prototype = {
	say: function () {
		console.log("hello");
	}
}

var p = new Person();
console.log(p.__proto__ === Person.prototype); //true
console.log(p.__proto__ === p.constructor.prototype); //false

这潭水太深了我去JavaScript中__proto__与prototype的关系

一个例子

var A = function() {
	this.i = 1;
}

A.prototype = {
	i: 2
}

var B = function() {
	this.i = 1;
}

B.prototype= {
	i : 3
}

var a = new A();
delete a.i; //删除的不是原型链上的值
console.log(a.i);  //2 查找原型链上的i
a.i = 5; //重新赋值自身属性
console.log(a.i); //找的是this.i
delete a.i; //删除的不是原型链上的值
A.prototype.i = 9; //注意是A而不是a,如果是a.prototype.i是错误的
console.log(a.i); //9

a.__proto__ = B.prototype;
console.log(a.i); //3

console.log((typeof a) === (typeof (new B()))); //true

感觉属性查找的时候,先查找构造函数里的属性,如果找不到,才是查找原型链(查找方法也是一样)。如果要修改原型链上的属性,应当用A.prototype.i = ?,要取原型链上的值应当是console.log(A.prototype.i)。