【JavaScript--365】基础概念整理总结(一)

254 阅读17分钟

内容提要:

  1. let/var/const区别
  2. 普通函数和箭头函数this的区别
  3. call/apply/bind的区别
  4. 闭包
  5. 原型链
  6. class面向对象编程的写法
  7. Ajax
  8. http状态码
  9. 跨域
  10. href和search
  11. for/for…of/for…in/forEach/map的区别
  12. SASS
  13. COMMONJS规范/AMD规范/CMD规范

1. let/var/const区别

  • let和var声明变量(可改变),const声明常量(只读)

  • ==变量提升==

    var存在变量提升现象,前置访问值为undefined;

    let声明变量不存在变量提升,前置访问报错(ReferenceError);

  • ==暂时性死区,块级作用域==

    let声明形成块级作用域,这个变量“绑定”这个区域,不再受外部影响;

    块级作用域:

    在代码内,let命令声明变量之前,该变量不可用,在语法上称为“暂时性死区”;

  • ==重复声明==

    let不允许在相同作用域内重复声明同一个变量;

    var允许在相同作用域内重复声明同一个变量;

  • const

    ==不存在变量提升、具有块级作用域、存在暂时性死区==;

    ==一旦声明必须赋值,不能使用null占位==;

    如果声明的是==基本类型的数据,声明后值不可改变==;

    如果声明的是==复杂类型数据,可以修改其属性==;

  • 顶层对象的属性和全局对象挂钩,在es6中全局对象将逐步与顶层对象的属性脱钩

    (↑↑翻译成人话就是var声明的全局变量相当于window的属性,而let和const声明的不是,结果为undefined)

    var a=1;
    console.log(window.a);//1
    let b=1;
    console.log(window.b);//undefined
    const c=1;
    console.log(window.c);//undefined
    
声明方式 变量提升 暂时性死区 重复声明 块作用域有效 初始值 重新赋值
var 不存在 允许 不是 非必须 允许
let 不会 存在 不允许 非必须 允许
const 不会 存在 不允许 必须 不允许

2. 普通函数和箭头函数this的区别

  • ==普通函数==

    this是执行函数时绑定;

    谁调用,this指向谁;

    非严格模式下如果没找到直接调用者,this指向window;

    严格模式下,没有直接调用者,this是undefined;

    使用call,apply,bind时,this指向绑定的对象;

  • ==箭头函数==

    this是定义函数时绑定;

    箭头函数自身并没有this值,它的this是继承而来,捕获上下文的this值作为自己的this值;

    箭头函数的this永远指向其上下文的this,任何方法 (例:call(), bind(), apply() ) 都改变不了其指向!;

  • ==↑↑ 翻译成人话:==

    普通函数:根据调用我的人(谁调用我,我的this就指向谁),可改

    箭头函数:根据所在的环境(我在哪个环境中,this就指向谁),不可改

  • ** 附

    箭头函数没有原型属性;

    箭头函数作为匿名函数,不能作为构造函数,不能使用new;

    箭头函数不绑定arguments,用rest参数取代;

    如果外层没有普通函数,this指向window,不建议最外层使用箭头函数【12.25加】

3. call/apply/bind的区别

  • ==相同点==

    三者都是Function原型中的方法;

    功能上都可以==改变this的指向==;

    第一个参数都是==指定函数内部中this的指向==;

    三者都可以在函数调用时传递参数;

  • ==不同点==

    call,bind方法直接传参,apply方法需以==数组==形式传入;

    call,apply方法是在调用之后==立即执行==函数,bind调用之后==返回改变this后的函数==,==需要再执行一次==;

  • **附:应用场景

    求数组中的最大和最小值

    var arr = [1,2,3,89,46]
    var max = Math.max.apply(null,arr)//89
    var min = Math.min.apply(null,arr)//1
    

    将类数组转化为数组

    var trueArr = Array.prototype.slice.call(arrayLike)
    

    数组追加

    var arr1 = [1,2,3];
    var arr2 = [4,5,6];
    var total = [].push.apply(arr1, arr2);//6
    // arr1 [1, 2, 3, 4, 5, 6]
    // arr2 [4,5,6]
    

    判断数据类型

    function objtype(obj){
        return Object.prototype.toString.call(obj);
    }
    console.log(objtype([1,2])); // [object Array]
    console.log(objtype({name:'zhangsan'})); // [object Object]
    console.log(objtype('zhangsan')); // [object String]
    

    利用call和apply做继承

    function Person(name,age){
        // 这里的this都指向实例
        this.name = name;
        this.age = age;
        this.sayAge = function(){
            console.log(this.age);
        };
    };
    function Female(){
        Person.apply(this,arguments);// 将父元素所有方法在这里执行一遍就继承了
    };
    var dot = new Female('Dot',2);
    

    使用 log 取代 console.log

    function log(){
      console.log.apply(console, arguments);
    };
    // 当然也有更方便的 var log = console.log()
    

4. 闭包

  • ==概念==

    指能够读取其它函数作用域的变量的函数。

  • ==特点==

    在一个函数内部定义另一个函数,并且返回内部函数;

    内部函数可读取外部函数定义的局部变量;

    让局部变量始终保存在内存中,即闭包可使得它诞生环境一直存在;

  • ==优点==

    希望一个变量长期储存在内存中(延长作用域);

    避免全局变量的污染;

    私有成员(函数内部的变量)的存在;

    可以模拟块级作用域;

  • ==缺点==

    常驻内存,增加内存使用量,需要手动销毁

    使用不当,易造成内存泄漏 --IE

  • ==问题及解决==

    闭包太多了会增加内存使用量,一块被分配的内存既不能使用,也不能回收,从而影响性能,甚至导致程序崩溃。

    常用的解决方法就是代码运行完,将形成循环引用的JavaScript对象手动设置为空,切断引用。

5. 原型链

function Person(){
};
Person.prototype.name='zhangsan';
var person=new Person();//person1:实例对象
console.log(person.name);//zhangsan
console.log(person.__proto__===Person.prototype);//true
console.log(Object.getPrototypeOf(person)===Person.prototype);//true
console.log(Person===Person.prototype.constructor);//true
person.name='zhangsan-person';
console.log(person.name);//zhangsan-person
delete person.name;
console.log(person.name);//zhangsan
  • prototype

    ==JavaScript规定,每一个函数都有一个prototype对象属性,指向另一个对象。==

img

  • __proto__

    ==每一个JavaScript对象(null除外)在创建时,都有一个__proto__属性,指向该对象的原型。==

    实例与实例原型(person和Person.prototype)的关系(↑上方代码第6/7行↑)

img

​ *注:__proto__并不存在于Person.prototype中,实际上它是来自于Object.prototype,与其说是一个属性,不如说是一个getter/setter,当使用obj.__proto__时,可以理解成返回了Object.getPrototypeOf(obj)。

  • constructor

    ==每个原型都有一个constructor属性指向关联的构造函数;==

    (↑上方代码第8行↑)

img

​ *注:当获取person.constructor时,其实person中并没有constructor属性,不能读取到constructor属性时,会从person的原型也就是Person.prototype中读取,正好原型中有该属性。

  • 实例与原型

    ==当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。==

    (↑上方代码第9-12行↑,假设没有找到'zhansgan’,那么就会往上找原型的原型↓)

img

  • 原型链

    ==原型链的概念:实例对象与原型之间的连接。_proto_( 隐式连接 )==

    ==总结下原型链的原理:利用原型让一个引用类型继承另一个引用类型的属性和方法,在JavaScript中,用__proto__属性来表示一个对象的原型链,当查找一个对象的属性或方法时,JavaScript引擎会向上遍历原型链,直到链表结束。==

    (Object.prototype的原型就是null,所以查到Object.prototype就可以停止查找了。)

    ↓由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线↓

img

6. 继承

  • 概念

    我们想要一个对象A能够访问另一个对象B的属性,同时对象A还能够添加自己新的属性或是覆盖对象B的属性,实现这个目标的方式叫做“继承”。

  • 实现继承的方式

    ==【原型链继承】==

    • js以原型链作为实现继承的主要方法
    • 基本思想是利用原型链让一个引用类型继承另一个引用类型的属性和方法
    • 构造函数、原型、实例的关系
      • 每个构造函数都有一个原型对象
      • 原型对象都有一个指向构造函数的方法
      • 实例都包含一个指向原型对象的一个指针
    • 缺点:子类型无法给超类型传递参数;

    ==【借用构造函数 (类式继承)】==

    • 通过call,apply 改变对象的this指向,在子类型构造函数的内部调用超类型构造函数;
    • 缺点:没有原型,每次创建一个实例对象时都需要执行一遍”父类“函数,无法复用一些公用函数;

    ==【混合继承 (原型+构造函数继承) 】==

    • 混合继承是 JS 最常用的继承模式,但使用过程中会被调用两次:一次是创建子类型的时候,另一次是在子类型构造函数的内部通过call,apply 改变对象的this指向来继承;

    【原型式继承】

    • 基于一个对象上,这个对象相当于作为原型,再根据需求对得到的对象加以修改;
    • 在没有必要兴师动众创建构造函数,而只想让一个对象与另一个对象保持类似的情况下使用;
    • 拷贝继承:Object.assign();

    【寄生式继承】

    • 创建一个仅用于封装继承过程的函数,在函数内部以某种方式增强对象;

    【寄生组合式继承】

    • 混合继承最大的问题是:无论什么情况下都会调用两次超类型构造函数;
      • 一次是创建子类型原型时,一次是子函数的内部构造函数;
    • 寄生组合继承通过借用构造函数来继承属性,通过原型链混成形式来继承方法;
    • 思路:不必为了指定的子类型原型而调用超类型的构造函数;
    • 使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型;
    • 寄生组合式继承方式,跟混合式继承的区别在于,不需要在一次实例中调用两次父类的构造函数;
    • 假如说父类的构造器代码很多,还需要调用两次的话对系统肯定会有影响,寄生组合式继承的思想在于:用一个空的构造函数去取代执行了 父类构造函数;

    ==【ES6继承】==

    • ES6提供了更接近传统语言”类”的写法,引入了Class(类)这个概念,作为对象的模板;
    • 通过class关键字,可以定义类;
    • 基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到;
    • 新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已;
  • ==class继承:extends关键字+super==

    Hi~ o( ̄▽ ̄)ブ extends吸星大法!

    ==class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多,extends也是语法糖。==

    class Han {};
    class XiaoHan extends Han {};
    
    • extends继承的核心

      创建对象,创建父类原型的一个副本;

      增强对象,弥补因重写原型而失去的默认的constructor 属性;

      指定对象,将新创建的对象赋值给子类的原型;

    class XiaoHan extends Han{
    	constructor(x,y){
    		 this.x = x;		//this is not defined
    	 }
     }
    

    (👆子类必须在constructor方法中调用super方法,否则新建实例时会报错。子类自己的this对象,必须先通过父类的构造函数完成塑造,如果不调用super方法,子类就得不到this对象。)

    ==super既可以当作函数使用,也可以当作对象使用。==

    • ==第一种情况:super作为函数调用时,代表父类的构造函数;==

      super()相当于Parent.prototype.constructor.call(this)

    • ==第二种情况:super作为对象时,在普通方法中,指向父类的原型对象;==

class XiaoHan extends Han {
  constructor(x, y, sex) {
    super(x, y); // 调用父类的constructor(x, y)
    this.sex = 'girl';
  }

  toString () {
    return this.sex + ' ' + super.toString(); // 调用父类的toString()
  }
}

ES5的继承,实质上是先创造子类的实例对象this,然后再将父类的方法添加到this上(Parent.call(this)). ES6的继承,需要先创建父类的this,子类调用super继承父类的this对象,然后再加工。

7. class面向对象编程的写法

  • ES5

    function Person( name , age ) {
        this.name = name;
        this.age = age;
    }
    Person.prototype.say = function(){
        return '我叫' + this.name + ',今年' + this.age + '岁';
    }
    var  p = new Person('小韩',18);  // Person {name: "小韩", age: 18}
    p.say();                        //"我叫小韩,今年18岁"
    
  • ES6

    class Person {
      constructor( name , age ) {
        this.name = name;
        this.age = age;
      }
      say() {
        return '我叫' + this.name + ',今年' + this.age + '岁';
      }
    }
    let p = new Person('小韩',18);  // Person {name: "小韩", age: 18}
    p.say()                           //"我叫小韩,今年18岁"
    

8. Ajax

  • ==概念==

    AJAX即“Asynchronous Javascript And XML”(==异步JavaScript和XML==),是指一种创建交互式网页应用的网页开发技术。

    ==通过在后端与服务器进行少量数据交换,AJAX 可以使网页实现异步(局部)更新。==

    ==可以把一部分以前由服务器负担的工作转移到客户端,利用客户端的闲置的资源进行处理,减轻服务器和带宽的负担,节约空间和成本。==

    可以调用xml、json、php等外部数据。

    ajax配合服务器的环境

  • ==优点==

    无刷新更新数据

    异步服务器通信

    前端和后端负载平衡

    基于标准广泛支持

    界面与应用分离(前后端的分离)

  • ==缺点==

    AJAX干掉了Back和History功能,即对浏览器机制的破坏

    AJAX安全问题

    对搜索引擎支持较弱

    AJAX不是很好支持移动设备

  • ==编写步骤==

    1.创建ajax对象

    let ajax= new XMLHttpRequest();

    var ajax= new ActiveXObject("Microsoft.XMLHTTP")--IE6

    2.ajax的open方法

    ajax.open(请求类型,接口地址,是否异步);

    3.ajax的send方法

    ajax.send();

    4.设置回调函数--监听就绪状态码

    ajax.onreadystatechange=function(){

    ​ if(ajax.readyState===4){

    ​ 获取接口返回值;// JSON.parse(ajax.responseText);

    ​ }

    }

9. http状态码

  • 200 OK 请求成功
  • 301 Moved Permanently 被请求的资源已永久移动到新位置
  • 302 Move Temporarily 请求的资源临时从不同的 URI响应请求
  • 304 Not Modified 文档的内容并没有改变,则服务器应当返回这个状态码
  • 403 Forbidden 服务器已经理解请求,但是拒绝执行它
  • 404 Not Found 请求失败,请求所希望得到的资源未被在服务器上发现
  • 501 Not Implemented 服务器不支持当前请求所需要的某个功能。
  • 502 Bad Gateway 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应
  • 503 Service Unavailable 由于临时的服务器维护或者过载,服务器当前无法处理请求

10. 跨域

  • ==产生跨域的原因--同源策略==

    ==同源策略:同源策略是浏览器最核心也最基本的安全功能,阻止从一个域上加载的脚本获取或操作另一个域上的文档属性。==

  • ==产生跨域的情况==

    端口不同

    域名不同

    协议不同

    域名和域名对应的ip

    主域相同,子域不同

  • ==解决跨域的方式==

    • 后端代理(后端不存在跨域)--第三方接口

    • CORS解决跨域(xhr2)

    • jsonp(json with padding)

    ==JSONP是一个非官方的协议,通过javascript callback的形式实现跨域访问,由于同源策略的限制,XmlHttpRequest只允许请求当前源(域名协议端口)的资源,为了实现跨域请求,可以通过script标签实现跨域请求,然后在服务端输出JSON数据并执行回调函数,从而解决了跨域的数据请求。==

    ==触发第三方接口是jsonp格式的,才使用jsonp==。

    • nginx反向代理

11. href和search

  • location.href:返回完整的 URL
  • location.search:获取地址栏?后面的数据,包括问号

12. for/for…of/for…in/forEach/map的区别

Hi~ o( ̄▽ ̄)ブ废话不多说,先上表格对比

方法 可遍历对象 ES index OR key的类型 自定义属性 原型链上的自定义属性
for 数组、字符串 - number 不可遍历 不可遍历
for in 数组、字符串、对象 ES5 string 可遍历 可遍历
for of 数组、字符串 ES6 - 不可遍历 不可遍历
forEach 数组 ES5 number 不可遍历 不可遍历
map 数组 ES5 number 不可遍历 不可遍历

for in 遍历,根据key遍历 根据属性名遍历所以key的类型是string,遍历顺序也可能不是实际数组的顺序。 遍历数组时如果给数组增加了自定义属性,也会把自定义属性遍历出来,所以for in更适合遍历对象。 如果对象原型上和原型链的对象原型上有自定义属性都会遍历出来,当不想遍历原型链的属性时可已使用hasOwnProperty过滤。注意:hasOwnProperty过滤的是自身以外的属性

for (const key in arr) {
    if (arr.hasOwnProperty(key)) { // 这样就可以过滤掉原型链上的可枚举属性了
        console.log(key, arr[key]);
    }
}

for of 遍历, 根据值遍历 用来弥补for in在遍历时不能根据值遍历的不足。 由于是ES6,兼容性非常不好。 for (const iterator of obj) { console.log(iterator); }

forEach 遍历,根据index遍历 和for项目forEach除了写法没有任何优势。 forEach遍历是从头到尾遍历,没有中途跳出的方法,如:for遍历 的break。 想能上 for>forEach。 arr.forEach((val, i, arr) => { console.log(val); });

map 遍历,根据index遍历 和forEach相比,map可以返回一个新数组,新数组的内容是回调函数的返回值。 可以用来克隆数组。 arr.map((val, i, arr) => { return val * 2; });

13. SASS

  • SASS是以“.sass”后缀为扩展名,而 SCSS 是以“.scss”后缀为扩展名;

  • SASS是以严格的缩进式语法规则来书写,不带大括号({})和分号(;), SCSS 的语法书写和CSS语法书写类似;

  • SCSS的输出方式

    • 嵌套输出方式 nested
    • 展开输出方式 expanded
    • 紧凑输出方式 compact
    • 压缩输出方式 compressed
  • SASS编译方式

    • ruby命令行编译--不推荐(了解sass历史)
    • gulp编译
    • webpack编译
  • 八种核心的语法

    • 变量(普通变量、特殊变量)必须是$符号开头,变量名称和变量值之间要使用冒号

    • 注释(单行注释编译后不显示,多行注释编译后显示压缩后不显示,重要注释编译后显示压缩后显示)

    • 嵌套(选择器嵌套 &)--选择器嵌套,尽量不超过四层

    • 混合(@mixin声明混合(混合宏)/@include调用)-- 共有模块(参数不同)

    • 继承(@extend继承扩展--包括子元素,占位继承-名字前面添加%)-- 群组选择器

    • 局部文件(@import)--细分每一块,方便维护

    • 循环

      @for num from 1 to 10/@fornum from 1 through 10

      @each $animal in one, two, three, four

    • 算法(ceil,floor,random,abs......)

14. COMMONJS规范/AMD规范/CMD规范

  • ==COMMONJS规范==

    • CommonJS 最开始是 Mozilla 的工程师于 2009 年开始的一个项目,它的目的是让浏览器之外的 JavaScript能够通过模块化的方式来开发和协作---Node.js(服务器端);
    • CommonJS是最早的规范;
    • CommonJS 的规范中,每个 JavaScript 文件就是一个独立的模块.
    • CommonJS 规范的主要适用场景是服务器端编程,所以采用同步加载模块的策略;
    • CommonJS规范加载模块是同步的,只有加载完成,才能执行后面的操作;
  • ==AMD规范==

    • -AMD 是 Asynchronous Module Definition 的简称,即“异步模块定义”,是从 CommonJS 讨论中诞生的

    • AMD 优先照顾浏览器的模块加载场景,使用了异步加载和回调的方式。浏览器端异步加载库Require.js实现的就是AMD规范

    • AMD规范是非同步加载模块,允许指定回调函数;

  • ==CMD规范==

    • CMD规范,全称”Common Module Definition”,称为 通用模块加载规范 。一般也是用在浏览器端。浏览器端异步加载库Sea.js实现的就是CMD规范;
  • ==区别==

    • CommonJS 服务器端编程,采用同步加载模块,每个JavaScript文件就是一个独立的模块node.js;
    • AMD异步模块定义 require.js,浏览器端异步加载和回调;
    • CMD通用模块加载规范 sea.js,浏览器端异步加载;
  • ==RequireJS模块化开发==

    • 解决冲突和依赖
    • 引入requirejs文件 async表示异步加载,defer是IE data-main是指定主模块
    • define定义模块 参数1是数组,指明依赖;参数2回调函数
    • require调用参数 参数1是数组,表依赖的模块;参数2回调函数
    • require.config({})配置模块 第三方的公共模块
    • requireJS模块进行打包和压缩工具 -- r.js (配置build.js文件--文件名自定义)
  • ==ES6 模块化==

    • export命令用于规定模块的对外接口
    • import命令用于输入其他模块提供的功能

----------------------------学(tu)到(tou)了----------------------------

我个人开了一个微信公众号,每天会分享一些JavaScript技术相关的基础干货!

欢迎用微信搜索【易碎品啊今天吃什么研究所】或者【扫描下方二维码】关注和我一起JavaScript365!❤️