JavaScript高级内容:原型链、继承、执行上下文、作用域链、闭包
最近在系统的学习JS深层次内容,并稍微整理了一下,作为备忘和后期复习,这里分享给大家,希望对大家有所帮助。如有错误请留言指正,tks。
了解这些问题,我先一步步来看,先从稍微浅显内容说起,然后引出这些概念。
本文只用实例验证结果,并做简要说明,给大家增加些印象,因为单独一项拿出来都需要大篇幅讲解。
1.值类型 & 引用类型
function show(x) {
console.log(typeof(x)); // undefined
console.log(typeof(10)); // number
console.log(typeof('abc')); // string
console.log(typeof(true)); // boolean console.log(typeof(function() {})); //function console.log(typeof([1, 'a', true])); //object
console.log(typeof({
a: 10,
b: 20
})); //object
console.log(typeof(null)); //object
console.log(typeof(new Number(10))); //object
}
show();
undefined, number, string, boolean 属于简单的值类型,不是对象;
函数、数组、对象、null、new Number(10)都是对象;
判断一个变量是不是对象非常简单。值类型的类型判断用typeof,引用类型的类型判断用instanceof。
2. 语法糖(糖衣语法)
var obj = { a: 10, b: 20 };
var arr = [5, 'x', true];
语法糖其实是一种“快捷方式”,上面的代码是一种缩写,完整的写法是:
var obj = new Object();
obj.a=10;
obj.b=20; var arr= new Array();
arr[0]=5;
arr[1]='x';
arr[2]=true;
3. 对象都是通过函数创建的,而函数又是一种对象
方法一:
var fn = function() {
alert(100);
};
fn.a = 10;
fn.b = function() {
alert(123);
};
fn.c = {
name: "典橙贸易",
year: 2016
};
fn是函数,a/b/c是fn创建的对象。
方法二:
var obj = { a: 10, b: 20 };
var arr = [5, 'x', true]; 其本质是:
var obj = new Object();
obj.a = 10;
obj.b = 20; var arr = new Array();
arr[0] = 5;
arr[1] = 'x';
arr[2] = true; 而
console.log(typeof (Object)); // function
console.log(typeof (Array)); // function
4. 构造函数
只有构造函数才有prototype属性。
通常我们自定义的函数都属于构造函数,所以都有此属性。
JS运行时环境内置的函数有些不是构造函数,比如alert和Math.sqrt等,就没有此属性。
注:构造函数是指有一个内部属性[[Construct]],通过new可以创建对象的那些函数。
5. prototype 与 __proto__
每个函数function都有一个prototype,即原型。
每个对象都有一个__proto__,即隐式原型。
只有构造函数才有prototype属性。
对象的__proto__指向的是创建它的函数的prototype。
函数的prototype都是被Object创建的,每个自定义函数创建之初,都会有一个prototype(它是如何被创建的,不得而知!)。
例如:
function Foo(){};
var foo = new Foo();
Foo.__proto__ === Function.prototype // 任何函数都是由Function创建的,所以任何函数的__proto__都指向Function的prototype
foo.__proto__ === Foo.prototype // 对象的__proto__指向的是创建它的函数的prototype
Foo.prototype.__proto__ === Object.prototype // 如果是自定义的函数,它的prototype.__proto__ 指向 Object.prototype,因为自定义函数的prototype本质上就是和语法糖 var obj = {} 是一样的,都是被Object创建。
Object.prototype.__proto__ === null // Object.prototype也是个对象,它的__proto__是null(这是个特例,切记切记) foo.prototype == undefined //只有函数才有prototype属性,foo是对象
Object.prototype === (new Object()).__proto__ // 如上所述,Object本身是一个函数,(new Object())是对象
Function.prototype.__proto__ === Object.prototype // Function是一个函数,它的prototype是被Object创建,所以它的Prototype.__proto__指向创建Object.prototype
Function.__proto__ === Function.prototype //Function也是一个函数,函数是一种对象,也有__proto__属性。既然是函数,那么它一定是被Function创建。所以——Function是被自身创建的
Object.__proto__ === Function.prototype //Object是个函数,也是对象,它的__proto__指向的是创建它的函数的prototype
参考:
http://www.cnblogs.com/wangfupeng1988/p/3978131.html
http://www.cnblogs.com/wangfupeng1988/p/3979290.html
6. instanceof原理
Instanceof运算符的第一个变量是一个对象,暂时称为A;第二个变量一般是一个函数,暂时称为B。
Instanceof的判断规则是:
沿着A的__proto__这条线来找,如果B的prototype在这条线上,那么就返回true,否则返回false。
如:
function Foo(){}
var f1 = new Foo();
console.log(f1 instanceof Foo); // true
console.log(f1 instanceof Object); // true
分析方法:
f1的__proto__路线A:
(1) f1.__proto__ === Foo.prototype
(2) Foo.prototype.__proto__ === Object.prototype
(3) Object.prototype.__proto__ === null //到终点
结论:
f1 instanceof Foo :A线路(1)中出现了Foo.prototype
f1 instanceof Object :A线路(2)中出现了Object.prototype
参考:
http://www.cnblogs.com/wangfupeng1988/p/3979533.html
7. 原型链
访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链。
例如:
function Foo(){}
var f1 = new Foo();
f1.a = 2;
f1.b = 3;
Foo.prototype.b = 6;
Foo.prototype.c = 7; console.log(f1.a); // 2,a是f1的基本属性,直接访问没问题
console.log(f1.c); // 7,f1的基本属性中没有c,沿着原型链向上,f1.__proto__是Foo.prototype,而Foo.prototype有c,可以访问。
console.log(f1.b); // 3,b是f1的基本属性,直接访问没问题。可Foo.prototype还有一个b,但是不会显示,这种情况被称为“属性遮蔽”。
判断属性是基本属性(也叫实例属性)还是原型链上的属性(也叫原型属性)使用hasOwnProperty,一般在for…in..循环中。for…in…会输出实例属性和原型属性。
属性列表参考实例属性 vs 原型的属性 vs 静态属性
如:
var item;
for(item in f1){
console.log(item); // a,b,c
} for(item in f1){
f1.hasOwnProperty(item) && console.log(item); // a,b
}
8. 继承
在原型链的基础上,很容易理解继承。例如在原型链的实例中,f1.hasOwnProperty就是继承自Object.prototype。
过程如下:
- f1基本属性中没有hasOwnProperty,沿原型链向上找:f1.__proto__是Foo.prototype;
- Foo.prototype中也没有hasOwnProperty,继续沿原型链向上找:Foo.prototype.__proto__是Object.prototype;
Object.prototype中有hasOwnProperty,查找结束,允许访问hasOwnProperty。
由于所有的对象的原型链都会找到Object.prototype,因此所有的对象都会有Object.prototype的方法。这就是所谓的“继承”。
另外,每个函数都有的call,apply方法,和length,arguments,caller等属性也都是继承下来的。
9. 执行上下文/执行上下文环境
定义1:js在执行一段代码之前,会声明提前,赋值或赋初始值(即undefined)。所以同一代码段中,可以先访问,再定义,这就是“执行上下文”。
定义2:在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined占个空。
分4种情况:
第1种情况:变量、函数表达式——只提前声明,不提前赋值,默认赋值为undefined;
console.log(a); // undefined
var a=10;
console.log(fn1); // undefined
var fn1 = function(){};
第2种情况:this关键字——提前声明并赋值
console.log(this); // Window {external: Object, chrome: Object, document: document, __ctrip: Object, __SERVERDATE__: Object…}
第3种情况:函数声明——提前声明并赋值
console.log(fn2); // function fn2(){}
function fn2(){};
第4种情况:函数内部会创建自己独有的上下文环境
函数每被调用一次,都会产生一个新的执行上下文环境。因为不同的调用可能就会有不同的参数。
另外,每个函数在定义的时候(不是调用的时候),就已经确定了函数体内部自由变量的作用域。
如:var a = 10; function fn() {
console.log(a); // a是自由变量,函数创建时,就确定了a要取值的作用域
} function bar(f) {
var a = 20;
f(); // 10,到创建fn的地方找变量a的作用域,而不是调用它的当前作用域。
}
bar(fn);
10. 执行上下文栈
如【执行上下文/执行上下文环境】所述,js代码中,会有无数的函数调用和嵌套调用,会产生无数的上下文环境,这么多上下文环境如何管理,以及如何销毁而释放内存呢?这就需要【执行上下文栈】的参与了。
执行上下文栈:执行全局代码时,会产生一个全局上下文环境,并将该上下文环境压入栈,每次调用函数都又会产生执行上下文环境,并将该函数上下文环境也压入栈。当函数调用完成时,该函数上下文环境出栈,并消除该函数上下文环境以及其中的数据,再重新回到全局上下文环境。处于活动状态的执行上下文环境只有一个。其实这是一个压栈出栈的过程。
如:
var a = 10, // 1. 进入全局上下文环境
fn,
bar = function(x) {
var b = 5;
fn(x + b); // 3. 进入fn函数上下文环境,并入栈,设为活动状态。该函数执行完毕后,bar函数上下文出栈,并及时销毁,释放内存
}; fn = function(y) {
var c = 5;
console.log(y + c);
} bar(10); // 2. 进入bar函数上下文环境,并入栈,设为活动状态。该函数执行完毕后,bar函数上下文出栈,并及时销毁,释放内存
参考:http://www.cnblogs.com/wangfupeng1988/p/3989357.html
11. this
this在执行上下文中有着非常重要的作用,这里应该说一下。
了解this,需要记住一点:在函数中this到底取何值,是在函数真正被调用执行的时候确定的,函数定义的时候确定不了。
大致分为以下4种情况:
情况1:构造函数,this就代表new出来的对象。
function Foo(){
this.name = '典橙贸易';
this.year = 2016;
console.log(this); // Foo {name: "典橙贸易", year: 2016}
}
var f1 = new Foo(); // 所谓构造函数就是用来new对象的函数。
另外,如果在原型链中使用this(Foo.prototype中使用this),this仍然代表new出来的对象。
function Foo(){
this.name = '典橙贸易2016';
}
Foo.prototype.getName = function(){
console.log(this); // Foo {name: "典橙贸易2016"}
}
var f1 = new Foo();
f1.getName();
情况2:函数作为对象的一个属性,并且被对象直接调用时,this指向该对象。
var obj = {
name:'典橙贸易',
fn:function(){
console.log(this); // Object {name: "典橙贸易"}
console.log(this.name); // 典橙贸易
}
}
obj.fn(); // 直接调用 // 如果不是直接调用,this就指向window
var obj = {
name: '典橙贸易',
fn: function() {
console.log(this); // Window {external: Object, chrome: Object, document: document, __ctrip: Object, __SERVERDATE__: Object…}
console.log(this.name); // undefined
}
}
var fn1 = obj.fn;
fn1();
情况3:函数用call或者apply调用,this就是传入的对象的值
var obj ={
name:'典橙贸易'
}
var fn = function(){
console.log(this); // Object {name: "典橙贸易"}
console.log(this.name); // 典橙贸易
}
fn.call(obj);
// 或者
// fn.apply(obj);
情况4:全局 & 调用普通函数,this是window
全局:
console.log(this===window); // true 调用普通函数:
var name ="典橙贸易";
var fn = function() {
console.log(this); // Window {external: Object, chrome: Object, document: document, __ctrip: Object, __SERVERDATE__: Object…}
console.log(this.name); // 典橙贸易
}
fn();注意:
var obj = {
name: '典橙贸易',
fn: function() { function f() {
console.log(this); // Window {external: Object, chrome: Object, document: document, __ctrip: Object, __SERVERDATE__: Object…}
console.log(this.name); // undefined
}
f(); // 函数f虽然是在obj.fn内部定义的,但是它仍然是一个普通的函数,this仍然指向window。 }
}
obj.fn();
12. 作用域
知识点1:javascript中没有块级作用域。
var i = 2;
if(i>1){
var name ='典橙贸易';
}
console.log(name); // 典橙贸易
for循环的{}也是类似。
知识点2:javascript除了全局作用域之外,只有函数可以创建自己的作用域,称为函数作用域。
如经典的问题:
<ul id = "list">
<li> we </li>
<li> sdf </li>
<li> cx </li>
<li> h </li>
<li> z </li>
</ul>var list = document.getElementById('list');
var e = list.getElementsByTagName('li');
var i = 0; 错误写法:
for (; i < e.length; i++) {
e[i].onclick = function() {
console.log(i);
}
} 正确写法:
for (; i < e.length; i++) {
(function(a) {
e[i].onclick = function() {
console.log(a);
}
})(i);
}
等效于:
for (; i < 5; i++) {
var Fn = function(a) {
e[i].onclick = function() {
console.log(a);
}
}
Fn(i);
}错误的原因:
e[i].onclick 每次循环,只是赋值给onclick,而for循环是没有块级作用域的,所以i的值会不断累加,直到最大值5,故每次循环都会输出5。
正确的原因:
由于函数可以创建自己的作用域,而且各个作用域间不会相互影响,所以每次循环Fn(i)都会创建一个特有的函数作用域提供给相应的onclick,而每个作用于中的a变量也不会相互影响。
知识点3:作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
{
var a = 1,
b = 2; function fn1() {
var a = 100,
b = 200; function fn2() {
var a = 1000,
b = 2000;
}
}
}三个作用域下都声明了“a和b”变量,但是他们不会有冲突。各自的作用域下,用各自的“a和b”。
13. 自由变量及其取值规则
在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量。
如:
var x=1;
function fn(){
var b=2;
console.log(x+b); // 这里的x就是自由变量
}
自由变量取值规则:要到创建 自由变量所在函数 的那个作用域中取值–是【创建】,而不是【调用】,也不是【父作用域】。
如:
var x = 10;
function fn() {
console.log(x); // fn创建了自由的函数作用域,所以无论什么地方调用它,都会输出10
}
function show(f) {
var x = 20;
(function() {
console.log(x); // 要到x所在的匿名函数的作用域中找x,故输出20
f(); // 要到创建fn函数的作用域中找x,故这里输出10,而不是20
})();
}
show(fn);
14. 作用域链
在自由变量的基础上理解作用域链更加容易。
在作用域Fn中使用一个变量A,如果Fn中没有定义A,则到创建Fn的那个作用域Fm中找,如果仍没有找到,则继续到创建Fm的作用域中找……依次类推,直至找到变量A或者到全局作用域中仍未找到 为止,这种跨越多个作用域的线路,就叫“作用域链”。
代码如下:
{
// var A = 4; // 第4步,这是全局作用域,找到则返回4,如果到这里仍未找到,就结束,报错"A is not defined" function Fw() {
// var A = 3; // 第3步,在作用域Fw中找,找到则返回3,否则到创建Fw的全局作用域中找 function Fm() {
// var A = 2; // 第2步,在作用域Fm中找,找到则返回2,否则到创建Fm的Fw中找 function Fn() {
// var A = 1; // 第1步,在当前作用域找,找到则返回1,否则到创建Fn的Fm中找
console.log(A);
}
return Fn();
}
return Fm();
}
Fw();
}
注意:
这里说的创建Fn的那个作用域,而不是调用Fn的那个作用域,也不是“父作用域”。详情参考【自由变量及其取值规则 > 自由变量取值规则】中的实例。
15. 闭包
要想理解闭包,前面的【作用域、自由变量、作用域链】三部分是基础。
闭包的两种形式:函数作为返回值、函数作为参数被传递。
第一,函数作为返回值
function fn() {
var max = 10;
return function bar(x) {
if (x > max) { // max是自由变量,取值规则,参考【自由变量及其取值规则】
console.log(x);
}
}
}
var f1 = fn();
f1(15); //
第二,函数作为参数被传递
var max = 10,
fn = function(x) {
if (x > max) { // max是自由变量,取值规则,参考【自由变量及其取值规则】
console.log(x);
}
};
(function(f) {
var max = 100;
f(15); //
})(fn)
另外,
在【执行上下文栈】中说到,每个函数执行完毕,都会销毁其函数上下文环境,并清空数据。但是闭包函数执行完后,上下文环境不会被销毁。因为闭包中函数会返回或者作为参数被传递,在其他地方会被用到,一旦销毁,调用闭包的地方就无法再使用了。所以闭包会增加内容开销。
参考:
http://www.cnblogs.com/wangfupeng1988/p/3994065.html
http://blog.csdn.net/yueguanghaidao/article/details/9568071
http://blog.csdn.net/lmj623565791/article/details/25076713
JavaScript高级内容:原型链、继承、执行上下文、作用域链、闭包的更多相关文章
- 【授课录屏】JavaScript高级(IIFE、js中的作用域、闭包、回调函数和递归等)、MySQL入门(单表查询和多表联查)、React(hooks、json-server等) 【可以收藏】
一.JavaScript授课视频(适合有JS基础的) 1.IIFE 2.js中的作用域 3.闭包 4.表达式形式函数 5.回调函数和递归 资源地址:链接:https://pan.baidu.com/s ...
- Js 作用域与作用域链与执行上下文不得不说的故事 ⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄
最近在研究Js,发现自己对作用域,作用域链,活动对象这几个概念,理解得不是很清楚,所以拜读了@田小计划大神的博客与其他文章,受益匪浅,写这篇随笔算是自己的读书笔记吧~. 作用域 首先明确一个概念,js ...
- 【转】JavaScript中的原型和继承
请在此暂时忘记之前学到的面向对象的一切知识.这里只需要考虑赛车的情况.是的,就是赛车. 最近我正在观看 24 Hours of Le Mans ,这是法国流行的一项赛事.最快的车被称为 Le Mans ...
- 《JavaScript高级程序设计》读书笔记 ---执行环境及作用域
执行环境及作用域 执行环境(execution context,为简单起见,有时也称为“环境”)是JavaScript 中最为重要的一个概念.执行环境定义了变量或函数有权访问的其他数据,决定了它们各自 ...
- JavaScript高级内容笔记:原型链、继承、执行上下文、作用域链、闭包
最近在系统的学习JS深层次内容,并稍微整理了一下,作为备忘和后期复习,这里分享给大家,希望对大家有所帮助.如有错误请留言指正,tks. 了解这些问题,我先一步步来看,先从稍微浅显内容说起,然后引出这些 ...
- Javascript高级编程学习笔记(10)—— 作用域、作用域链
昨天介绍了,JS中函数的作用域 什么词法环境之类的,可能很多小伙伴不太明白. 在今天的内容开始之前,先做个简短的声明: 词法环境这一概念是在ES5中提出的,因为词法环境主要用于保存let.const声 ...
- 深入理解JavaScript系列(11):执行上下文(Execution Contexts)
简介 从本章开始,我将陆续(翻译.转载.整理)http://dmitrysoshnikov.com/网站关于ECMAScript标标准理解的好文. 本章我们要讲解的是ECMAScript标准里的执行上 ...
- JavaScript 类型、原型与继承学习笔记
目录 一.概览 二.数据类型 1. JavaScript中的数据类型 2. 什么是基本类型(Primitive Data Type) 2.1 概念 2.2 七个基本类型 2.3 基本类型封装对象 3. ...
- javascript --执行上下文,作用域
执行上下文 顾名思意就知道他是动态的,只在代码运行的时候产生 作用域 顾名思意就知道它是一个"领域",并且这个"领域"在一开始就规划好, 不会在改, var d ...
随机推荐
- oracle commond
常用commond alter user scott account unlock; --解锁账号 alter user scott identified by new_pwd; --设置密码 lsn ...
- 使用Spring构建RMI服务器和客户端
上一篇文章我们实用JDK原生API构造了简单RMI应用,本篇将实用Spring框架来构造RMI的应用,实用Spring你会体验到简单,不需要那么多的条条框框,因为Spring给你做了很多封装. 项目构 ...
- Java面试09|多线程
1.假如有Thread1.Thread2.Thread3.Thread4四条线程分别统计C.D.E.F四个盘的大小,所有线程都统计完毕交给Thread5线程去做汇总,应当如何实现? 把相互独立的计算任 ...
- Strtus2 S2-045漏洞
S2-045漏洞已经爆发几天了,但还很多网址都存在此漏洞,很多金融类网站也收到了保护费通知...唉,收保护费少,报警无效!只能酌情处理了!做黑产的,还是少做为秒,常在河边站哪有不湿鞋,劝各位早日金盆洗 ...
- 模态Model视图Push下一个视图(混合跳转)
来自: http://www.cnblogs.com/dingding3w/p/6222626.html 如果没有UINavigationController导航栏页面之间切换是不能实现Push操作的 ...
- ajax天气查询
直接法伤代码: <!DOCTYPE html><html><head> <meta charset="utf-8" /> ...
- 你的外接键盘的小键盘在Num Lock键亮着的,但是数字按了不能用,解决办法在这里
1.可能是Num Lock键卡住了导致的,你多按几次numlock键试试. 如果上面的不行,你就再试试下面的这个: 2.系统下开启了启用鼠标键导致的,解决的方法如下: (1).打开"控制面板 ...
- 用python抓取求职网站信息
本次抓取的是智联招聘网站搜索“数据分析师”之后的信息. python版本: python3.5. 我用的主要package是 Beautifulsoup + Requests+csv 另外,我将招聘内 ...
- eclipse 中 Servlet 模板代码(其实是代码提示模板)
说的是模板代码,应该说的是提示的模板代码,并不是一新建就会出现模板. 第一步:先建一个Servlet文件,写好自己想要的模板 我的模板如下: 全选并复制,等会要粘贴到Servlet的提示模板中. pa ...
- Unity 3D Framework Designing(2)——使用中介者模式解耦ViewModel之间通信
当你开发一个客户端应用程序的时候,往往一个单页会包含很多子模块,在不同的平台下,这些子模块又被叫成子View(视图),或者子Component(组件).越是复杂的页面,被切割出来的子模块就越多,子模块 ...