JavaScript原型、原型链、作用域、作用域链、闭包

  • JavaScript面向对象的基础——原型链
  • JavaScript闭包的形成
  • JavaScript中this
  • JavaScript中的毒瘤和缺陷

一些JavaScript简单语法
. 操作符:访问对象的属性
var 操作符:用来声明变量(也叫标识符)
function: 用来创建函数
new 操作符:执行构造函数
全局环境和全局对象
对象字面量

原型对象
原型对象:构造函数都设置一个prototype属性,这个属性就指向原型对象。其实原型对象就只是个普通对象,里面存放着所有实例对象需要共享的属性和方法!所以,我们把需要共享的放到原型对象里,把那些不需要共享的属性和方法存在在构造函数里!

1 function Female(name){
2     this.name = name;
3     this.sex = 'female';  
4 }

通过new命令来生成一个person实例:
var person1 = new Female(“Summer”)
这里,构造函数Female就是实例对象person1的原型!!!Female里的this关键字就指的是person1这个对象!new出来的person1对象此时已经和Female再无联系了!也就是说每一个new出来的实例都有自己的属性和方法的副本,是独立的的!修改其中一个不会影响另一个!

JavaScript中的面向对象
面向对象的语言有一个标志,那就是它们都有类的概念,通过类可以创建多个具有相同属性和方法的对象。但是,在JavaScript中并没有类的概念(虽然有new操作符),那JavaScript怎么实现面向对象呢?
答案就是:原型链

原型链
原型链是一个用来实现继承和共享属性的有限对象链。
考虑这么一个情况,我们拥有两个对象,它们之间只有一小部分不同,其他部分都相同。显然,对于一个设计良好的系统,我们将会重用相似的功能/代码,而不是在每个单独的对象中重复它。

 function Ball () {  // 球的构造函数
    this.shape = 'ball';
}
Ball.prototype.roll = function () {
    console.log('I am rolling.');
}
function Pingpong () {  // 乒乓球的构造函数
    this.diameter = 40; 
}
Pingpong.prototype = new Ball();
Pingpong.prototype.getDiameter = function () {
    return this.diameter;
}

var b = new Pingpong();
console.log(b.getDiameter()); // 40
b.roll();  // I am rolling.

这里写图片描述

原型链小结
js里完全依靠”原型链”(prototype chain)模式来实现继承。
proto:事实上就是原型链指针!!
prototype:上面说到这个是指向原型对象的
constructor:每一个原型对象都包含一个指向构造函数的指针,就是constructor
原理:当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止,大多数JavaScript的实现用 proto 属性来表示一个对象的原型链,(使用一个C对象时,原型链中通过proto寻找它的属性和方法,如果没有继续寻找上一级对象的原型链)
proto和prototype的区别和关系:
几乎任何对象有一个[[prototype]]属性,在标准中,这是一个隐藏属性。该属性指向的是这个对象的原型。然而虽然说[[prototype]]是一个隐藏属性,但很多浏览器都给每一个对象提供.proto这一属性,这个属性就是上文反复提到的该对象的[[prototype]]。由于这个属性不标准,因此一般不提倡使用。ES5中用Object.getPrototypeOf函数获得一个对象的[[prototype]]。ES6中,使用Object.setPrototypeOf可以直接修改一个对象的[[prototype]]

function foo() {
  var x = 10;
  return function bar() {
    console.log(x);
  };
}
var returnedFunction = foo();
var x = 20;
returnedFunction(); // 会出现哪个数字?
var x = 10;
function foo() {
  console.log(x);
}

var b = function (funArg) {
  var x = 20;

  funArg();
}
b(foo); // 会出现哪个数字?

相同的代码在不同的上下文中的执行结果是不一样的。
函数的每次调用都会创建一个新的上下文。

function make_withdraw(balance) {  // 这是一个“提款处理器”
  return function (amount) {
    if (balance >= amount) {
        balance = balance - amount;
        return balance;
     } else {
        throw "insufficient funds";
     } 
  }
}
var w1 = make_withdraw(100);
var w2 = make_withdraw(200);
w1(50); //50
w2(100); //100

执行上下文栈
JavaScript在对一个函数的每次调用,会进入到函数执行上下文中,并对函数体代码进行求值。
一个函数可能会创建无数的上下文,因为对函数的每次调用(即使这个函数递归的调用自己)都会生成一个具有新状态的上下文。
一个执行上下文可能会触发另一个上下文,比如,一个函数调用另一个函数(或者在全局上下文中调用一个全局函数)。从逻辑上来说,这是以栈的形式实现的,它叫作执行上下文栈。

var number = 10;
function sum (anotherNumber) { 
    return number + anotherNumber;
}
sum(2);  // 12

这里写图片描述

执行上下文
一个执行上下文可以抽象的表示为一个简单的对象。每一个执行上下文拥有一些属性用来跟踪和它相关的代码的执行过程。下图中展示了一个上下文的结构:
这里写图片描述

变量对象
变量对象是与执行上下文相关的数据作用域。它是一个与上下文相关的特殊对象,其中存储了在上下文中定义的变量和函数声明。变量对象是一个抽象概念。对于不同的上下文类型,是使用不同的对象。比如,在全局上下文中变量对象就是全局对象本身;当函数被调用的时候,则会创建一个活动对象来作为变量对象使用。函数调用创建的活动对象,会比全局对象多一些属性,例如:形参、arguments。
这里写图片描述
作用域
全局作用域和函数作用域,块作用域
最外层函数定义的变量拥有全局作用域,即对任何内部函数来说,都是可以访问
局部作用域:
和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,而对于函数外部是无法访问的,最常见的例如函数内部
需要注意的是,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!
作用域链(根据在内部函数可以访问外部函数变量的这种机制,用链式查找决定哪些数据能被内部函数访问)
这个规则还是与原型链同样简单以及相似:如果一个变量在函数自身的作用域(在自身的变量/活动对象)中没有找到,那么将会查找它父函数(外层函数)的变量对象,以此类推。需要搜索作用域链的变量叫做自由变量。
在创建时刻函数会保存父函数的作用域链,因为确切的说这个保存下来的作用域链将会在未来的函数调用时用来查找变量。在函数调用时,将当前函数的变量对象拼接到父函数作用域链开头,然后保存在活动对象中。
这里写图片描述

闭包
在计算机科学中,闭包,又称词法闭包或函数闭包,是引用了自由变量的函数。
在JavaScript中,函数是第一级对象,即函数可以作为函数的参数、可以作为函数的返回值、可以赋值给变量。
这是一个很强大的语言特性,不过它会引起两个问题:
1.当函数作为返回值,函数内部的自由变量的解析问题
2. 当函数作为参数传递时,函数内部的自由变量的解析问题

function foo() {
  var x = 10;
  return function bar() {
    console.log(x);
  };
}
var returnedFunction = foo();
var x = 20;
returnedFunction(); // 会出现哪个数字?

这里写图片描述

var x = 10;
function foo() {
  console.log(x);
}

var b = function (funArg) {
  var x = 20;

  funArg();
}
b(foo); // 会出现哪个数字?

这里写图片描述

var data = [];

for (var k = 0; k < 3; k++) {
  data[k] = function () {
    alert(k);
  };
}
data[0](); 
data[1](); 
data[2](); 
var data = [];

for (var k = 0; k < 3; k++) {
  data[k] = (function (x) {
    return function () {
      alert(x);
    };
  })(k);
}

data[0](); // 0
data[1](); // 1
data[2](); // 2

闭包产生内存泄漏的解决方案
1、不要使用el变量
2、加入一个闭包函数
解释JavaScript中的作用域与变量声明提升?
* JavaScript作用域:
* 在Java、C等语言中,作用域为for语句、if语句或{}内的一块区域,称为作用域;
* 而在 JavaScript 中,作用域为function(){}内的区域,称为函数作用域。
* JavaScript变量声明提升:
* 在JavaScript中,函数声明与变量声明经常被JavaScript引擎隐式地提升到当前作用域的顶部。
* 声明语句中的赋值部分并不会被提升,只有名称被提升
* 函数声明的优先级高于变量,如果变量名跟函数名相同且未赋值,则函数声明会覆盖变量声明
* 如果函数有多个同名参数,那么最后一个参数(即使没有定义)会覆盖前面的同名参数
this
this是一个与执行上下文相关的特殊对象。在JavaScript的实现中,this是执行上下文的一个属性,而不是变量对象的一个属性。这个特性非常重要,因为与变量相反,this从不会参与到标识符解析过程。换句话说,在代码中当访问this的时候,它的值是直接从执行上下文中获取的,并不需要任何作用域链查找。this的值只在进入上下文的时候进行一次确定。
在全局环境中,this的值是全局对象
在函数中的this,一共有四种情况
1、作为构造函数调用时,this指向构造函数创建的对象
2、作为对象的方法调用时,指向该对象
3、作为函数调用时,指向全局对象
4、call或apply调用时,指向传入的第一个参数

var foo = function (name) {
  this.name = name;
  console.log(this);
}
foo(); // 作为函数调用

var obj = {
  name: 'obj',
  f: foo
};
obj.f(); //作为对象的方法调用

var anotherObj = {
    name: 'another'
};
foo.apply(anotherObj); // apply借用函数 

JavaScript中的毒瘤
全局环境
作用域
自动插入分号
parseInt
+
假值(0,NaN,’’,false,null,undefined)
JavaScript中的缺陷
== (‘’==0,false==0)
with
eval
function 声明提升
new
void

参考资料
《JavaScript高级程序设计》第四章
《ECMA-262 5.1 edition》 Chapter 10
《计算机程序的构造与解释》第3章第2节
《JavaScript语言精粹》第四章、附录A
http://weizhifeng.net/javascript-the-core.html
http://lzw.me/pages/ecmascript/#118

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值