JS高级之基础

一、JS基本介绍

  • JS的用途:Javascript可以实现浏览器端、服务器端(nodejs)。。。
  • 浏览器端JS由以下三个部分组成:
    • ECMAScript:基础语法(数据类型、运算符、函数。。。)
    • BOM(浏览器对象模型):window、location、history、navigator。。。
    • DOM(文档对象模型):div、p、span。。。
  • ECMAScript又名es,有以下重大版本:
    • 旧时代:
      • es1.0。。。es3.1
    • 新时代:
      • es5
      • es6(es2015)
      • es7(es2016)、es8(es2017)

二、数据类型

  • 基本数据类型——值类型:(数字、字符串、布尔值、null、undefined)
    • undefined类型?
  • 复杂数据类型——引用类型:(对象)
    • 数组
    • 函数
    • 正则表达式
    • Date

三、对象的基本使用

3.1 创建一个对象

var student={ 
    name:"lisi" , //student有一个name属性,值为"李白"
    grade:"初一" ,
    //a、student有一个say属性,值为一个函数
    //b、student有一个say方法
    say:function(){
      console.log("你好");
    },
    run:function(speed){
      console.log("正在以"+speed+"米/秒的速度奔跑");
    }
}

3.2 对象是键值对的集合

对象是由属性和方法构成的 (ps:也有说法为:对象里面皆属性,认为方法也是一个属性)

3.3 对象属性操作

3.3.1 获取属性

  1. . 语法

    • student.name 获取到name属性的值,为:“李白”
    • student.say 获取到一个函数
  2. [] 语法

2种方式的差异:

  • .语法更方便,但是坑比较多(有局限性),比如:. 后面不能使用js中的关键字、保留字(class、this、function。。。); . 后面不能使用数字
var obj={};
obj.this=5; //语法错误
obj.0=10;   //语法错误
  • []使用更广泛
    • o1[变量name]
    • [“class”]、[“this”]都可以随意使用 obj["this"]=10
    • [0]、[1]、[2]也可以使用
      • obj[3]=50 = obj["3"]=50 (思考:为什么obj[3]=obj[“3”])
    • 甚至还可以这样用:["[object Array]"] (jquery里面就有这样的实现)
    • 也可以这样用:["{abc}"] //给对象添加了{abc}属性

3.3.2 设置属性

student[“gender”]=“男” 等价于: student.gender="男"

  • 含义:如果student对象中没有gender属性,就添加一个gender属性,值为"男"
  • 如果student对象中有gender属性,就修改gender属性的值为"男"
//例
student.isFemale=true
student["children"]=[1,2,5]
student.toShanghai=function(){   
  	console.log("正在去往上海的路上")   
}

3.3.3 删除属性

  • delete student[“gender”]
  • delete student.gender

四、通过构造函数创建对象

4.1 构造函数创建对象的例子

var xiaoming = new Object()     //==>   var xiaoming = {};  
var now = new Date() 
var rooms = new Array(1,3,5)    //==>   var rooms = [1,3,5]
var isMale=/123/;   						//==> var isMale=new RegExp("123")
//isMale是通过RegExp构造函数创建出来的对象
//isMale是RegExp构造函数的实例

以上例子中,Object、Date、Array都是内置的构造函数

五、自定义一个构造函数来创建对象

  • 构造函数
function Person(name,age){
    this.name=name;
    this.age=age;
}
var p1=new Person("赵云",18)

说明:p1就是根据【Person构造函数】创建出来的对象

5.1 构造函数的概念

任何函数都可以当成构造函数 : function CreateFunc(){ }

只要把一个函数通过new的方式来进行调用,我们就把这一次函数的调用方式称之为:构造函数的调用

  • new CreateFunc(); 此时CreateFunc就是一个构造函数
  • CreateFunc(); 此时的CreateFunc并不是构造函数

5.2 关于new Object()

new Object()等同于对象字面量{}

5.3 构造函数的执行过程

var p1=new Person();

  • 1、创建一个对象 (我们把这个对象称之为Person构造函数的实例)- _p1

  • 2、创建一个内部对象,this,将this指向该实例(_p1)

  • 3、执行函数内部的代码,其中,操作this的部分就是操作了该实例(_p1)

  • 4、返回值:

    • a、如果函数没有返回值(没有return语句),那么就会返回构造函数的实例(p1)

    • b、如果函数返回了一个基本数据类型的值,那么本次构造函数的返回值是该实例(_p1)

      function fn(){}
      var f1=new fn();    //f1就是fn的实例
      
      function fn2(){
          return "abc";
      }
      var f2=new fn2();   //f2是fn2构造函数的实例
      
    • c、如果函数返回了一个复杂数据类型的值,那么本次函数的返回值就是该值

      function fn3(){
          return [1,3,5]; 
          //数组是一个对象类型的值,所以数组是一个复杂数据类型的值
          //本次构造函数的真正返回值就是该数组,不再是fn3构造函数的实例
      }
      var f3=new fn3();   //f3还是fn3的实例吗?错
      //f3值为[1,3,5]
      

六、继承

6.1 JS中继承的概念

通过【某种方式】让一个对象可以访问到另一个对象中的属性和方法,我们把这种方式称之为继承 并不是所谓的xxx extends yyy

6.2 为什么要使用继承?

有些对象会有方法(动作、行为),而这些方法都是函数,如果把这些方法和函数都放在构造函数中声明就会导致内存的浪费

function Person(){
    this.say=function(){
        console.log("你好")
    }
}
var p1=new Person();
var p2=new Person();
console.log(p1.say === p2.say);   //false

6.3 继承的方式

6.3.1 原型链继承1

Person.prototype.say=function(){
  	console.log("你好")
}

缺点:添加1、2个方法无所谓,但是如果方法很多会导致过多的代码冗余

6.3.2 原型链继承2

Person.prototype={
    constructor:Person,
    say:function(){
      console.log("你好");
    },
    run:function(){
      console.log("正在进行百米冲刺");
    }
}

注意点:

a、一般情况下,应该先改变原型对象,再创建对象
b、一般情况下,对于新原型,会添加一个constructor属性,从而不破坏原有的原型对象的结构

6.3.3 拷贝继承(混入继承)

场景:有时候想使用某个对象中的属性,但是又不能直接修改它,于是就可以创建一个该对象的拷贝
实际运用:

  • jquery:$.extend:编写jquery插件的必经之路
  • 基于jquery封装一个表格控件
var o1={ age:2 };

var o2 = o1;
o2.age=18;      
//1、修改了o2对象的age属性,由于o2对象跟o1对象是同一个对象,所以此时o1对象的age属性也被修改了

var o3={gender:"男",grade:"初三",group:"第五组",name:"张三"};
var o4={gender:"男",grade:"初三",group:"第五组",name:"李四"};
//上述代码中,如果使用拷贝继承对代码进行优化会非常和谐

//实现拷贝继承:
//1、已经拥有了o3对象
//2、创建一个o3对象的拷贝(克隆):for...in循环
//3、修改克隆对象,把该对象的name属性改为"李四"

实现1:

var source={name:"李白",age:15}
var target={};
target.name=source.name
target.age=source.age;
  • 浅拷贝和深拷贝
    • 浅拷贝只是拷贝一层属性,没有内部对象
    • 深拷贝其实是利用了递归的原理,将对象的若干层属性拷贝出来
var students=[
    {name:"",age:""},
    {name:"",age:""}
]

上面的方式很明显无法重用,实际代码编写过程中,很多时候都会使用拷贝继承的方式,所以为了重用,可以编写一个函数把他们封装起来:

function extend(target,source){
    for(key in source){
        target[key]=source[key];
    }
    return target;
}
extend(target,source)

由于拷贝继承在实际开发中使用场景非常多,所以很多库都对此有了实现

  • jquery:$.extend

es6中有了对象扩展运算符仿佛就是专门为了拷贝继承而生

var source={name:"李白",age:15}
//让target是一个新对象,同时拥有了name、age属性
var target={ ...source }
var target2={ ...source,age:18 }

6.3.4 原型式继承

  • 创建一个纯洁的对象
  • 创建一个继承自某个父对象的子对象
var parent={ age:18,gender:"男"};
var student=Object.create(parent);
//student.__proto__===parent

使用方式:空对象:Object.create(null)

var o1={ say:function(){} }
var o2=Object.create(o1);

6.3.5 借用构造函数实现继承

场景:适用于2种构造函数之间逻辑有相似的情况
原理:函数的call、apply调用方式

function Animal(name,age,gender){
    this.name=name;
    this.age=age;
    this.gender=gender;
}
function Person(name,age,gender,say){
    this.name=name;
    this.age=age;
    this.gender=gender;
    this.say=function(){}
}

局限性:Animal(父类构造函数)的代码必须完全适用于Person(子类构造函数)
以上代码用借用构造函数实现

function Animal(name,age){
    this.name=name;
    this.age=age;
}
function Person(name,age,address){
    Animal.call(this,name);
    //this.name=name;
    //this.age=age;
    this.address=address;
}

寄生继承、寄生组合继承

七、原型链(家族族谱)

概念:JS里面的对象可能会有父对象,父对象还会有父对象,。。。。。祖先

根本:继承

  • 属性:对象中几乎都会有一个__proto__属性,指向他的父对象
  • 意义:可以实现让该对象访问到父对象中相关属性

根对象:Object.prototype

  • var arr=[1,3,5]
  • arr.proto:Array.prototype
  • arr.proto.__proto__找到了根对象
function Animal(){}
var cat=new Animal();
//cat.__proto__:Animal.prototype
//cat.__proto__.__proto__:根对象

错误的理解:万物继承自Object?

八、闭包

8.1 变量作用域

变量作用域的概念:就是一个变量可以使用的范围

JS中首先有一个最外层的作用域:称之为全局作用域

JS中还可以通过函数创建出一个独立的作用域,其中函数可以嵌套,所以作用域也可以嵌套

var age=18;     				  //age是在全局作用域中声明的变量:全局变量
function f1(){
    console.log(name);    //可以访问到name变量
    var name="周董" 			//name是f1函数内部声明的变量,所以name变量的作用域就是在f1函数内部

    console.log(name);   //可以访问到name变量
    console.log(age);    //age是全局作用域中声明的,所以age也可以访问
}
console.log(age);        //也可以访问

多级作用域

//-->1级作用域
var gender="男";
function fn(){
    console.log(gender); //可以访问
    console.log(age);		 //可以访问,但值为undefined
    console.log(height); //不能访问
  
    return function(){	//-->2级作用域
        console.log(gender); //可以访问
        console.log(age);		 //可以访问
        console.log(height); //可以访问,但值为undefined

        var height=180;	//-->3级作用域
    }
    var age=5;
}

8.2 作用域链

由于作用域是相对于变量而言的,而如果存在多级作用域,这个变量又来自于哪里?这个问题就需要好好地探究一下了,我们把这个变量的查找过程称之为变量的作用域链

简单来说,作用域链可以用以下几句话来概括:(或者说:确定一个变量来自于哪个作用域)

  • 查看当前作用域,如果当前作用域声明了这个变量,就确定结果
  • 查找当前作用域的上级作用域,也就是当前函数的上级函数,看看上级函数中有没有声明
  • 再查找上级函数的上级函数,直到全局作用域为止
  • 如果全局作用域中也没有,我们就认为这个变量未声明(xxx is not defined)

举例1:

var name="张三";
function f1(){
    var name="abc";
    console.log(name);
}
f1();

举例2:

var name="张三";
function f1(){
    console.log(name);
    var name="abc";
}
f1();

举例3:

var name="张三";
function f1(){
    console.log(name);
    var name="abc";
}
f1();

举例4:

var name="张三";
function f1(){
    return function(){
        console.log(name);
    }
    var name="abc";
}
var fn=f1();
fn();

举例5:

var name="张三";
function f1(){
    return {
        say:function(){
            console.log(name);
            var name="abc";
        }
    }
}
var fn=f1();

8.3 闭包的问题

function fn(){
    var a=5;
    return function(){
        a++;
        console.log(a);
    }
}
var f1=fn();
f1();
f1();
f1();

8.4 闭包问题的产生原因

函数执行完毕后,作用域中保留了最新的a变量的值

8.5 闭包的应用场景

  • 模块化
  • 防止变量被破坏

8.6 函数的4种调用方式

8.6.1 函数调用

var age=18;
var p={
    age:15
    say:function(){
    	  console.log(this.age);	
    }
}
var s1=p.say()
s1();       //函数调用

8.6.2 方法调用

var age=18;
var p={
    age:15
    say:function(){
        console.log(this.age);
    }
}
p.say()//方法调用

8.6.3 new调用(构造函数)

var age=18;
var p={
    age:15
    say:function(){
      	console.log(this.age);
    }
}
new p.say()//构造函数调用

8.6.4 上下文方式(call、apply、bind)

var length=21;
function f1(){
    console.log(this.length);
}
f1.call([1,3,5])
f1.apply(this)
f1.call(5)

上下文模式应用场景:

  • 一些需要指定this的情况,比如$.each方法回调函数内部的this
  • 判断数据类型:
    • Object.prototype.toString.call(1);
      在ES6的箭头函数之前的时代,想要判断一个函数内部的this指向谁,就是根据上面的四种方式来决定的

8.7 原型

  • 原型很多人开发用不到?
    • 很多人都用es6/7/8开发,确实用的比较少
    • 如果你用es5之前的版本开发代码(IE8、IE7。。。),可能天天都要写原型
    • 理解了原型,才是理解了JS面向对象的核心,没有理解原型,你就没有理解面向对象的核心
  • 类继承其实本质上还是用原型继承来(包装)的

九、对象的属性查找规则

1、首先查看本身有没有length属性
2、如果本身没有该属性,那么去它的原型对象中查找
3、如果原型对象中没有,那么就去原型对象的原型对象中查找,最终一直找到根对象(Object.prototype)
4、最终都没有找到的话,我们认为该对象并没有该属性,如果获取该属性的值:undefined

十、判断数据类型

10.1 typeof

typeof只能判断:数字、字符串、布尔值、undefined、函数

10.2 Object.prototype.toString.call()`

  • 5 ‘[object Number]’
  • “abc” ‘[object String]’
  • true ‘[object Boolean]’
  • null ‘[object Null]’
  • undefined ‘[object Undefined]’
  • [1,3,5] ‘[object Array]’
  • function(){} ‘[object Function]’
  • new Date() ‘[object Date]’
  • /abc/ ‘[object RegExp]’

10.3 Array.isArray() es5中提出来的检测数组

10.4 isNaN()

10.5 isInfinity()

十一、指向window

$.ajax({
    success:function(){
        console.log(this);        //window
    }
})
[1,3,5].map(function(){
    console.log(this);      //window
})
$("div")
`${$}`
`${$('div')}`

十二、global和window的区别

global是es中全局作用域中的根对象

  • 但是nodejs里面,global全是表示全局变量的载体
  • 浏览器端的js里面,全局变量都放在了window中,浏览器中不存在global对象

十三、框架的封装

  • 参考jquery
  • 作为JS高级的案例
  • $(“div”).css(“color”,“red”)
  • $(“div”).click(function(){ … })
(function(window){
    function Fn(selector){}
    Fn.prototype = {
        init(selector){
            var elements = document.querySelectorAll(selector);
            for( var i = 0 ; i < elements.length ; i++ ){
                this[i] = elements[i];
                this.length++;
            }
        },
        length : 0
    }

    function jQuery(selector){
        return new Fn(selector)
    }
    window.$ = window.jQuery = jQuery;
})(window)
$("div")
原文地址:https://www.cnblogs.com/daozhangblog/p/12446350.html