js 深入学习(一篇长文 干货满满…)

314 阅读9分钟

学习来源:一位对js理解很透彻和写文章很易读的人,还很帅

数据类型:

基本数据类型有六种,分别是undefined,null,boolean,string,number,symbol(ES6新增)  

复杂类型只有一种:Object(参照MDN)。Array Date 都是对象的一种(面试可能会问)

  • Null类型只有一个值null,表示一个空对象指针,使用typeof操作符检测null会返回object。但是null不是对象,而是一种基本数据类型

    根据《你不知道的JS》书写到 null是js中的一个基本数据类型,之所以显示为'object'是因为对象在底层被表示为二进制,在js中二进制前三位都为0会被判断为object类型,null的二进制表示全是0,自然前三位是0,所有typeof null会返回'object' 这是语言层面的bug 知悉!

存储区别:

  • 基本数据类型存储在栈内存,存储的是值

  • 复杂数据类型的值存储在堆内存,地址(指向真实数据)存储在栈内存,当我们把对象赋值给另外一个变量的时候,赋值的是地址,指向同一块内存,当一个对象改变时,另外一处也会变化

以上代码中,obj是一个自定义的对象,其中a、b、c就是它的属性,而且在c的属性值还是一个对象,它又有name、year两个属性。

那么函数和数组也可以这样定义属性吗?——当然不行

但是它可以用另一种形式,形式不同
总之函数/数组之流,只要是对象,它就是属性的集合。

prototype和__proto__

函数形式,函数还可以通过prototype 增加属性

 var fn = function () {
            alert(100);
        };
        fn.a = 10;
        fn.b = function () {
            alert(123);
        };
        fn.c = {
            name: "王福朋",
            year: 1988
        };

例如 jquery $.xxx   这个实现方式是prototype 实例化出来的函数属性

函数也是一种对象。他也是属性的集合,你也可以对函数进行自定义属性。

不用等咱们去试验,javascript自己就先做了表率,人家就默认的给函数一个属性——prototype。对,每个函数都有一个属性叫做prototype。这个prototype的属性值是一个对象(属性的集合,再次强调!),默认的只有一个叫做constructor的属性,指向这个函数本身。

如这位Object大哥,人家的prototype里面,就有好几个其他属性。

函数是一种对象  对象通过函数创建

函数和对象之间的关系比较复杂,甚至有一点鸡生蛋蛋生鸡的逻辑

{  }   这个——是一种——“快捷方式”,在编程语言中,一般叫做“语法糖”。

每个函数function都有一个prototype,即原型。这里再加一句话——每个对象都有一个__proto__,可成为隐式原型。

这个__proto__是一个隐藏的属性,javascript不希望开发者用到这个属性值

自定义函数的prototype本质上就是和 var obj = {} 是一样的,都是被Object创建,

所以var obj = {}  的 __proto__指向的就是Object.prototype。

Object.prototype是一个特例——它的__proto__指向的是null,切记切记!

原型prototype的用法

最主要的方法就是将属性暴露成公用的,原型的灵活性:对象属性可以随时改动。

对象或者函数,刚开始new出来之后,可能啥属性都没有。但是你可以这会儿加一个,过一会儿在加两个,非常灵活。

new Function()

function 的由来:由new Function() 生成,和 new Object()是一个形式的。

Function也是一个函数,函数是一种对象,也有__proto__属性。既然是函数,那么它一定是被Function创建。所以——Function是被自身创建的。所以它的__proto__指向了自身的Prototype。

Instanceof运算符

Instanceof运算符的第一个变量是一个对象,暂时称为A;第二个变量一般是一个函数,暂时称为B。

Instanceof的判断队则是:沿着A的__proto__这条线来找,同时沿着B的prototype这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。

原型链

访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链。

继承

所有的对象的原型链都会找到Object.prototype,因此所有的对象都会有Object.prototype的方法。这就是所谓的“继承”。

函数自带方法

我们都知道每个函数都有call,apply方法,都有length,arguments,caller等属性。为什么每个函数都有?这肯定是“继承”的。函数由Function函数创建,因此继承的Function.prototype中的方法。还有hasOwnProperty呢?——那是Function.prototype继承自Object.prototype的方法。

js 执行上下文

两个名词——“函数表达式”和“函数声明”。虽然两者都很常用,但是这两者在“准备工作”时,却是两种待遇。

  • 变量、函数表达式——变量声明,默认赋值为undefined;
  • this——赋值;
  • 函数声明——赋值;

javascript在执行一个代码段之前,都会进行这些“准备工作”来生成执行上下文。这个“代码段”其实分三种情况——全局代码,函数体,eval代码。

  • eval不常用,也不推荐大家用  例如:  evel("alert(123)")

  • 全局代码  (script 标签中的代码)

  • 函数体  (函数)

引入一句名句:

任何问题,都要去追根溯源,要知道这个问题是真正出自哪一块知识点,要真正去理解。光靠背诵是没用的。

arguments变量和函数的参数都已经被赋值。从这里可以看出,函数每被调用一次,都会产生一个新的执行上下文环境。因为不同的调用可能就会有不同的参数。

函数在定义的时候(不是调用的时候),就已经确定了函数体内部自由变量的作用域

全局代码的上下文环境数据内容为:

普通变量(包括函数表达式),

如: var a = 10;

声明(默认赋值为undefined)

函数声明,

如: function fn() { }

赋值

this

赋值

如果代码段是函数体,那么在此基础上需要附加:

参数

赋值

arguments

赋值

自由变量的取值作用域

赋值

给执行上下文环境下一个通俗的定义——在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined占个空。

this

  • 函数中this到底取何值,是在函数真正被调用执行的时候确定,函数定义的时候确定不了
  1. ****构造函数
    **构造函数的prototype,和整个原型链中,this代表的都是当前对象的值。
    **
  2. ****函数作为对象的一个属性
  3. ******函数用call或者apply调用
  4. ********全局 & 调用普通函数

函数f虽然是在obj.fn内部定义的,但是它仍然是一个普通的函数,this仍然指向window。

执行上下文栈

处于活动状态的执行上下文环境只有一个。其实这是一个压栈出栈的过程——执行上下文栈

有很常用的一种情况,无法做到这样干净利落的说销毁就销毁。这种情况就是伟大的——闭包。

要说闭包,得从自由变量和作用域说起。

作用域

javascript除了全局作用域之外,只有函数可以创建的作用域作用域在函数定义时就已经确定了。而不是在函数调用时确定

我们在声明变量时,全局代码要在代码前端声明,函数中要在函数体一开始就声明好。除了这两个地方,其他地方都不要出现变量声明。而且建议用“单var”形式。

匿名函数:限制在一个独立的作用域中,而不会与全局作用域或者其他函数作用域的同名变量产生冲突。两种形式:

(function(){ .. }())
(function foo(){ .. })(windows可传参)

作用域只是一个“地盘”,一个抽象的概念,其中没有变量。要通过作用域对应的执行上下文环境来获取变量的值。同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。所以,作用域中变量的值是在执行过程中产生的确定的,而作用域却是在函数创建时就确定了。

如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文环境,再在其中寻找变量的值。

变量提升

var a = 2 // var a; 和 a = 2;先有蛋(声明)后有鸡(赋值)

自由变量

要到创建这个函数的那个作用域中取值——是“创建”,而不是“调用”,切记切记——其实这就是所谓的“静态作用域”。

“作用域链”过程:(假设a是自由量)

第一步,现在当前作用域查找a,如果有则获取并结束。如果没有则继续;

第二步,如果当前作用域是全局作用域,则证明a未定义,结束;否则继续;

第三步,(不是全局作用域,那就是函数作用域)将创建该函数的作用域作为当前作用域;

第四步,跳转到第一步。

闭包

只需要知道应用的两种情况即可——函数作为返回值,函数作为参数传递。

闭包就是能够读取其他函数内部变量的函数,或者子函数在外调用,子函数所在的父函数的作用域不会被释放。

使用闭包会增加内容开销,现在很明显了吧!

闭包执行到最后才会去执行销毁

闭包和作用域、上下文环境有着密不可分的关系,真的是“想说爱你不容易”!

详情

JSONP原理

ajax 请求受同源策略影响,不允许进行跨域请求,而 script 标签 src 属性中的链接却可以访问跨域的js脚本,利用这个特性,服务端不再返回JSON格式的数据,而是返回一段调用某个函数的js代码,在src中进行了调用,这样实现了跨域。

强调基础,强调理论!理论和实践相结合不是一句空话。
工作之余,切不可忘记充电

推荐去顶部链接进入阅读原文!!!(为啥三个感叹号 强烈推荐呢,话不多说,我上个图)

推荐去上方连接进入阅读原文!!!(为啥三个感叹号 强烈推荐呢,话不多说,我上个图)

推荐去上方连接进入阅读原文!!!(为啥三个感叹号 强烈推荐呢,话不多说,我上个图)