JS(二)基本知识

一 预解释

1、预解释(变量提声):在当前的作用域中,JS代码执行之前,浏览器首先会默认的把所有带var和function的进行提前的声明或者定义。
2、var和function在预解释的时候操作不同:

  • var:在预解释的时候只是提前的声明
  • function:在预解释的时候提前的声明和定义都完成了。

3、预解释只发生在当前的作用域下,例如,开始只对window下的进行预解释,只有函数执行的时候才会对函数中的代码进行预解释。
4、预解释的时候不管条件是否成立,只要是带var的都要进行提前的声明。
5、预解释的时候只预解释等号左边的,右边的是值,不参与预解释。
6、函数体中return下面的代码虽然不在执行了,但是需要进行预解释,return后面跟的是返回值,所以不进行预解释。
7、在预解释的时候,如果名字已经声明过了,不需要重新声明,但是需要重新的赋值。在JS中,如果变量的名字和函数的名字重复了,也算冲突。

1、如何区分私有变量和全局变量?
(1)在全局作用域下声明(预解释的时候)的变量是全局变量。
(2)在“私有作用域中声明的变量”“函数的形参”都是私有变量。

2、当函数执行的时候(直接目的:让函数体中的代码执行),首先会形成一个新的私有作用域,然后按照如下步骤执行:
(1)如果有形参,先给形参赋值,
(2)进行私有作用域中的预解释,
(3)私有作用域中的代码从上到下执行
......
函数形成的一个新的私有的作用域保护了里面的私有变量不受外界的干扰(外面改变不了私有的,私有的也修改不了外面的),把这种机制称为闭包。

三作用域链

在私有作用域中,我们代码执行的时候遇到了一个变量,首先我们需要确定他是否为私有的变量,如果是私有的变量,那么和外面的任何东西没有任何的关系,如果不是私有的,则往当前作用域的上级作用域进行查找,如果上级作用域也没有则继续查找,一直找到window为止...(作用域链)。

在全局作用域中,带var和不带var的关系?
区别:带var的可以进行预解释,所以在赋值的前面执行不会报错;不带var的是不能进行预解释的,在前面执行会报错。
关系:不带var声明,num=12; ---->相当于给window增加了一个叫做num2的属性名,属性值是12 。带var声明,var num=12;----->首先相当于给全局作用域增加了一个全局变量num,但是不仅如此,他也相当于给window增加了一个属性名num2,属性值是12。

如何查找当前作用域的上一级作用域?
看当前函数是在哪个作用域下定义的,那么他的上级作用域就是谁,和函数在哪执行的没有任何关系。

四 JS内存

1、内存分类
栈内存:用来提供一个供JS代码执行的环境。--->作用域(全局作用域/私有作用域)
堆内存:用来存储引用数据类型的值,对象存储的是属性名和属性值,函数存储的是代码字符串。
2、内存释放
(1)堆内存内存释放:对象数据类型或者函数数据类型在定义的时候首先都会开辟一个堆内存,堆内存有一个引用的地址,如果外面有变量等知道了这个地址,我们就说这个内存被占用了,就不能销毁了。
我们想要让堆内存释放/销毁,只需要把所有引用他的变量赋值为null即可,如果当前的堆内存没有任何东西被占用了,那么浏览器会在空闲的时候把他销毁...
(2)栈内存
全局作用域:只有当页面关闭的时候全局作用域才会销毁。
私有作用域(只有函数执行才会产生私有作用域):一般情况下,函数执行会形成一个新的私有作用域,当私有作用域中的代码执行完成后,我们当前作用域都会主动的进行释放和销毁。
但是还是存在特殊的情况的:
当前私有作用域中的部分内存被作用域以为的东西占用了,那么当前的作用域就不能销毁了。
a)函数执行返回了一个引用数据类型的值,并且在函数的外面被一个其他的东西给接收了,这种情况下一般形成的私有作用域都不会被销毁。
b)在一个私有的作用域中给DOM元素的事件绑定方法,一般情况下我们的私有作用域都不销毁。
c)下述情况属于不立即销毁--->fn返回的函数没有被其他的东西占用,但是还需要执行一次,所以暂时不销毁,当返回的值执行完成后,浏览器会在空闲的时候把他销毁了。---->“不立即销毁”。

五 this关键字

  • JS中的this代表的是当前行为执行的主体,JS中的context代表的是当前行为执行的环境(区域)。
  • this是谁和函数在哪定义的和在哪执行的都没有任何的关系。

如何区分this?
1、函数执行,首先看函数名前面是否有“.”,有的话,“.”前面是谁,this就是谁,没有的话this就是window。
2、自执行函数中的this永远是window。
3、给元素的某一个事件绑定方法,当事件触发的时候,执行对应的方法,方法中的this就是当前的元素。
4、在构造函数模式中,类中(函数体中)出现的this.xxx = xxx中的this是当前类的一个实例。

六 类的继承、封装、多态

1、函数的封装:把实现同一件事情的相同的代码放到一个函数中,以后如果在想实现这个功能,不需要重新编写这些代码了,只需要执行当前的函数即可。“低耦合高内聚”。
2、继承:子类继承父类中的属性和方法。
3、多态:当前方法的多种形态。
4、后台语言中,多态包含重载和重写。
5、重载:两个方法名相同,两个方法的参数不一样,执行的时候调用不同的方法来执行。
6、JS中不存在重载,方法名如果一样的话,后面的会把前面的覆盖掉,最后只保留一个。
7、JS中有一个操作类似重载但是不是重载,我们可以根据传递的参数不同,实现不同的功能。
8、重写:子类重写父类的方法。

七 构造函数模式

构造函数模式的目的就是为了创建一个自定义类,并且创建这个类的实例。
构造函数模式和工厂模式的区别:

  • 1、执行的时候:
    普通函数执行-->createPerson()
    构造函数模式-->new CreatePerson() 通过new执行后,我们的CreatePerson就是一个类了。(一般,类的首字母大写。)
    而函数执行的返回值就是CreatePerson这个类的一个实例。
    JS中所有的类都是函数数据类型的,他通过new执行变成了一个类,但是他本身也是一个普通的函数。JS中所有的实例都是对象数据类型的。

  • 2、在函数代码执行的时候:
    相同:都是形成一个私有作用域,然后经历了形参赋值、预解释、代码从上到下执行(类和普通函数一样,他也有普通的一面)
    不同:在代码执行之前,不用自己手动的创建对象了,浏览器会默认的创建一个对象数据类型的值(这个对象其实就是我们当前类的一个实例)。
    接下来代码从上到下执行,以当前的实例为执行的主体(this代表的就是当前的实例),然后分别的把属性名和属性值赋值给当前的实例,最后浏览器会默认的把创建的实例返回。

  • 1、hasOwnproperty:用来检测某一个属性是否为这个对象的“私有属性”,这个方法只能检测私有的属性。

console.log(f1.hasOwnproperty("getX"));
  • 2、检测某一个属性是否为该对象的“公有属性” --->>hasPubProperty
function hasPubProperty(obj,attr) {
    //首先保证是他的一个属性并且还不是私有的属性,那么只能是公有的属性了
    return (attr in obj) && !obj.hasOwnProperty(attr) ;
}
console.log(hasPubProperty(f1,"getX");
  • 3、instanceOf:检测某一个实例是否属于这个类
  • 4、in:用来检测某一个属性是否属于这个对象(attr in object),不管是私有的属性还是公有的属性,只要存在,用in来检测都是true。

八 原型链模式

构造函数模式中拥有了类和实例的概念,并且实例和实例之间是相互独立分开的,解决了实例识别的问题。
基于构造函数模式的原型模式解决了方法或者属性公有的问题,把实例之间相同的属性和方法提取成公有的属性和方法,想让谁公有就把他放在CreatePerson.prototype上即可。

  • 1、每一个函数数据类型(普通函数、类)都有一个天生自带的属性:prototype(原型),并且这个属性是一个对象数据类型的值。
  • 2、并且在prototype上浏览器天生给他加了一个属性constructor(构造函数),属性值是当前函数(类)本身。
  • 3、每一个对象数据类型(普通的对象、实例、prototype、数组、正则、函数)也天生自带一个属性:_proto_ ,属性值是当前实例所属类的原型(prototype)。
function Fn() {
    this.x = 100;
}
Fn.prototype.getX = function () {
    console.log(this.x);
};
var f1 = new Fn();
var f2 = new Fn();

8.png

所有对象数据类型的实例都是object。
1、
1)f1 instanceOf Object --> true 因为f1通过_proto_ 可以向上级查找,不管有多少级,最后总能找到object。
2)object是JS中所有对象数据类型的基类(最顶层的类)。
3)在object.prototype上没有_proto_这个属性。
2、原型链模式

f1.hasOwnProperty("x");       

3、hasOwnProperty是f1的一个属性,但是我们发现在f1的私有属性上并没有这个方法,那如何处理呢?

  • 通过 “对象名.属性名” 的方式获取属性值的时候,首先在对象的私有的属性上进行查找,如果私有中存在这个属性,则获取的是私有的属性值。如果私有中没有这个属性,则通过_proto_找到所属类的原型(类的原型上定义的属性和方法都是当前实例的公有的属性和方法),原型上存在的话,获取的是公有的属性值;如果原型上也没有,则继续通过原型上的_proto_继续向上查找,一直找到object.prototype为止...
    这种查找的机制就是我们的“原型链模式”
    在IE浏览器中,原型模式也是同样的原理,但是IE浏览器怕你通过_proto_把公有的修改,禁止我们使用_proto_ 。
    4、在原型模式中,this常用的有两种情况:
    1)在类中this.xxx = xxx; this---->当前类的实例
    2)某一个方法中的this---> 看执行的时候 “.” 前面是谁this就是谁。
  • a)需要先确定this的指向(this是谁)。
  • b)把this替换成对应的代码。
  • c)按照原型链查找的机制,一步步的查找结果。

批量设置公有属性
1、起一个别名:

function Fn() {
    this.x = 100;
}
var pro = Fn.prototype;   把原来原型指向的地址赋值给Pro,现在他们操作的是同一个内存空间
pro.getX = function () {

};
pro.getY = function () {

};
pro.getZ = function () {

};

9.png

2、重构原型对象的方式:自己新开辟一个堆内存,存储我们共有的属性和方法,浏览器原来给Fn.prototype开辟的那个替换掉。

  • (1)只有浏览器天生给Fn.prototype开辟的堆内存里面才有constructor,而我们自己开辟的这个堆内存没有这个属性,这样constructor指向就不再是Fn而是Object了。
    console.log(f.constructor) ->没做处理之前Object
    为了和原来的保持一致,我们需要手动的增加constructor这个属性。
  • (2)用这种方式给内置类增加公有的属性。我们这种方式会把之前已经存在于原型上的属性和方法给替换掉,所以我们用这种方法修改内置类的话,浏览器是给屏蔽掉的。但是我们可以一个个的修改内置的方法,当我们通过下述方式在数组的原型上增加方法,如果方法名和原来的内置重复了,会把人家内置的修改掉。---->我们以后在内置类的原型上增加方法,命名都需要加特殊的前缀。
function Fn() {
    this.x = 100;
}
Fn.prototype = {
    constructor:Fn,   为了和原来的保持一致,我们需要手动的增加constructor这个属性。
    a: function () {
},
<span class="hljs-attr">b</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{

};

};

10.png

1、for in循环在遍历的时候默认的话可以把自己私有的和在他所属类原型上扩展的属性和方法都可以遍历到,但是一般情况下,我们遍历一个对象只需要遍历私有的即可,我们可以使用以下的判断进行处理:

if(obj.propertyIsEnumerable(key))  {
    console.log(key);
}
或者
if(obj.hasOwnProperty(key))  {
    console.log(key);
}

2、Object.create():创建一个拥有指定原型和若干个指定属性的对象。

var obj = { getX: function () { } };
function Fn()  {
};
Fn.prototype = obj;

原型链模式的常用继承方式

1、原型继承:是我们JS中最常用的一种继承方式。

  • 子类B想要继承父类A中的所有的属性和方法(私有和公有),只需让B.prototype= new A即可。
function A() {
    this.x = 100;
}
A.prototype.getX = function () {
    console.log(this.x);
};
function B() {
    this.y = 200;
}
B.prototype = new A;
  • 原型继承的特点:他是把父类中私有的和公有的都继承到了子类原型上(子类公有的)。
  • 核心:原型继承并不是把父类中的属性和方法克隆一份一模一样的给B,而是让B和A之间增加了原型链的连接,以后B的实例n想要A中的getx方法,需要一级级的向上查找来使用。

2、call继承:把父类私有的属性和方法,克隆一份一模一样的,作为子类私有的属性。

function A() {
    this.x = 100;
}
A.prototype.getX = function () {
    console.log(this.x);
};
function B() {
    this.x = 200;
    A.call(this);
}
var n = new B;

3、冒充对象继承:把父类私有的和公有的克隆一份一模一样的,给子类私有的。

function A() {
    this.x = 100;
}
A.prototype.getX = function () {
    console.log(this.x);
};
function B() {
    var temp = new A;
    for (var key in temp) {
        this[key] = key[key];
    }
    temp=null;
}

4、混合模式继承:原型继承+call继承
5、寄生组合式继承

function A() {
    this.x = 100;
}
A.prototype.getX = function () {
    console.log(this.x);
};
function B() {
    A.call(this);
}
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
var n = new B;
console.dir(n);
//兼容IE浏览器
fuction objecrCreate(o) {
    function fn() {
}
fn.prototype = o;
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> fn;

}

所有类的原型都是对象数据类型的,所有类本身都是函数数据类型的。

函数本身属性

  • length:0 形参的个数
  • name:“Fn” 函数名
  • prototype:类的原型,在原型上定义的方法都是当前Fn这个类实例的公有方法。
  • _proto_:把函数当做一个普通的对象,指向function这个类的原型。

1、call方法
首先我们让原型上的call方法执行,在执行call方法的时候,我们让fn方法中的this变为第一个参数值obj,然后再把fn这个函数执行。

var obj = {name:"zzh"};
function fn() {
    console.log(this);
}
fn.call(obj);

2、apply方法
apply和call方法的作用是一模一样的,都是用来改变方法的this关键字并且把方法执行,而且在严格模式下和非严格模式下对于第一个参数是null/undefined这种情况的规律都是一样的。

fn.call(obj,100,200);
fn.apply(obj,[100,200]);
  • apply给fn传递参数时,是把要传递的参数值统一的放在一个数组中进行操作,但是也相当于一个个的给fn的形参赋值。
    3、bind方法
    IE6-8不兼容。和call/apply类似都是用来改变this关键字的。
fn.bind(obj,1,2);
只是改变了fn中的this为obj,并且给fn传递了两个参数值1,2,但是此时并没有把fn这个函数执行。
var tempFn = fn.bind(obj,1,2);
tempFn();
执行bind会有一个返回值,这个返回值tempFn就是我们把fn的this改变后的结果
  • bind会预处理:事先把fn的this改变为我们想要的结果,并且把对应的参数值也准备好,以后要用到了,直接的执行即可。

严格模式下的this相对于非严格模式下的this主要区别在于:
对于JS代码中没有写执行主体的情况下,非严格模式下默认都是window执行的,所以this指向的就是window,但是在严格模式下,没有写就是没有执行主体,this指向的是undefined。

十 try/catch

  • 代码执行报错,如果用try/catch捕获了异常信息,不影响下面的代码继续执行。
  • 如果try中的代码执行出错了,会默认的去执行catch中的代码。
try {
    console.log(num);
} catch(e)  {
    形参必须要写,一般起名为e
    console.log(e.massege);
    e.massege可以收集当前代码报错的原因,把其进行统计
}
consolo.log("zzh");
try {
    console.log(num);
} finally  {
    一般不用:不管try中的代码是否报错,都要执行finally中的代码
}

有时候既想捕获到错误的信息,又不想让下面的代码执行,所以需要终止代码执行。

try {
    console.log(num);
} catch(e)  {
    throw new Error("当前网络繁忙,请稍后再试");
}

十一 JSON

11.png

JSON不是一个单独的数据类型,他只是一个特殊的数据格式,他是对象数据类型的。

var obj = {name:"zzh",age:18};
var jsonObj = {"name":"zzh","age":18}; 
  • JSON格式的对象,相对于普通的格式来讲,只是属性名用双引号(只能是双引号)包起来了。
    1、在window浏览器对象中,提供了一个叫做json的属性,它里面提供了两个方法。----->window.JSON
    (1)JSON.parse:把JSON格式的字符串转换为JSON格式的对象。
var str = ' {"name":"zzh","age":18} '; 
JSON.parse (str);
eval("("+str+")"); 一定要记住使用eval的话,不要忘记手动加一个小括号

(2)JSON.stringify:把JSON格式的对象转换为JSON格式的字符串
3、DOM回流(重排、reflow):当页面中的HTML结构发生改变(增加、删除元素、位置发生改变......),浏览器都需要重新计算一遍新的DOM结构,重新对当前的页面进行渲染。
重绘:某一个元素的部分样式发生改变了(背景颜色...),浏览器只需要重新的渲染当前的元素即可。

十二

DOM映射的机制:页面中的标签和JS中获取到的元素对象(元素集合)是紧紧绑定在一起的,页面中的HTML结构改变了,JS中不需要重新获取,集合里面的内容也会跟着自动改变。

猜你喜欢

转载自blog.csdn.net/ganlubaba666/article/details/84035540
今日推荐