记录所有JavaScript的初级知识点,方便更加了解JavaScript以及更好的巩固JavaScript基础
JavaScript
- ECMAScript(ES):规定了JS的一些基础核心的知识(变量、数据类型、语法规范、操作语句等)
- DOM: document object model文档对象模型,里面提供了一些属性和方法,可以让我们操作页面中的元素
- BOM: browser object model浏览器对象模型,里面提供了一些属性和方法,可以让我们操作浏览器
变量和常量
- 变量:值是可以改变的
- 常量:值是不可以改变的
1
2//->JS中定义变量的形式
var 变量名 : 值 变量其实只是一个无意义的名字, 它所代表的意义都是其存储的那个值
1 | num = 13 //=>让原有num存储的变量改为13(变量只能代表一个值) |
JS中的数据类型
- 1、基本数据类型(值类型)
- number 数字
- string 字符串
- booler 布尔
- null 空对象指针
- undefined 未定义
- 2、 引用数据类型
- object 对象数据类型有{} 普通对象、[] 数组/^、$/ 正则……
- function 函数数据类型有 function(){}普通函数 、类等
基本类型是值类型 直接操作值的 引用类型操作的是地址所以叫数据引用类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23//-> 变量名没有什么实际意义 变量名所代表的否是他所存储的值
var a= 12;
var b = a;
b=13
console.log(a)//12
//->引用类型操作的是地址所以叫数据引用类型
var n = {name: '李四'};
var m = n;
m.name= '张三';
console.log(n.name)//张三
//->对象是先开空间
var m = {name:'小李'}
var n = m //n指向m的空间地址
n = {name:'小黑'} n又开辟一个新的内存空间地址改变地址指向
console.log(m.name)//小李
function fn(){
console.log(1)
}
console.log(fn)//输出函数本身
console.log(fn())//把当前函数执行的返回结果(return什么返回就是什么,不写默认undefined)当浏览器加载HTML页面的时候,首先会提供一个供全局JS代码执行环境-全局作用域window
- 引用数据类型是按照空间地址(引用地址)来操作的:var n = {name:李四};
- 1、先创建一个变量n
- 2、浏览器首先会开辟一个新的存储空间(内存空间),目的是把对象中需要存储的内容(键值对)分别的存储在这个空间中,为了方便后期找到这个空间,浏览器给空间设定一个地址(16进制的)
- 3、把空间的地址赋值给了变量
JS中的堆栈内存
- 栈内存:俗称叫做作用域(全局作用域/私有作用域)
1 为js代码提供执行环境(执行js代码的地方)
2 基本数据类型值是直接放在栈内存中的- 堆内存:存储引用数据类型值的(相当于一个存储的仓库)
1 对象存储的是键值对
2 函数存储的是代码字符串
在项目中 我们的内存越少性能越好 我们需要把一些没用的内存处理掉
[堆内存]
- var o={} 当前对象对应的堆内存被变量o占用,无法销毁
- o = null; null空对象指针(不只想任何的堆内存),此时上一次的堆内存就没有被占用了
- 谷歌浏览器会在空闲时间把没有被占用的堆内存自动释放(销毁/回收)
[栈内存] - 一般情况下,函数执行形成的内存,函数执行完,浏览器会把形成的栈内存自动释放,有时候执行完成,栈内存不能被释放
- 但是还有其他情况:函数执行完成,当前私有作用域(栈内存)中的某一部分内容被意外其他的东西(变量或者元素事件)占用了,当前的栈内存就不能被释放掉,也就形成了不销毁的私有作用域(里边的私有变量也不会销毁)
- 全局作用域在加载页面的时候执行,关掉页面的时候销毁
1 函数作用域以及闭包作用域链详解
一、变量提升(ES6的let和const不存在变量提升)
- 在当前作用域中,js代码自上而下执行之前,浏览器首先会把所有带var/function关键字的进行提前的声明或者定义
- 声明(declare):var num;在当前作用域告知我有num这个名了
- 定义(difined): num = 12 把声明的名字赋一个值
- 带var关键字的只是提前声明一下:带function关键字的在变量提升阶段把声明和定义都完成了(变量提升的核心)
在私有作用域声明的变量都是私有变量;闭包机制:私有作用域保护里边的私有变量不受外界的干扰
- 在全局作用域当中,我们声明一个变量,相当于给全局对象window增加了一个属性名 var a; <=>window.a=undefined
1
2
3
4
5
6
7
8
9
10console.log(num)
console.log(fn)
var num = 13;
function fn(){
console.log(a)
var a = 10
console.log(a)
}
fn()1.1、定义变量带var和不带var的区别
- 带var:在当前作用域当中声明一个变量,如果当前是全局作用域,相当于给全局对象window增加了一个属性名 var a/window.a
- 不带var:在全局作用域中仅仅是给全局对象设置了一个新的属性名,省略了window.a
1.2、闭包和作用域链
- 闭包(理论):函数执行形成私有作用域,保护里边私有变量不受外界干扰的这种机制叫闭包
- 但是在市面上,99%的开发者认为:函数执行,形成一个不销毁的作用域,除了保护私有变量意外还可以存储一些内容,这样的模式才是闭包
闭包和作用域链
- 函数执行形成一个私有的作用域链(保护私有变量),进入到私有作用域中,首先变量提升(声明过的变量是私有的),接下来自上而下执行
- 1、执行的时候遇到一个变量,如果这个变量是私有的那么按照私有处理即可
1
2
3
4
5
6
7
8
9
10function fn(){
//->私有作用域
//->变量提升 var a;(私有变量)
console.log(a)//->undefined
var a = 12
console.log(a)//->12
}
fn()
console.log(a)//->not defined
//->闭包机制:私有作用域保护里边的私有变量不受外界的干扰 - 2、如果当前这个变量不是私有的,我们需要向他的上级作用域进行查找,如果上级也没有,则继续向上查找,一直找到window全局作用域为止,我们把这种查找机制叫做作用域链
- 2-1、如果上级作用域有这个变量,我们当前操作的是上级作用域中的变量(我们在当前作用域把值改了,相当于把上级作用域中的这个值给改了)
- 2-2、如果上级作用域没有这个变量(找window也没有)
- 变量=值:相当于给window设置了一个属性,后在操作window下就有了
- alert(变量)想要输出这个变量会报错
1
2
3
4
5
6
7function fn(){
console.log(a)//->not defined 私有和全局都没有
a =12;//->window.a=12
console.log(a)//->12
}
fn();
console.log(A)//->121.3、只对等号左边的进行变量提升
- 1 =:赋值、左边的是变量右边的是值
- 变量提升案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//->声明
var num ;
// 开辟空间地址存储字符串然后赋值给fn
// function关键字的在变量提升阶段把声明和定义都完成了
function fn(){
console.log(a)
var a = 10
console.log(a)
}
//然后代码自上而下执行
num => undefined
fn => 函数本身
num = 13;->变量提升阶段已经完成声明了,此处不再需要var,但是没有定义过需要重新赋值
//->遇到创建fn代码直接跳过,在提升阶段声明和定义已经都做过了
fn();//执行会开辟一个私有作用域,然后开始变量提升var a;然后代码自上而下执行 - 匿名函数:函数表达式(把函数当作一个值赋给变量或者其他内容)
1
2
3
4
5var fn//->变量提升 undefined
var fn = function(){
}
console.log(fn)//->函数本身 - 只对等号左边的进行变量提升,右边是值不会提前声明什么
- 真是项目中应用这个原理,我们创建函数的时候可以使用函数表达式
- 1·因为只能对=号左边的进行提升,所以变量提升完成后,当前的函数只是声明了没有定义,**
想要执行函数只能放在赋值的代码之后
**(放在前边执行相当于undefined执行,会报错) - 2·这样能让我们的代码逻辑更加严谨,以后想要知道一个函数做了什么功能只需要向上查找定义的部分即可(不会存在定义的代码在执行下面的情况)
1.4不管条件是否成立都要进行变量提升
- 不管条件是否成立,判断体中出现var/function都会进行变量提升。
- 但是在最新浏览器中function声明的变量只能提前声明不能定义了(前提函数实在判断体中)
1
2
3
4
5
6
7
8
9
10
11
12
13
14console.log(num)//undefined
console.log(fn)//undefined
if(1==1){
console.log(num)//undefined
console.log(fn)//函数本身
var num =12;
function fn(){
}
console.log(num)//12
console.log(fn)//函数本身
}
console.log(fn)//函数本身(因为fn是全局的) - 代码执行到条件的地方
- [条件不成立] 进入不到判断体,赋值的代码执行不了,此时之前声明的变量或者函数依然是undefined
- [条件成立] 进入条件判断体的第一件事是不是代码执行而是把之前变量提升没有定义的函数定义了(进入条件判断体当中函数就定义了迎合ES6的块级作用域)
老版本浏览器不管条件是否成立都会进行变量提升(和新版本不一样的地方是 新版本function只是声明,老版本function是声明加定义)
1
2
3
4
5
6
7
8function fn(){
//变量提升 var num;
//return后边跟着的是返回值就不进行预解释了
return function(){
}
var num = 100
}函数体重的return后边的代码就不会执行了但是还会进行变量提升,return后边跟着的是返回值就不进行预解释了
1.5变量提升重名的情况
- 在变量提升阶段,如果名字重复了,不会重新进行声明,但是会重新定义,(后面赋的值会把前边的值替换掉)
1
2
3
4
5
6
7
8
9
10
11
12//->变量提升 fn=aaafff111 (aaafff222、aaafff333、aaafff444)
fn()//->4
function fn(){console.log(1)}
fn()//->4
function fn(){console.log(2)}
fn()//->4
var fn = 13 //-> fn = 13
fn()//-> 13不能执行 报错TypeError 后边不会再走
function fn(){console.log(3)}
fn()
function fn(){console.log(4)}
fn() - 自执行函数 定义和执行一起完成了
1
2
3
4
5(function(){})(100)
~function(){}(100)
+function(){}(100)
-function(){}(100)
!function(){}(100)二、作用域[scope]
- [栈内存]
- 全局作用域:window
- 私有作用域:函数执行
- ES6块级作用域:使用let创建变量存在块级作用域
- [作用域链]
- 当前作用域代码执行的时候遇见一个变量,我们首先看一下他是否属于私有变量,如果是当前作用域的私有变量,那么以后在私有作用域中在遇到这个变量都是操作私有的(**
闭包
**私有作用域保护私有变量不受外界干扰),如果不是私有的变量,向其上级作用域查找,也不是上级作用域私有的,继续向上寻找,一直到window全局作用域为止,我们把这种一级一级向上查找的机制叫做作用域链,全局下有酒操作全局的,全局下没有(设置:给全局对象window增加属性名 && 获取:会报错)2.1、私有变量有哪些
- JS中所有的变量有且只有两种
- 1、在私有作用域变量提升阶段,声明过的变量(或者函数)
- 2、形参也是私有变量
1
2
3
4
5
6
7
8//变量提升 var result; fn = aaafff111
functin fn(num1,num2){
var total = num1 + num2
return total
}
var result = fn(100,200)
// result fn是全局变量
// total num1 num2是私有变量2.2函数执行形成一个私有作用域、形参是私有变量
- 1、形参赋值
- 2、变量提升
- 3、代码自上执行
- 4、当前栈内存(私有作用域)销毁或者不销毁
1
2
3
4
5
6
7
8
9
10
11
12
13//全局变量提升 var x; var y; var z ; fn =AAAFFF111 声明加定义
var x=10,y=20,z=30
function fn(x,y) {
//->私有作用域 形参赋值 x=10 y=20(x/y 形参都是私有变量)
//->变量提升 var x;(存在用名不重复声明)
console.log(x, y, z);//x/y是私有的10 20 全局的z30
var x= 100;
y=200;
z=300;
console.log(x, y, z);//100 200 300
}
fn(x, y, z)//fn传递的是实参(实参都是值)fn(10,20,30)
console.log(x, y, z);//10 20 3001
2
3
4
5
6
7
8
9
10
11
12
13function fn(b) {
//私有作用域
//形参赋值 b =1(私有变量)
//变量提升 b = AAAFFF111 (函数声明加定义 此处赋值操作替换了形参的赋值内容)
console.log(b);//函数本身
function b() {
//私有作用域
//形参赋值和变量提升都没有 b不是私有的向上级作用域查找
console.log(b);//函数本身
}
b();
}
fn(1)2.3如何查找作用域
- 一个函数执行形成私有作用域(A),A的上级作用域是谁,和他在哪执行的没关系,主要看他是在哪定义的,在那个作用域下定义的当前A的上一级作用域就是谁
1
2
3
4
5
6
7
8
9var n = 10
function sum() {
console.log(n)
}
//sum()//->10
~function(){
var n = 100
sum()//->10 sum的宿主环境是当前自执行函数形成的私有作用域
}();1
2
3
4
5
6
7
8
9
10
11
12
13var n = 10
var obj = {
n:20,
fn:(function(){
var n = 30;
//上一级作用域是全局作用域 obj只是堆内存
return function(){
//上一级作用域是自执行函数
console.log(n)
}
})()
};
obj.fn()//->30 - 1、提供一个js的执行环境window全局作用域
- 2、变量提升 var n; var obj;
- 3、赋值操作 n=20 obj是数据数据引用类型需要开空间内存地址(会返回16进制的空间地址 AAAFFF111) 在地址没有开启完obj是没有值的
- 4、开辟obj空间内存地址(AAAFFF111) n=20 fn是自执行函数,也需要开启空间内存地址(会返回16进制的空间地址 AAAFFF222)
- 5、开辟fn空间地址进行形参赋值、变量提升 var n;定义n=30 return函数(函数属于数据引用类型需要开空间地址)
- 6、return function的空间地址是(AAAFFF111) 自执行函数相当与return AAAFFF111
- 7、自执行函数返回结果相当于 AAAFFF111
- 8、然后obj = AAAFFF000
1
2
3
4
5
6
7
8
9
10
11
12var n = 10
var obj = {
n:20,
fn:(function(n){
return function(){
//上一级作用域是自执行函数
console.log(n)
}
})(obj.n)
};
obj.fn()
// Uncaught TypeError: Cannot read property 'n' of undefined - 所有的空间内存开启完obj才有值、如图所示
三、闭包的作用
- 保护内部私有变量不受外界干扰
3.1 闭包的作用之 — 保护
- 形成私有作用域,保护里边的私有变量不受外界干扰
- jQuery代码片段(核心原理 利用闭包的机制保护私有变量 部分核心代码)
1
2
3
4
5
6
7
8
9
10(functrion(){
var jQuery = function(window){
...
}
...
//->私有变量外部或者全局想要访问需要return
//->但是jQuery把私有变量jQuery赋值给了window下的一个属性jQuery和属性$
window.jQuery = window.$ = jQuery
})(window)
//jQuery()执行 $()执行 - Zepto代码片段
1
2
3
4
5
6
7
8
9var Zepto = (function(){
var Zepto = function(){
...
}
...
return Zepto
})();
//Zepto执行
//也能用$ window实现的 - 真是项目中,我们利用这种保护机制实现团队协作开发(避免多人同一个命名导致代码冲突问题)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//->A
~function(){
//A写的代码
function fn(){
}
window.fn = fn
}()
//->B
~function(){
//B写的代码
function fn(){
}
//B想要调取A写的fn
fn();//用的是B自己的fn
window.fn();
}() - 但是这种写法很繁琐,如果要暴露10个fn、20fn 用单例模式可以解决这种情况
3.2 i++ 和 ++i的区别
- i++:先拿原有i的值和其他值进行运算,运算完成在自身累加(i++保留旧值)
- ++i:先自身累加1,然后再拿完成累加完成的结果和其他值进行运算(++i保留新值)
1
2
3
4
5
6
7var i =5;
console.log(5 + i++) //10 5+(i++)也一样
console.log(i) //6
var i =5;
console.log(5 + ++i);//11
console.log(i) //63.3 闭包的作用之 — 保存
- 函数执行形成一个私有作用域,函数执行完,形成的这个栈内存一般情况下都会自动释放,
- 但是还有其他情况:函数执行完成,当前私有作用域(栈内存)中的某一部分内容被意外其他的东西(变量或者元素事件)占用了,当前的栈内存就不能被释放掉,也就形成了不销毁的私有作用域(里边的私有变量也不会销毁)
1
2
3
4
5
6
7
8
9
10
11
12
13//->变量提升函数声明加定义重新开启堆内存空间存储字符串
//->引入16进制的地址var f; fn = AAAFFF000
function fn() {
var i = 1;
return function (n) {
console.log(n+ i++)
}
}
var f = fn()//形成私有作用域
f(10)//11 同一空间地址 i++保留旧值后 运算完成i自身在累加
fn()(10)//11 //形成私有作用域
f(20)//22
fn()(20)//21 - 函数执行形成了一个私有作用域,如果作用域中的部分内容被以外的变量占用了,当前作用域不销毁
- [形式]函数执行返回了一个
数据引用类型堆内存的地址
(并且堆内存隶属于这个作用域)在外面有一个变量接受了这个返回值,此时当前作用域就不能销毁(想要销毁,只需要让外部的变量赋值为null,也就不占用当前作用域中的内存了) - 1、浏览器会给js执行环境为全局作用域 变量提升
- 2、变量提升 var f; fn = AAAFFF000 (函数是声明+定义需要开启堆内存和指向16进制的内存地址)
- 3、f赋值fn 开辟私有空间A;形参赋值、变量提升var i;重上到下执行 i=1;return function需要在开一个堆内存;内存存储console.log(n+ i++)和内存地址 AAAFFF111
- 4、相当于f=fn()(f=AAAFFF111这个内存地址) fn这个地址被外界f占用所以不能销毁栈内存
- 5、f(10)形成私有作用域形参赋值n=10 10+i++,i是空间A的i,因为没有销毁i累加为2 然后fn(10)运行完销毁 fn中的i累加为2
- 6 fn(10)开辟私有空间B:形参赋值、变量提升var i;重上到下执行 i=1;return function需要在开一个堆内存;内存存储console.log(n+ i++)和内存地址 AAAFFF222
- 6.1 fn(10)相当于(AAAFFF222(10))形参赋值n=10 10+i++,i是空间B的i,fn(10)运行完销毁,但是B空间是临时不销毁,值运行完在销毁所以下一次i还是1
四、JS中的this指向
- 1、自执行的函数this一般都是window
- 2、给某个元素事件绑定方法,当事件触发执行对应方法的时候,方法中的this一般都是当前元素本身
- 3、还有一种方式可以快速区分this,当方法执行,看一看方法名前边是否有**
点
,有的话点
**前边是谁this就是谁,没有一般都是window - 4、在构造函数模式中,方法体中的出现的this是当前类的一个实例(this.xxx=xxx都是在给当前实例增加私有的属性)
当前函数执行的主体(谁执行函数this就是谁)
- 函数外边的this是window,我们一般研究函数内的this
this是谁和他在哪定义的以及在哪执行没有任何关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14function fn1() {
function b() {
console.log(this) //window
}
b()
}
fn1()
function fn2() {
console.log(this) //window
}
~function(){
fn2()
}()- 在js非严格模式下(默认模式就是非严格模式、松散模式)
- 1、自执行的函数this一般都是window
1
2
3
4
5
6
7
8
9//先开堆内存 存fn属性值的时候自定义函数执行
var obj1 = {
name:'obj',
//fn在变量提升阶段自执行返回return的内存空间 obj还没有值(变量提升完才会赋值)
fn:(function(){
//this ->window
return function(){}
})()
} - 2、给某个元素事件绑定方法,当事件触发执行对应方法的时候,方法中的this一般都是当前元素本身
1
2
3oBox.onclick = function(){
//->this->oBox
} - 3、还有一种方式可以快速区分this,当方法执行,看一看方法名前边是否有**
点
,有的话点
**前边是谁this就是谁,没有一般都是window1
2
3
4
5
6
7
8
9var obj = {
name:'obj',
fn:function(){
console.log(this) //window
}
}
obj.fn()
var f = obj.fn;//obj
f();//window - JS严格模式(让JS更加严谨)
- 1、自执行函数中的this永远是undefined
- 2、方法执行看前边是否有点,有的话前边是谁this就是谁
- 开启严格模式:在当前作用域的第一行加上’use strict’,开启严格模式,那么当前作用域下在执行的js代码都是按照严格模式执行的
1
2
3
4
5'use strict'//当前js代码开启严格模式(包含了函数中的代码)
~function(){
'use strict'//部分代码开启严格模式(对外界没有影响)
}() - 在js严格模式下,如果执行主体不明确,this是undefined(非严格模式下是window)
1
2
3
4
5
6function fn() {
'use strict'
console.log(this) //undefined
}
fn()
window.fn()//window4.1 this和闭包的综合练习
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17var num = 1;
var obj = {
num :2,
fn:(function(num){
this.num*=2;
num+= 2;
return function(){
this.num*=3;
num ++;
console.log(num);
}
})(num)//把全局的num赋值给形参
}
var fn = obj.fn;
fn()//4
obj.fn()//5
console.log(num, obj.num);//6 6
记住我们真正操作的是哪个作用域,否则很容易搞错 obj = fn中return回来的这个AAAFFF111地址
- 1、第一步变量提升 var num; var obj; obj是对象开启堆内存(地址AAAFFF000)var fn;
- 2、堆内存AAAFFF000中: num =2 fn自执行函数需要开启私有空间fn(A)和返回结果
- 3、私有空间fn(A)进行形参赋值 num=1 this.num*=2(自执行函数this指向window 全局的var num=2) num+=2 = 3(num是私有的形参1) return AAAFFF111(函数是数据引用类型需要开启堆内存AAAFFF111)
- 4、堆内存存储的是字符串(AAAFFF111) this.num*=3; num ++; console.log(num);
- 5、fn(A) return的是 AAAFFF111这个内存地址 obj = AAAFFF111这个内存地址(等上边都执行完obj才有值)
- 6 代码开始重上到下执行 fn= onj.fn = AAAFFF111这个内存地址
- 7 fn()函数执行形成私有作用域(AAAFFF111),第一步形参赋值和变量提升都没有,this num*=3(this指window 全局下的var num= 6) num++(4 num不是私有变量查找上一级fn的num为3 num++为4 上一级的也累加为4)
- 8 fn()函数执行完销毁,但是fn(A)的内存空间被占用所以无法被销毁
- 8 obj.fn()函数执行形成私有作用域执行的还是(AAAFFF111)但是this指向的是obj,this.num3 = obj.name3 = 6(obj.name =6) num++找到上级作用域名(上次fn()执行num为4 这次++为5)运行完作用域销毁
4.2闭包总结
- 自执行函数形成私有作用域,返回了一个地址被外界变量接收,不会销毁
1
2
3
4
5var utils = (function(){
return {
}
})()4.3闭包的作用总结
- 1、保护
- 团队协作开发,每个开发者把自己的代码存放在一个私有作用域中,防止代码冲突,提供别人需要的方法,通过return或者window.xxx暴露在全局下即可
- jQuery也是这种保护机制
1
2
3
4
5
6
7~function(){
var jQuery = function(){
...
}
...
window.$ = window.Jquery = jQuery
}() - 2、保存
- 形成一个不销毁的私有作用域
- 单例模式(JS高级编程技巧:惰性思想/柯理化函数思想…)
2、JS的面向对象(OOP)
一、单例模式 (Singleton Pattern)
- 在真实项目中,为了实现模块化开发或者团队协作开发,我们经常用到单例模式(一般业务逻辑部分的代码都是依托单例模式设计的)
单例模式的由来:很久以前,js都是值类型,没有引用数据类型
1
2
3
4
5
6
7var name = '张三';
var sex = '女';
var age = 13;
var name='李四';
var sex = '男';
var age = 13;- 如果后边的代码创建的变量和之前函数名一样,会把之前存储的值替换掉,团队开发,如果是这样处理,经常会导致代码冲突(全局变量污染,全局变量冲突)
- **
后来JS中诞生了对象数据类型
**,把描述同一件事物的特征或者属性,进行归纳汇总,以此来避免全局冲突1
2
3
4
5
6
7
8
9
10
11var person1 = {
name : '张三';
sex : '女';
age : 13;
}
var person2 = {
name:'李四';
sex : '男';
age : 13;
}
person1.nane - 单例模式:我们把对象数据类型实现
把描述同一件事物的属性或者特征
归纳汇总到一起,以此避免全局变量冲突的方式和思想叫做单例设计模式
1
2
3
4//->singleton不仅仅是对象名了,在单例模式中,singleton称之为命名空间
var singleton = {
xxx : xxx
} - 把描述同一件事情的属性或者方法存放在某一命名空间,多个命名空间中的属性和方法是互不干扰的
1.1、用单例模式实现模块化开发
1
2
3
4
5
6
7
8
9
10
11
12
13//->第一个人开发的功能
var shopping = {
setName:function(){...}
setage:function(){...}
}
//->第二个人开发的功能
var changeName = {
doSomeThing:function(){...}
//在当前命名空间想要调取其他命名空间的方法:指定好对应的命名空间即可
shopping.setName();
//调取本模块的一些方法,此方法中的this一般都是当前模块的命名空间
this.doSomeThing()
} - 单例模式虽然解决了分组问题,但是不能批量的生产,属于手工业模式(请看工厂模式)
1.2 高级单例模式
- 基于js高级编程技巧**
惰性思想
**来实现的单例模式,并且可以把一些常用的设计模式(例如:命令模式、发布订阅设计模式、promise设计模式等)融合进来,最后清晰的规划业务逻辑代码,方便后期的二次开发和维护,这种代码综合体就是高级单例模式,也是项目中最常用的模式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var searchModel = (function(){
//形成一个私有的不销毁的作用域
function sumbit(){... }
function fn(){...}
return {
sumbit:sumbit.
//命令模式 init唯一入口 可以做判断加载方法
init:function(){
sumbit();
fn();
}
}
})()
searchModel.sumbit();
searchModel.init()工厂模式
- 把实现同一件事情的相同的代码放到一个函数中,在想实现这个功能不需要重新编写这些代码了,执行当前函数即可(函数的封装)
- 函数的封装不需要写重复的代码(低耦合高内聚:减少页面中的冗余代码)
1
2
3
4
5
6
7
8
9
10
11function creatJsPerson(name age){
var obj ={}
obj.name = name
obj.age = age
obj.writeJs = function(){
console.log(name + age)
}
return obj;
}
var p1 = creatJsPerson('张三',25)
p1.writeJs()1.3 初步理解面向对象( OOP)
- 面向对象是编程思想(面向过程变成思想:C语言是面向过程),JAVA、PHP、C#、C++、.net(dot net)、JS都是面向对象编程的
- HTML和CSS是标记语言不是编程语言,没有所谓的面向对象编程(Less/SASSs属于css预编译语言 旨在把css变为编程语言(面向对象))
1.4 面向对象需要了解什么是
对象、类、实例
- 对象:编程中语言泛指万物皆对象(我们索要研究学习以及使用的都是对象)
- 类:把对象具体细分(按照属性或者特征细分为一些类别)
- 实例:某一类中具体的事物
- 【实际生活中】:自然界中万物皆对象,我们为了认真自然界,我们会把自然界中的事物按照特征分类,例如
- 动物类(人类(老年、少年…)、爬行动物类、哺乳动物类…)、植物类、微生物
- 类:每个人都属于人类中的一个实例,属于其中一个类别(教师,医生…)
JS就是基于面向对象创造出的语言(所以它是面向编程)我们想要学习js,首先也要给其进行分类,我们拿出某一类中的实例进行研究和学习
1.5 JS中一些常用的内置类
- 关于数据类型的
- Number:每一个数字或者NaN是他的一个实例
- String:字符串类
- Boolean:布尔类 true/false是它的实例
- Null/ Undefined:浏览器屏蔽了我们操作Null或者Undefined这个类
- Object:对象类;每一个对象数据类型都是它的实例(还可细分为Array、RegExp、Date)
- Array:数组类
- RegExp 正则类
- Date 日期类
- Function:函数类 每一个函数都是它的实例
- 关于元素对象和元素集合的
- HTMLCollection:元素集合类 getElementsByTagName/className/querySelectAll来获取
- NodeList:节点集合类 childNodes/getElementsByTagName
- HTMLDivElement
- HTMLElement:htmL标签类
- Element
- Node
- EventTarget ### 1.6 目前学习面向对象对于我们的意义
研究数组
- 1、创建一个数组类的实例,研究其基本语法和结构
- 2、如果想要研究数组具备的功能方法,我们只需要看Array/Object这些类上都赋予了他什么样的方法
1
console.dir([])//看父级_proto_上类的方法和爷爷级Object上_proto_的类的方法
- document.getElementById他的上下文只能是document,其他为什么不可以?(console.dir(document))
- 因为getElementById这个方法只有Document这个类才有,所以只有document这个作为Document的实例才可以使用这个方法
以后再看在一个方法先想一下它是哪个类的实例,类给提供了哪些方法
二、构造函数
- Js中所有的类都是函数数据类型,他通过new执行变成一个类,但是他本身也是一个普通的函数
- 构造函数模式(new fn())通过new执行后我们的fn就是一个类了
2.1 基于面向对象创建数据类型值(构造函数)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21var ary = [12,23]//->字面量创建方式
var ary = new Array();//严谨的基于面向对象(构造函数)方式创建数组
/*
* 两种创建方式在核心意义上没事差别,都是创建Array这个类的一个实例,但是在语法上是有区别的
* 1:字面量创建方式传递进来什么,都是给数组每一项加入内容
* 2:构造函数创建方式
* new Array(10):创建一个长度为10的数组,数组中的每一项都是空
* new Array('10')如果只传递一个实参并且实参不是数字相当于把当前值当作数组的第一项存储进来
* new Array(10,20,30)如果传递多个实参不是设置长度而是把传递中的内容当做数组中的每一项存储起来
* */
var obj = {name:'wanglei'}//->字面量方式直接一次添加即可
var obj = new Object()//->一般只用于创建空对象创建完成后自己依次添加即可
obj.name = 'wanglei'
//----------
//->字面量创建出来的是基本数据类型值但是也是Number的一个实例可以调取Number赋予他的方法
//->num.toFixed就是Number赋予他的方法
var num = 12
//->构造函数方式创建出来的也是Number的实例也可以调取Number赋予他的方法
//->但是获取的结果是对象数据类型的 typeof(num) = object
var num = new Number(12)2.2 构造函数设计模式(constructor)
- 实现构造函数方式,主要是为了创建类和实例的 ,也就是基于面向对象编程思想来实现一些需求的处理
1
2
3
4function fn(){
}
var f = new fn()//->fnS是一个类,f是当前这个类的实例 - 在js中,当我们使用 new xxx()来执行函数的时候,此时的函数就不是普通函数了,而是变为一个类,但会的结果叫做当前类的实例,我们把这种new xxx执行的方式称之为**
构造函数模式
**(一般都会把类名第一个字母大写)2.3 普通函数执行vs构造函数执行
- [普通函数执行]
- 1、开辟新的私有作用域
- 2、形参赋值
- 3、变量提升
- 4、代码自上而下执行(return后面的值就是当前函数的返回结果)
- 5、栈内存是否销毁
1
2
3
4
5
6
7function fn(num){
var total = null;
this.num = num;//->window 相当给全局对象几个个num的属性名属性值为10
total += num
return total
}
var f = fn(10)//10 - [构造函数执行]
- 1、首先和普通函数执行一样 也需要开辟一个新的私有作用于
- 2、在私有作用域完成类似于普通函数的操作:形参赋值、变量提升
- 3、在代码自上而下执行之前构造函数有属于自己比较特殊的操作:
浏览器会在当前作用域当中默认创建一个对象数据类型值,并且会让当前函数中的this指向创建的这个对象
- 4、像普通函数一样,代码自上而下执行**
this.xxx = xxx这类操作都是在给创建的这个对象增加属性名或者属性值
** - 5、代码执行完成后,即使函数中没有写return,在构造函数模式中:
浏览器会默认的把创建的对象返回到函数的外面
总结:构造函数执行,既具备普通函数执行的一面也具备自己独有的一些操作;**
在构造函数执行期间,浏览器默认对象(也就是函数中的this)就是当前这个类的一个实例,浏览器会把默认创建的实例返回,所以我们说:new Fn()执行,Fn是一个类,返回结果是Fn的这个类的一个实例
**1
2
3
4
5
6
7function fn1(num){
//->在构造函数模式中,方法体中的出现的this是当前类的一个实例
//->(this.xxx=xxx都是在给当前实例增加私有的属性)
this.num = num
}
var f =new fn1(10)
console.log(f);//-> {num: 10}2.4 深入理解构造函数执行的步骤
- 当构造函数或者类,执行的时候不需要传递任何的实参值,此时我们是否加小括号就不重要了(不传递实参的情况下小括号可以省略)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function Fn(){
var num = 100;
this.name = '珠峰培训'
this.sum = function(){
//this需要看sum执行的时候才知道
console.log(this.num)
}
}
var f1 = new Fn();
f1.sum//100 this指向f1
var f2 = new Fn;
console.log(f1.num);//undefined
console.log(f1.name);//珠峰培训
console.log(f1.sum == f2.sum)//false
console.log(f1.sum === f2.sum)//false
var ss = f1.sum;
ss()//undefined this指向全局 - 1、在全局作用域下变量提升var f1;var f2; fn(函数变量提升是声明加定义)所以开辟堆内存AAAFFF111,字符串存储’var num = 100;this.name = ‘珠峰培训’’
- 2、代码自上而下执行f1 = new Fn() 构造函数执行开辟私有作用域,然后形参赋值(无)变量提升 var num
- 3、**
浏览器会自动创建一个对象数据值,对象数据值(this/实例)需要开启堆内存BBBFFF111 (this指向BBBFFF111)
** - 4、代码自上而下执行**`this.xxx = xxx这类操作都是在给创建的这个对象增加属性名或者属性值也就是BBBFFF111.name=珠峰培训
- 5、代码执行完会自动把创建的实例返回 return this,所以fn = BBBFFF111这个堆内存地址 所以f1.name = ‘珠峰培训’ 而num和BBBFFF111这个堆内存没关系所以undefined
- 6、f2执行和f1一样 浏览器会自动创建一个对象数据值,对象数据值(this/实例)需要开启堆内存BBBFFF222
- 总结:构造函数执行,同时具备了普通函数执行的一面,
但是和实例相关的,只有自己特殊的一面才相关(也就是this.xxx=xxx才相当于给当前实例增加的私有属性)函数体中出现的私有变量(var/形参)和实例都没有直接的关系
- **
通过类创建出来的每一个实例都是单独的个体(单独的堆内存空间)实例和实例之间是不相同并且独立或不影响的
**(部分开发把这种模式叫做单例模式,这种说法是错的,js中这种模式叫构造函数设计模式) 在构造函数体中this.xxx给实例设置属性都是当前实例的私有属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function Fn1(name){
this.name = '珠峰培训'
return 10;
}
var a = new Fn1->//a依然是当前类的一个实例
console.log(a);
function Fn2(name){
this.name = '珠峰培训'
return {
name:1
}
}
var b = new Fn2()
console.log(b);//->b已经是一个引用数据类型值不是实例了- 当构造函数体当中自己手动设置了return,(默认返回的是实例L对象类型值)return的是一个基本类型值,对最后返回来的实例没有影响,但是如果返回的是引用数据类型值,会把默认返回的实例替换掉:
2.5、instanceof检测某一个实例是否属于这个类
1
2
3
4
5
6
7function Fn(name){
this.x = '100'
}
var f = new Fn()
console.log(f instanceof Fn )//true
console.log(f instanceof Array )//false
console.log(f instanceof Object )//true - 因为所有实例都是对象数据类型的,而每一个对象数据类型都是Object这个内置类的一个实例,所以f也是他的一个实例
- instanceof解决了typeof无法识别数组还是正则问题 instanceof帮我们解决了这个问题
1
2
3
4
5
6
7
8typeof [] //->'object'
typeof /^$/ //->'object'
typeof null //->'object'
[] instanceof Array //->true
/^$/ instanceof Array //->false
null instanceof Object //->false2.6 hasOwnProperty VS in
- hasOwnProperty 用来检测当前属性是否是对象的私有属性(不仅要是对象的属性,而且需要是私有的才可以)
- in用来检测当前属性是否隶属于对象(不管共有还是私有只要有就是true)
1
2
3
4
5
6
7
8
9var obj = {name:'张三'}
console.log('name' in obj);//true
console.log('sex' in obj);//false
console.log('toString' in obj);//true 私有属性
console.log('hasOwnProperty' in obj);
//->hasOwnProperty、toString是Object的这个内置类中提供的属性方法
//->只要当前对象是Object的一个实例,就可以使用这个方法
obj.hasOwnProperty('name')//true name是obj的私有属性1
2
3
4function fn(){
this.num = 100;//在代码执行前浏览器会自动创建对象类型值
}
console.log(f.hasOwnProperty('num'));//true2.7 检测一个属性是否是当前对象的共有属性
- 1、是对象的一个属性
- 2、不是对象的私有属性
1
2
3
4
5
6
7
8
9
10
11
12var obj = {name:'张三'}
function hasPubProperty(attr,obj){
var result;
//in不管私有共有都为true hasOwnProperty只检测私有为true 公有为false
//有这个属性并且不是私有 那么剩下的就是公有属性了
if(attr in obj != false && obj.hasOwnProperty(attr) != true){
return result = '公有属性'
}
return result = '私有属性'
// return (attr in obj)&& (obj.hasOwnProperty(attr) === false)
}
console.log(hasPubProperty('toString',obj));三、原型和原型链(prototype)
- JS中对象和函数汇总
- 【对象数据类型值】
- {}包起来的普通对象
- [] 数组
- /^$/ 正则
- Math 数学对象
- 一般类的实例都是对象数据类型
- 函数的prototype属性
- 实例的__proto__属性
- 【函数数据类型值】
- 普通函数
- 所有的类(内置类和自定义类)
1
2
3typeof Object -> "function"
typeof String -> "function"
typeof Number -> "function"1.1 原型
原型执行步骤
- prototype:类的原型,在原型上定义的方法都是当前类实例的公有方法**
(代表自身是父级)
** - __proto__,把函数当作一个普通对象,指向所属类的原型(函数都指向Function原型,函数类在最终指向Object)**
代表自身是子级别
** - 1、所有函数都天生自带一个属性:prototype(原型)他是对象数据类型值,在当前对象中存储了类需要给其实例使用的公有的属性和方法
- 2、prototype这个对象,浏览器会默认为其开辟一个堆内存,这个堆内存中天生自带一个属性:constructor(构造函数),这个对象储存的值就是当前函数本身
- 3、每一个类的实例(每一个对象)都天生自带一个属性_proto_,属性值是当前对象所属类的原型
- 注意:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function Fn(name,age){
this.name = name
this.age = age
this.say = function(){
console.log('我的名字是'+this.name + this.age+'years old!')
}
}
//函数天生自带一个prototype属性
Fn.prototype.say = function(){
console.log('hello world')
}
Fn.prototype.eat = function(){
console.log('i like food')
}
var f1 = new Fn('王磊','25')
var f2 = new Fn('张欣娅','22') - Fn开辟对内存 存储函数体中的代码字符串
- Fn 函数天生自带prototype 是对象数据类型 浏览器自带开辟堆内存(Fn.prototype.)
- Fn.prototype自带 constructor 指向Fn函数本身
- Fn.prototype对内存中有增加了两个属性say和eat;函数还要开两个堆内存存储代码字符串(公共的)
- var f1 = new Fn(‘王磊’,’25’)实例是对象数据类型 开辟堆内存 this指实例f1 f1.name= 王磊 f1.age= 25 say:function(私有的属性)
- var f2 = new Fn(‘张欣娅’,’22’)实例是对象数据类型 开辟堆内存 this指实例f1 f1.name= 张欣娅 f1.age= 22 say:function(私有的属性)
- 每一个类的实例(每一个对象)都天生自带一个属性_proto_,属性值是当前对象所属类的原型
- f1 f2是Fn所属类的实例 所以_proto,都指向 Fn的_proto,
- 而Fn的prototyp我们不知道是怎么new出来的但是作为对象肯定是Object的实例,__proto__则指向所属类的原型Object ### 1.2原型链
- 运行实例会查找__proto__私有属性,如果没有就会向上一级找所属类原型上公有的,如果还没有就找到Object上的原型,这种机制叫原型链
1
2
3
4
5//先找私有没有找共有 因为是共有所以true 如果公有没有就找到基础类Object上的原型 这种机制叫原型链
console.log(f1.eat === f2.eat);//true
console.log(f1.hasOwnProperty === f2.hasOwnProperty);//true
//f1.__proto__.say跳过私有直接找共有原型 f2.say是私有 所以false
console.log(f1.__proto__.say === f2.say);//false1.3 内置类原型链引发的一些思考
<img src=’/images/prototype1.png’style=’height:350px;’> - 私有和公有是一个相对论,
- 1、相对于实例来说 push是公有属性
- 2、相对于Array.prototype来说push就是自己私有的
- 3、凡是通过__proto__原型链查找的属性都是公有的属性 反之都是私有的
- 4、arguments类数组和数组的区别在于arguments原型指的是Object 没有Array原型上的方法
- 5、arguments._proto_ = Array.prototype 可以指向Array的原型(IE浏览器屏蔽了我们使用或修改__proto__)
1.4 原型链上的this问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function Fn(name,age){
this.name = name
this.age = age
this.say = function(){
console.log('我的名字是'+this.name + this.age+'years old!')
}
}
/*
* 关于原型链中提供的私有(公有)方法中的this问题
* 1、看点前边是谁this就是谁
* f1.say() -> this:f1
* f1.__proto__.say() -> this:f1.__proto__
* 2、把需要执行的方法中的this进行替换
* 3、替换完成后,如果想要替换结果,按照原型链的查找机制去查找即可
*/1.5 在原型上批量扩展属性和方法
- 别名法(小名)用途不大只是少写几个单词
1
2
3
4
5
6
7
8
9
10
11
12//类
function Fn(name,age){
this.name = name
this.age = age
}
//正常一个一个写比较麻烦
Fn.prototype.aa = function(){
console.log(this.age)
}
var pro = Fn.prototype //->指向同一堆内存
pro.bb = function(){}
pro.cc = function(){}1.51 重新构造原型
- 原型浏览器会默认开辟对内存自带constructor指向本身 但是重新构造原型空间改成我们自己设定的就会失去constructoe这个属性,所以要自己声明指向当前类的本身
注意 浏览器自己开辟的Fn.prototype就没了Fn的prototype指向自己开辟的内存空间
让原型指向自己开辟的堆内存有一个问题,自己开辟的堆内存中没有constructor这个属性,所以实例在调取constructor的时候找到的是Object,这样不好
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21function Fn(name,age){
this.name = name
this.age = age
}
Fn.prototype.cc = function(){
console.log(this.age)
}
Fn.prototype = {
//让原型指向自己开辟的堆内存有一个问题,自己开辟的堆内存中没有constructor这个属性
//没有constructor这个属性,所以实例在调取constructor的时候找到的是Object这样不好
//此时我们应该重新设置constructor保证机制的完整性
constructor:Fn,//指向当前类本身
aa:function(){
console.log(this.name)
},
bb:function(){
}
}
var f = new Fn('xxx',28)
f.cc() //not a function只有浏览器天生给prototype开辟的堆内存里面才有constructor,而我们自己开辟的这个堆内存没有这个属性,这样constructor指向就不是Fn而是Object了,为了和原来的保持一致我们需要手动增加constructor的指向
1.52 原型继承
- 原型继承是js最常用的一种继承方式
- 核心:原型继承并不是把父类中的属性和方法克隆一份一模一样的给B,而是让B和A之间增加了原型链的的连接,以后B的实例as想要A中的getX方法,需要一级级的向上查找来使用
1
2
3
4
5
6
7
8
9
10
11
12function A(){
this.x = 100
}
A.prototype.getX = function(){
console.log(this.x)
}
function B (){
this.x = 200
}
B.prototype = new A;
var as = new B;
as.getX()//200 - 子类B想继承A中所有的属性和方法(私有+公有)只需要B.prototype = new A;即可
- 原型继承特点:他是把父类中私有+公有的都继承到子类原型上
1.6 jQuery源码在原型上扩展属性和方法的代码片段
1
2
3
4
5
6
7
8
9
10
11
12~function(){
var jQuery = function(selector,context){
return new jQuery.fn.init(selector,context)
}
jQuery.fn = jQuery.prototype = {
constructor:jQuery,//指向类的本身
init:function(selector,context){
}
}
window.jQuery = window.$ = jQuery
}1.7 基于内置类原型扩展的方法
- 我们新增加的方法最好设置一个前缀:防止我们新增加的方法和内置方法冲突而把内置方法替换掉了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36//内置类原型扩展 数组去重 (高仿原型上的方法)
Array.prototype.myDistinct = function myDistinct(){
var obj = {}
for(var i =0;i<this.length;i++){
var cur = this[i]
if(typeof obj[cur] !== 'undefined'){
this[i] = this[this.length-1];
this.length--;
i--;
continue
}
obj[cur] = cur
}
obj = null;
return this;//可以实现链式写法 执行完这个方法后我们还可以调用其他方法
//前提是要属于这个类的实例
}
arr.myDistinct()
console.log(arr);
ary.myDistinct()
//普通方法
function myDistinct(ary){
var obj = {}
for(var i =0;i<ary.length;i++){
var cur = ary[i]
if(typeof obj[ary] !== 'undefined'){
arr.splice(i,1)
i--;
continue
}
obj[ary] = ary
}
obj = null;
return ary
}
myDistinct(ary)1.8 内置类原型扩展面试题
- 实现(3).plus(2).minus(1) 等于4
1
2
3
4
5
6
7
8
9
10//this指的是3操作plus这个方法 3属于Number
Number.prototype.plus = function plus(){
var value = Number(arguments[0]) || 0
return this + value
}
Number.prototype.minus = function minus(){
var value = Number(arguments[0]) || 0
return this - value
}
console.log((3).plus(2).minus(1)) - 链式写法:执行完第一个方法紧跟着就调取下一个方法(执行完成一个方法后,返回的结果依然是当前类的实例,这样就可以调取当前类的其他方法了)
1.9 fon…in遍历对象
- var key; var attr (attribute) ;
- 对象中有多少组键值对,我们的FOR. IN循环就遍历多少次(不一-定)//=>每一次循环KEY这个变量存储的都是当前循环这组键值对的属性名//1. KEY存储的值都是字符串格式的(不管属性名是否为数字)
- 在FOR IN循环遍历的时候,大部分浏览器都是先把对象中的键值对进行排序(把数字属性名的排在前面,并且排列的时候按照数字由小到大排列),其次在把非数字的属性名按照之前编写的顺序排列,循环的时候按照重新排列的顺序依次遍历
fon..in循环在遍历的时候默认可以把自己私有的和他在原型上扩展的属性和方法都可以遍历到,一般情况下我们只需要遍历私有的
- propertyIsEnumerable 用户自定义对象和引擎内置对象上属性可枚举性的区别.私有可枚举为true共有的都为false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17Object.prototype.aaa = function(){}
//hasOwnProperty该方法会忽略掉那些从原型链上继承到的属性。
var obj = {name:'王二小'}
for (var key in obj) {
//hasOwnProperty该方法会忽略掉那些从原型链上继承到的属性。
if(obj.hasOwnProperty(key)){
console.log(obj[key]);
}
//可枚举属性
if(obj.propertyIsEnumerable(key)){
console.log(key);
}
console.log(key)//name aaa 私有的和原型上扩展的都打印出来了
}
console.log(obj.propertyIsEnumerable('name'))//true
console.log(obj.propertyIsEnumerable('aaa'))//false
console.log(obj.propertyIsEnumerable('toString'))//false 只要用for..in遍历的时候都要加上hasOwnProperty属性
2.0 function 函数类
- 所有的函数都是function的一个类
- 每个类都带有prototype作为他实例的父级原型但是他本身也属于function的一个类,所以本身也带有__proto__指向函数
- 最终指向Object基类
- prototype:类的原型,在原型上定义的方法都是当前类实例的公有方法(代表自身是父级)
- __proto__,把函数当作一个普通对象,指向Function这个类原型
- 函数在整个JS最复杂也是最重要的知识
<img src=’/images/prototype_.png’style=’height:350px;’> - 1、一个函数存在多面性
- “普通函数”:他本身就是一个普通的函数,执行的函数形成一个私有作用域(必报)形参赋值,变量提升,代码执行,执行完后栈内存销毁/不销毁
- “类”:他有自己的实例,也有一个叫prototype属性是自己的原型,他的实例都可以指向自己的原型 this.xxx是他的私有属性其余的没关系
- “普通对象”:var obj = {}中的obj一样就是一个普通的对象,作为对象可以有一些自己的私有属性,也可以通过__proto__找到Function.prototype
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//类
function Fn(){
var num = 500;
this.x = 100//f的私有属性
}
//原型
Fn.prototype.aaa = function(){
}
//普通对象obj.aaa = 100
Fn.aaa = 100;
var f = new Fn;//作为类执行
console.log(f.num); //undefined
console.log(f.aaa);//undefined
var res = Fn()//函数执行 Fn()中的this是window var是私有属性
console.log(res);//undefined 没有返回值为undefined
console.log(Fn.aaa);//1000 普通对象四、call 、apply改变this关键字的指向以及和bind的区别
- 首先我们让原型上的call方法执行,在执行call方法的时候,我们让fn中的this变成第一个参数值;然后再把fn这个函数执行,有参数的话就是给fn这个函数传需要的参数
- 实例运行方法
1
2
3var ary = [1,2,3]
ary.slice //->ary这个实例通过原型链的查找机制找到Arrat.prototype上的slice方法
ary.slice()//让找到的slice方法执行在执行过程中对ary进行了截取1、call方法的作用
1
2
3
4
5
6
7
8
9var obj = { name:'王磊'}
function fn(num1,num2){
console.log(num1+num2 ,this)
}
console.log(fn.call(obj,100,200));
//call方法的作用
//->首先我们让原型上的call方法执行,在执行call方法的时候,
//->我们让fn中的this变成第一个参数值obj;然后再把fn这个函数执行
//->有参数的话就是给fn这个函数传需要的参数 - call方法的思路
1
2
3
4
5
6
7
8
9
10
11
12//自己模拟内置的call方法, 深入探讨call方法的执行思路
Function.prototype.myCall = function(context){
//this就是当前我要操作和改变this关键字的那个函数名 Fn.myCall(obj)
//eval(this.toString().replace('this','context') 当前函数转换成字符串然
//后把this替换成context,eval让字符串变成js表达式
var that = eval(this.toString().replace('this','context'))
//1、让fn中的this关键字变为context的值
//让this函数中的this关键字变成context
//2、让fn方法执行
this()
}
fn.myCall(obj)2、apply方法的作用
- apply和call方法是一模一样的都是改变this指向的并且吧方法执行而且在严格模式下和非严格模式下队医第一个参数是null、undefined的规律也是一样的
1
2
3
4
5
6
7var obj = { name:'王磊'}
function fn(num1,num2){
console.log(num1+num2 ,this)
}
fn.call(obj,100,200)//call传递参数的时候是一个一个传递值得
fn.apply(obj,[100,200])//apply是把要给传递的参数值统一的放在一个数组中进行操作
//->apply但是也相当于一个一个赋值3、bind方法的作用
- bind这个方法在IE6-8不兼容,和call、apply类似,都是改变this指向的
- 预处理机制:实现把fn的this改变成我们想要的结果,并且把对应值也准备好,以后用到了直接执行即可
1
2
3
4
5
6
7
8
9var obj = { name:'王磊'}
function fn(num1,num2){
console.log(num1+num2 ,this)
}
fn.call(obj,100,200)//改变this和执行fn函数是一起完成的
var temp = fn.bind(obj,100,200)
//->只是改变了this为obj,并且给fn传递了两个参数但是并没有把fn执行
//->执行bind会有一个返回值,这个返回值temp就是把fn的this改变后的那个结果
temp()//this指向obj了
JS如何检测数据类型?
1 | typepof:检测数据类型的运算符 |
[局限性]
1、typeof null不是null’而是object’: 因为null虽然是单独的一个数据类型,但是它原本意思是空对象指针,浏览器使用typeof检测的时候会把它按照对象来检测
1
2
3
4
5typeof 12 //->'number'
typeof '12' //->'string'
typeof true //->'boolean'
typeof null //->'object'
typeof undefined //->'undefined'2、使用typeof无法具体细分出到底是数组还是正则,因为返回的结果都是object’
1
2
3
4typeof{name:'wl'} //->'object'
typeof function(){} //->'function'
typeof [] //->'object'
typeof /^$/ //->'object'3、instanceof可以帮我们解决这个问题
1
2
3
4
5
6
7typeof [] //->'object'
typeof /^$/ //->'object'
typeof null //->'object'
[] instanceof Array //->true
/^$/ instanceof Array //->false
null instanceof Object //->false面试题
1
2
3console.log( typeof typeof []);
typeof [] -> object
typeof "object" -> string1、boolean 布尔类型详细解读
1
2//-> '把其他数据类型转换为boolean类型'
//-> '只有0、NaN、空字符串、null、undefined这五个数据转换为布尔类型为false'2、字符串简要概括
在js中 被单引号和双引号包起来的都是字符串
常用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23s.chatAt(index) // 返回指定位置的字符
s.charCodeAt(index) // 返回给定位置字符的Unicode码点(十进制表示)
s.toLowerCase() // 全部转为小写
s.toUpperCase() // 全部转为大写
s.slice(start,end) // 用于从原字符串取出子字符串并返回
s.substring(start,end) // 用于从原字符串取出子字符串并返回
s.substr()
s.indexOf(s) // 返回给定元素在字符串中第一次出现的位置,没有出现则返回-1
s.lastIndexOf() //返回元素在字符串最后一次出现的位置,没有出现则返回-1。
s.replace(oldValue,newValue) // 用于替换匹配的子字符串
s.split() // 按照给定规则分割字符串,返回一个由分割出来的子字符串组成的数组
s.match(regexp) // 用于确定原字符串是否匹配某个子字符串,返回一个数组
s.trim() // 用于去除字符串两端的空格,返回一个新字符串
s.localeCompare(s2) // 用于比较两个字符串。
s.search() // 返回值为匹配的第一个位置。如果没有找到匹配,则返回-13、Number简要概括
1
2
3Number([])
-> 把引用数据类型转换为number,首先需要把引用数据类型转为字符串(toString),
-> 在把字符串转换为number即可例如;[ Number([12]) ->|[12]->'12’ '12'->12parseInt()
1
2
3
4parseInt()也是把其它数据类型值转换为number,和Number方法在处理字符串的时候有所区别
Number('12px') ->NaN
parseInt('12px') ->12
parseInt('12Ex13') ->12提取规则:从左到右依次查找有效数字字符,直到遇见非有效数字字符为止(不管后面是否还有,都不找了),把找到的转换为数字
parseFloat() 函数可解析一个字符串,并返回一个浮点数
null和空字符串的区别
1
2
3
4- 比如都是去种树
-> 空字符串属于挖了个坑,但是没有种任何东西
-> null是连坑都没挖
-> 空字符串相对于null来说开辟了内存,消耗了那么一丟丢的性能null和undefined的区别
1
2
3-> null空对象、一般都是暂时没有,预期中以后会有的(可能以后也没有达到预期;
-> 在JS中null一般都是手动先赋值为null,后期我们在给其赋具体值
-> undefined:未定义->连东西都没有4、对象数据类型object
var obj = {name:’王磊,age:18};
每一个对象都是由零到多组“属性名(key键) :属性值 (value值)组成的,或者说有多组键值对组成的,每一组键值对中间用逗号分隔
对象的属性名是字符串或者数字格式的,存储的属性值可以是任何的数据类型
对象名.属性名:忽略了属性名的单双引号
对象名[属性名]:不能忽路单双引号
如果操作的属性名在对象中不存在,获取的结果是undefined
1
2
3
4
5
6
7var obj = {name: '王磊' ,age:8,friend:['张三','里斯'],0:'数字属性'}
//->获取方式
obj.name
obj['name']
//->如果属性名是数字如何操作obj .0语法不支持
obj[0]
obj['0']设置/修改:一个对象的属性名是不能重复的(唯一性),如果之前存在就是修改属性值的操作,反之不存在就是新设置属性的操作
1
2
3
4
5var obj = {name: '王磊' ,age:8,friend:['张三','里斯'],0:'数字属性'}
obj[0] = '12';
obj.sex = '男'
console.log(obj)
// {0: "12", name: "王磊", age: 8, friend: Array(2), sex: "男"}obj[age]和obj[‘’]的区别
age:变量名,代表的是它存储的值。
‘age’:常量,字符串的具体值。
1
2-> obj[age]操作的是对象中的属性,并不是操作的变量, 所以是操作的变量所存储的值,也就相当于obj['age']
-> 而obj['age']就是操作的是obj对象中age属性,所以值就是age本身。基本数据类型和引用数据类型的区别
JS是运行在浏览器中的(内核引擎),浏览器会为IS提供赖以生存的环境(提供给JS代码执行的环境)=>全局 作用域window(后台是global)
= == ===
1
2
3
4
5
6
7
8
9
10
11
12
13
14-> =:赋值,符号左边是变量,右边是值
-> ==:比较,如果左右两边的类型不一样,浏览器会默认转换成一样的在比较 比如:
'6'==6 => 6 = 6 => true
-> ===:绝对相等,不仅要求值要一样,类型也要一样
'6'===6 => false
[] == [] => false //注意对象和对象比较的是空间地址,结果肯定是false
//对象和数字 把对象转换为数字
[] == 0 => true
//对象和字符串 把两边都转换为数字比较额
[] == '' ->true
//对象和布尔 把两边都转换为数字比较额
[] == true ->0 == 1 => false
[] == false ->0 == 0 => true
![] == false -> ![]把数组变成布尔在取反=false -> false==false ->true- 规律:两个等于号比较,左右两边的数据类型不一样,浏览器会把两边的类型都转换为数字在比较,但是null和undefined除外
- null == undefined -> true null === undefined -> false null和undefined和其他任何值都不相等
面试题
1
2
3
4
5
6
7
8
9
10
11var num = parseFloat( 'width: 12.5px');//->NaN
if (num==12.5){
alert(12.5) ;
}else if(num == NaN) {//->NaN != NaN
alert(NaN) ;
}else if(typeof num== 'number'){ //typeof num== 'number'
alert(0)
}else{
alert('啥也不是')
}
答案:alert(0)逻辑运算符 &&(与) ||(或) !(非)
&& 对于布尔值,逻辑与是非常简单的,只要有一个false,就返回false;
|| 对于布尔值,逻辑或是非常简单的,只要有一个true,就返回true;
1
2if(A>B && B>10){}只有两个条件为真,整体条件才为真
if(A>B || B>10){}只要一个条件为真,整体条件才为真判断操作语句
if、else if、else
1
2
3
4
5
6
7if(条件一){
}else if(条件二){
}else{
//以上条件都不成立
}如果好几个条件都成立了,只把第一个成立的条件执行,后边的条件忽略不管
条件
1
2//-> A == B A != B A >= B A < B
//-> if(A){} 先把A转化为布尔类型,判断真假来决定条件是否成立三元运算符
三元运算符: 条件 ? 条件成立执行 : 条件不成立执行
if()else():三元运算符就是这种简单的if、else的另外一种写法
改写三元运算符:如果条件成立或者不成立的某一种情况不需要做什么处理,我们空着语法不合适,我们要使用null、undefined、0等进行占位
在三元运算符中不能出现return、break、continue这些关键字
swith case循环操作
swith case应用于if else中一个变量在不同值的情况下进行不同操作
1
2
3
4
5
6
7
8
9
10swith后边的小括号放入的是变量或者是一个计算得到的值
swith(num){
case 1://验证的都是值,目的是验证swith括号中的值和case中的哪一个相等并进行处理
···//做些什么
break;
case 5:
···
break;
default:0
}for循环操作语句
循环:重复做一件事情
1
2
3for (设置循环起始值;没晋循环执行的条件;步长累加){
//=>循环体:重复做的事情都在循环体中
}案例剖析
1
2
3
4
5
6
7
8//1、设置初始值.
//2、验证条件
//3、条件成立,执行循环体;不成立,循坏结束;
//4、步长累加
for(var i=0;i<5;i++){
console.log(i);//->0 1 2 3 4
}
console,Log(i);//=>5continue、break、return
continue: 结束本轮循环,继续执行下一轮:循环体中continue,后面的代码都不会在执行,它会直接的去执行步长,然后进入到下一轮
break: 结束整个循环:循环体中一但遇到break,首先后面代码不执行了,而且步长累加也不执行了,循环都结束了
return返回的都是存储的值 并不是返回变量本身 函数中遇见return后,return后边的代码就不再执行了
1
2
3
4
5
6
7
8
9
10
11
12for(var i=l;i<10;i+2){
if(i<5){
i++;//->1...4满足结束本轮进入下一轮循环
continue;
} else {
i+=3;
//5的时候走到此 遇见break结束整个循环
break;
}
console.log(i);
}
console.log(i);ll=>10
数据类型转换汇总
- 把其他数据类型转换为number类型
- isNaN、Number、parseInt、parseFloat
- 数学运算 :+、-、*、%、 除了加法有特殊性,其余的运算符都是遇到非数字类型需要把其转换成Number在进行运算
引用数据类型转换为数字
1
2
3
4
5//[]数组通过toString方法把数组转换成字符串,然后再调用Number把字符串转换为数字
#### + 加法的特性 遇到字符串的时候是拼接,只要不是字符串的时候是加法运算
[] -> '' -> 0
[12] -> '12' -> 12
[12,23] -> '12,34' -> NaNMath常用的方法
- 数学函数:他的对象数据类型是 object
- Math对象给我们提供了很多操作数学的方法
- console.dir(Math)查看所有方法
- 经常用到的方法:如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21Math.abs()//取绝对值
Math.ceil()//向上取整
Math.floor()//向下取整
Math.round()//四舍五入
Math.random()//获取0-1之间的随机数
//-> 封装循环strat到end之间的随机数
function select(strat, end) {
var total = end - strat + 1;
return Math.floor(Math.random() * total + strat);
}
Math.max()//获得一组中的最大值
Math.min()//获得一组中的最小值
console.log(Math.min(2,4,3,6,3,8,0,1,3));//->0
Math.PI()//获取圆周率
Math.pow()//获取一个值的多少次幂
Math.sqrt()/开平方
//-> Math.sqrt(10,2) 100
//-> Math.sqrt(100) 10闭包案例
1
2
3
4
5
6
7
8
9
10
11function a(){
var n=100;
var b = function(){
n++;
return n;
}
return b;
}
var c = a();
c()//102
alert(c)