是什么
- 一门脚本语言
- 一门解释型语言
- 动态类型语言
- 基于对象的语言
- 弱类型语言
使用场景
- 网页特效
- 服务端开发
- 命令行工具
- 桌面程序
- APP
- 控制硬件——物联网
- 游戏开发
区别
-
HTML
标记语言,展示数据
-
CSS
美化页面
-
JavaScript
动态效果,与用户的交互
组成
变量
作用
储存数据或操作数据
声明与初始化
声明
var num (此时没有赋值)
初始化
var num = 10 (此时赋值)
方式:
var 变量名字
注意:
-
以下说法一个意思:
声明变量并初始化 变量的初始化 声明变量赋值
-
可以一次初始化多个值
var num1 = 10, num2 = 20, num3 = 30
命名规范
- 区分大小写
- 第一个字符必须是字母、下划线、或者 $
- 后面的字符可以是数字、字母、下划线、$
- 变量的名字要有意义
- 一般都是小写,并采用驼峰式
- 不能使用关键字
- 字符串可用单引号也可用双引号
交换变量小案例
思路一:使用第三方变量
思路二:一般适用于数字交换(计算方式)
注释
单行注释://
一般用在一行代码上
多行注释:/**/
一般用在函数或者一段代码上
数据类型
原始数据类型
number(数字类型:整数和小数)
string(字符串类型)
boolean (布尔类型)
-
null (空类型)
值只有一个:null,一个对象指向为空
-
undefined (未定义)
值只有一个:undefined,
如果声明了变量但是没有赋值的情况下就会是undefined
object (对象)
获取变量的数据类型
typeof 变量名
-
typeof(变量名)
var num = 10 console.log(typeof(num)) console.log(typeof num)
数字类型
整数和小数
JS中可以表示
八进制:0开头
十进制
十六进制:0x开头
数字类型有范围
最大值 Number.MAX_VALUE
最小值 Number.MIN_VALUE
数值判断
NaN:not a number
isNaN:is not a number
注意
不要用小数去验证小数
不要用NaN验证是不是NaN (使用isNaN())
字符串类型
获取字符串长度:
- .length(包括空格)
JS中的转义符:
转义序列 | 字符 |
---|---|
\b | 退格 |
\f | 走纸换页 |
\n | 换行 |
\r | 回车 |
\t | 横向跳格 (Ctrl-I) |
' | 单引号 |
" | 双引号 |
\ | 反斜杠 |
字符串拼接
使用‘+’号,将多个字符串拼为一个
一个字符串和其他数字拼接,最终结果是字符串
数字类型的字符串和一个数字类型的数字相加得到的是数字类型的数字(发生了隐式转换)
布尔类型
只有两个值,true & false
类型转换
其他类型转数字类型
- parseInt() 转整数
- parseFloat() 转小数
- Number() 转数字 (包括整数和小数)
其他类型转字符串类型
.toString() 变量有意义
-
String() 变量没有意义
var num = 10 console.log(num.toString()) console.log(String(num))
其他类型转布尔类型
Boolean()
console.log(Boolean(1)) // true
console.log(Boolean(0)) //false
操作符
含义:一些符号,用来计算
算数运算符
- /
- %
一元运算符
这个操作符只需要一个操作数就可以运算的符号
-
++
-
前++ 前--
先参与运算,运算结束后再加1
num++ +10
num的值为10 -
后++ 后--
自身先加1,然后再参与运算
++num +10
num的值为11
-
--
二元运算符
类似于算数运算符
需要两个操作数才能运算
三元运算符
表达式1 > 表达式2 ?表达式3 :表达式4
符合运算符
由复合运算符连接起来的表达式
- +=
- -=
- *=
- /=
- %=
关系运算符
结果是布尔类型
- ">"
- "<"
- ">="
- "<="
- "=="(不严格)(数据类型不用一致)
- "==="(严格)(数据类型必须一致)
- "!="
- "!=="
逻辑运算符
结果是布尔类型
- &&(并且)
- ||(或者)
- !(非)
赋值运算符
=
字面量
把一个值直接赋值给一个变量
变量的初始化就是以字面量的方式赋值
var num = 10
var flag = true
var str = 'haha'
//以上方式都叫字面量
流程控制
- 顺序结构
- 分支结构
- if
- if-else
- if-else if-else.......(最后的else可以不用写)(一般是范围的判断)
- switch-case(default后面的break可以省略)(一般是值得判断)
- 三元表达式
- 两个分支,最终的结果是其中之一
- 运算符号: ? :
- 大多数情况,使用if-else的语句都可以使用三元表达式
- 循环结构
- while (先判断后循环,可能一次循环都没有)
- do-while(先循环一次,再判断,最少有一次循环)
- for
- for-in
关键字
break
如果在循环中使用,遇到了break,则立刻跳出当前所在循环
continue
在循环中如果遇到了continue关键字,直接开始下一次循环
数组
概念、作用
- 一组有序的数据
- 可以一次性存储多个数据
创建方式
- 构造函数
- var 数组名 = new Array() //空数组
- 数组的名字如果直接输出,那么直接就可以把数组中的数据显示出来,如果没有数据,就看不到数据
- 如果数组中没有数据,但是有长度---,数组中的每个值就是undefined
- 构造函数的方式创建数组的时候,如果在Array(一个数字)--->数组的长度(数组元素的个数) var num = new Arrey(5) //表示这个数组的长度为5,有5个元素,每个数据是undefined
- 如果在Array(多个值);这个数组中就有数据了,数组的长度就是这些数据的个数
- 字面量
- var 数组名 = [] //空数组
- 无论是构造函数的方式还是字面量的方式,定义的数组,如果有长度,那么默认是undefined
数组元素
数组中存储的每个数据,都可以叫数组的元素,比如:存储了3个数据,数组中3个元素
数组长度
就是数组的元素的个数,比如有3个元素,就说,这个数组的长度是3
数组索引(下标)
数组索引(下标):用来存储或者访问数组中的数据的,索引从0开始,到长度减1结束
数组的索引和数组的长度的关系
数组长度减1就是最大的索引值
设置数组中某个位置的值
arr[3] = 100
//arr数组中索引为3的位置值为100
获取数组中某个位置的值
var result = arr[3];
console.log(result)
数组中储存的类型可以不一样
- 例1.数组的函数调用
var arr = [
function () {
console.log("eat")
},
function () {
console.log("sleep")
},
function () {
console.log("play")
},
function () {
console.log("drink")
},
];
arr.forEach(function (ele) {
ele();
})
冒泡排序
把所有的数据按照一定的顺序进行排列(从小到大,从大到小)
函数
把一坨重复的代码封装,在需要的时候直接调用即可
作用:代码重复调用
定义:function 函数名字() {
函数体
}
调用:函数名()
注意:
- 函数需要先定义,然后才能使用
- 函数的名字:要遵循驼峰命名法
- 函数一旦重名,后面的会把前面的覆盖
- 一个函数最好就是只有一个功能
函数参数
- 在函数定义的时候,函数名字后面的小括号里的变量就是参数,目的是函数在调用的时候,对用户传进来的值操作,此时函数定义的时候后面的小括号里的变量叫参数;写了两个变量,就有两个参数,
- 在函数调用的时候,按照提示的方式,给变量赋值--->就叫传值,这个值就传到了变量(参数)中
- 形参:函数在定义的时候小括号里的变量叫形参
- 实参:函数在调用的时候小括号里传入的值叫实参,实参可以是变量也可以是值
函数的返回值
- 在函数内部有return关键字,并且在关键字后面有内容,这个内容被返回了
- 当函数调用之后,需要这个返回值,那么就定义变量接收,即可
- 如果一个函数中有return,那么这个函数就有返回值,如果一个函数中没有return,那么这个函数就没有返回值
- 如果一个函数中没有明确的返回值,那么调用的时候接收了,结果就是undefined
- 没有明确返回值:
- 函数中没有return
- 函数中有return,但是return后面没有任何内容
- 没有明确返回值:
- 形参的个数和实参的个数可以不一致
- return 下面的代码是不会执行的
arguments对象
定义一个函数,如果不确定用户是否传入了参数,或者说不知道用户传入了几个参数,没办法计算
但是,如果在函数中知道了参数的个数,也知道了每个参数的值,就可以计算,怎么办呢?
使用arguments对象,可以获取传入的每个参数的值
arguments对象也可以叫做伪数组
函数的命名方式
- 命名函数
- 函数有名字
- 匿名函数
- 函数没有名字
函数的定义
-
声明函数——函数定义
function 函数名 (){}
-
函数表达式
-
把一个函数给一个变量
var f1 = function(){}
如果是函数表达式,那么前面的变量中存储的就是一个函数,而这个变量就相当于是一个函数,就可以直接加小括号用
注意:函数表达式,赋值结束后,要加分号
注意:函数声明如果放在if-else的语句中,在IE8的浏览器中会出现问题,以后宁愿用函数表达式,都不用函数声明
-
if(true){
function f1() {
console.log("哈哈,我又变帅了");
}
}else{
function f1() {
console.log("小苏好猥琐");
}
}
f1();//在IE8中输出`小苏好猥琐`
var ff;
if(true){
ff=function () {
console.log("哈哈,我又变帅了");
};
}else{
ff=function () {
console.log("小苏好猥琐");
};
}
ff();
-
函数自调用
- 没有名字,直接调用,一次性
函数也是一种数据类型
可以通过typeof查看
数据类型是 function
函数作为参数使用
函数可以作为参数使用,如果一个函数作为参数,那么我们说这个参数(函数)叫回调函数
只要是看到了一个函数作为参数使用了,那就是回调函数
函数作为返回值使用
function f1() {
console.log("this is a function ");
return function () {
console.log("this is another function");
}
}
var ff = f1(); // "this is a function "
ff() // "this is another function"
函数的作用域
- 全局变量
- 声明的变量使用var声明的
- 全局变量可以在页面的任何位置使用
- 除了函数以外,其他任何位置定义的变量都是全局变量
- 如果页面不关闭,那么就不会释放,就会占空间,消耗内存
- 局部变量
- 在函数内部定义的变量
- 隐式全局变量:
- 声明的变量没有var
- 可以被删除(全局变量则不可以)delete
- 全局作用域
- 全局变量的使用范围
- 局部作用域
- 局部变量的使用范围
- 块级作用域
- 一对大括号就可以看成是一块,在这块区域中应以的变量,只能在这个区域中使用,但是在js中在这个块级作用域中定义的变量,外面也能使用
- js没有块级作用域
预解析
提前解析代码
- 把变量的声明提前
- 把函数的声明提前
- -提前到当前所在作用域的最上面
- 注意:变量的提升,只会在当前的作用域中提升,函数中的变量只能提升到当前作用域的最前面,不会出去
- 预解析会分段(多对<script>标签中的函数重名。预解析的时候不会发生冲突)
this指向
- 普通函数中的this是谁?-----window
- 对象.方法中的this是谁?----当前的实例对象
- 定时器方法中的this是谁?----window
- 构造函数中的this是谁?-----实例对象
- 原型对象方法中的this是谁?---实例对象
面向对象
编程思想
根据需求,分析对象,找到对象有什么特征和行为,通过代码的方式来实现需求,要想实现这个需求,就要创建对象,要想创建对象,就应该显示有构造函数,然后通过构造函数来创建对象.,通过对象调用属性和方法来实现相应的功能及需求
为什么要使用面向对象编程
首先JS不是一门面向对象的语言,JS是一门基于对象的语言,那么为什么学习js还要学习面向对象,因为面向对象的思想适合于人的想法,编程起来会更加的方便,及后期的维护
在JS中如何使用面向对象编程
面向对象的编程语言中有类(class)的概念(也是一种特殊的数据类型),但是JS不是面向对象的语言,所以,JS中没有类(class),但是JS可以模拟面向对象的思想编程,JS中会通过构造函数来模拟类的概念(class)
特征
封装
- 一个值存储在变量中
- 一坨重复的代码放在一个函数中
- 一系列的属性放在一个对象中
- 一些功能类似的函数(方法)放在一个对象中
- 好多相类似的对象放在一个js文件中
继承
- 首先继承是一种关系,类(class)与类之间的关系,JS中没有类,但是可以通过构造函数模拟类,然后通过原型来实现继承
- 继承也是为了数据共享,js中的继承也是为了实现数据共享
多态
- 一个对象有不同的行为,或者是同一个行为针对不同的对象,产生不同的结果,要想有多态,就要先有继承,js中可以模拟多态,但是不会去使用,也不会模拟
万物皆对象
-
什么是对象?
有属性和方法,具体特指的某个事物
一组无序属性的集合的键值对,属性的值可以是任意的类型
-
对象的特点:
有特征
有行为
创建对象的方式
-
1.调用系统的构造函数创建对象
var person = new Object(); person.name = 'cysky'; person.age = 22; person.eat = function () { console.log("我饿了,我要吃东西") }; person.eat(); console.log(person.name)
-
2.自定义构造函数创建对象
function Person (name,age,sex) { this.name = name; this.age = age; this.sex = sex; this.eat = function () { console.log("我饿了,我要吃东西") }; } var hu = new Person('cysky', 22 , 'male');//创建对象————实例化一个对象,同时对属性初始化 console.log(hu.name); hu.eat()
- 构造函数与函数的区别
- 名字是不是大写(首字母)
- 自定义构造函数创建对象
- 先自定义一个构造函数,再创建对象
-
自定义构造函数的步骤
- 在内存中申请一块空闲空间
- 把this设置为当前的对象
- 设置对象的属性和方法的值
- 把this这个对象返回
- 构造函数与函数的区别
-
3.字面量的方式创建对象
//字面量方式创建对象 //一般写法 var person = {}; person.name = 'cysky'; person.age= 22; person.eat = function () { console.log("我饿了,我要吃东西") }; //简写方式 var person = { name:'cysky', age:20, eat:function(){ console.log("我饿了,要吃东西") } }; console.log(person.name); person.eat()
- 缺点:一次性函数
-
4.工厂模式创建对象
function person(name,age,sex){ var obj = {}; obj.name = name; obj.age = age; obj.sex = sex; obj.eat = function () { console.log("我饿了,我要吃东西") }; return obj; } var hu = person('cysky',20,'male'); hu.eat(); console.log(hu.name)
工厂模式创建对象与自定义和构造函数创建对象的差别:
- 函数名小写
- 有new 有return
- 直接调用函数就可以创建对象
如何获取该对象(变量)
- instanceof——值是布尔类型
Json数据格式
一般都是成对,键值对
Json也是一个对象,一般Json合适的数据无论是键还是值都是用双引号引起来
数据类型
-
原始数据类型
number string boolean undefined null object
-
基本类型(简单类型),值类型:number string boolean
- 值类型之间传递,传递的是值
-
复杂类型(引用类型):object
- 引用类型之间传递,传递的是地址(引用)
空类型:undefined null
内置对象
JS中有三中对象:
- 内置对象——JS系统自带
- Math
- Math.PI
- Math.E
- Math.abs()
- Math.ceil()——向上取整
- Math.floor()——向下取整
- Date
- var dt = new Date();
- console.log(dt.getFullYear());——获取年份
- console.log(dt.getMonth()+1);——获取月份(从0开始)
- console.log(dt.getDate());——获取日期
- console.log(dt.getHours());——获取小时
- console.log(dt.getMinutes());——获取分钟
- console.log(dt.getSeconds());——获取秒
- console.log(dt.getDay());——获取星期(星期日是0)
- console.log(dt.toDateString());——英文的日期
- console.log(toLocaleDateString());——数字格式日期
- console.log(dt.toTimeString())——小时分钟秒
- console.log(dt.toLocalTimeString())——小时分钟秒
- console.log(dt.valueOf());——毫秒值(从1971年开始计算)
- console.log(dt.toString())——转成字符串
- String
- 字符串可以看成是字符组成的数组,但是JS中没有字符类型
- 字符是一个一个的,在别的语言中,字符用一对单引号括起来
- 在JS中字符串可以使用单引号也可以使用双引号,因为字符串可以看成是一个数组,所以,可通过for循环进行遍历
- 字符串特性:不可变性,字符串的值是不能改变的
- 字符串的值看起来是改变了,其实是指向改变了
- 字符串可通过索引的方式访问字符串(只读)
- .length——字符串的长度
- .charAt(索引)——返回值是指定索引位置的字符串,超出索引返回空字符串
- .fromCharCode(数字值,可以是多个参数)——返回的是ASCII码对应的值
- .concat(字符串1,字符串2......)——返回的是拼接之后的字符串
- .indexOf(要找的字符串,从某个位置开始的索引)——返回的是这个字符串的索引值,没找到则返回-1
- .lastIndexOf(要找的字符串)——从后向前找,但是索引仍然是从左向右的方式,找不到则返回-1
- .replace("原来的字符串","新的字符串")——用来替换字符串的
- .slice(开始的索引,结束的索引)——从索引5的位置开始提取,到索引为10的前一个结束,没有10,并返回这个提取后的字符串
- .split("要干掉的字符串",切割后留下的个数)——切割字符串
- .substr(开始的位置,个数)——返回的是截取后的新的字符串
- .substring(开始的索引,结束的索引)——返回截取后的字符串,不包含结束的索引的字符串
- .toLocaleLowerCase()——转小写
- toLowerCase()——转小写
- toLocaleUpperCase()——转大写
- toUpperCase()——转大写
- trim()——干掉字符串两端的空格
- Array
- .isArray()——判断这个对象是不是数组
- .conact(数组,数组,数组,...)——组合一个新的数组
- .every(函数)——返回值是布尔类型,函数作为参数使用,函数中有三个参数,第一个参数是元素的值,第二个参数是索引值,第三个参数是原来的数组(没用),如果这个数组中的每个元素的值都符合条件,最后才返回的是true
- .filter(函数)——返回的是数组中每一个元素都复合条件的元素,组成了一个新的数组
- .push(值)——把值追加到数组中,加到最后了---返回值也是追加数据之后的数组长度
- .pop()——删除数组中最后一个元素,返回值就是删除的这个值
- .shift()——删除数组中第一个元素,返回值就是删除的这个值
- .unshift()——向数组的第一个元素前面插入一个新的元素,----返回值是插入后的新数组
- .forEach(函数)——遍历数组用---相当于for循环
- .indexOf(元素值)——返回的是索引,没有则是-1
- .join("字符串")——返回的是一个字符串
- .map(函数)——数组中的每个元素都要执行这个函数,把执行后的结果重新的全部的放在一个新的数组中
- .reverse()——反转数组
- .sort()——排序的,可能不稳定,如果不稳定,请写MDN中的那个固定的代码
- .arr.slice(开始的索引,结束的索引)——把截取的数组的值放在一个新的数组中,但是不包含结束的索引对应的元素值
- .splice(开始的位置,要删除的个数,替换的元素的值)——一般是用于删除数组中的元素,或者是替换元素,或者是插入元素
- Object
- Math
- 自定义对象——自己定义的构造函数创建的对象
- 浏览器对象——BOM
基本包装类型
普通变量不能直接调用属性或者方法
对象可以直接调用属性和方法
基本包装类型:本身是基本类型,但是在执行代码的过程中,如果这种类型的变量调用了属性或者是方法,那么这种类型就不再是基本类型了,而是基本包装类型,这个变量也不是普通的变量了,而是基本包装类型对象
原型
作用
- 数据共享,节省内存空间
- 为了实现继承
构造函数和实例对象之间的关系
- 实例对象是通过构造函数来创建的,创建的过程叫实例化
//创建构造函数
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
this.eat = function () {
console.log("我饿了,我要吃东西")
}
}
//实例化一个对象
var hu = new Person('cysky',20,'male');
console.dir(hu);//实例对象
console.dir(Person);//构造函数的名字
console.log(hu.constructor===Person);//实例对象的构造器是指向Person的,结果是true,所以,这个实例对象hu就是通过Person来创建的
构造函数创建对象带来的问题--原型使用的原因
调用同样的方法却会创建不同的对象,消耗内存
function Person(name, age) {
this.name = name;
this.age = age;
this.eat = function myEat() {
console.log("吃桂花糕");
};
}
var per1 = new Person("小白", 5);
var per2 = new Person("小黑", 6);
console.dir(per1);
console.dir(per2);
console.log(per1.eat === per2.eat);//false——创建了不同的对象
解决办法:
function myEat() {
console.log("我要吃桂花糕")
}
function Person(name,age){
this.name = name;
this.age = age;
this.eat = myEat;//将这个方法作为公用方法
}
var per1 = new Person('小白', 5);
var per2 = new Person('小黑', 6);
console.log(per1.eat === per2.eat);//true——调用的是同一方法
引入原型:
通过原型来解决---------数据共享,节省内存空间
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.eat = function () {
console.log("我饿了,我要吃东西")
};//通过构造函数的原型添加一个方法
var per1 = new Person('小黑',20);
var per2 = new Person('小白',22);
console.log(per1.eat === per2.eat)//true——调用同一方法
原型的简单语法
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
Person.prototype = {
//**手动修改构造器指向**
constructor:Person,
hieght:180,
weight:"80kg",
eat:function(){
console.log("大家都有饭吃")
},
play:function(){
console.log("一起玩游戏")
}
};
var per1 = new Person('小黑',20,"male");
console.log(per1.hieght);
console.log(per1.name);
per1.play()
原型总结
- 实例对象中有proto这个属性,叫原型,它是一个对象,这个属性是给浏览器使用,不是标准的属性----->proto----->可以叫原型对象
- 构造函数中有prototype这个属性,叫原型,也是一个对象,这个属性是给程序员使用,是标准的属性------>prototype--->也可以叫原型对象
- 实例对象的proto和构造函数中的prototype相等
- 又因为实例对象是通过构造函数来创建的,构造函数中有原型对象prototype, 实例对象的proto指向了构造函数的原型对象prototype
构造函数、实例对象、原型对象三者之间的关系
- 构造函数可以实例化对象
- 构造函数中有一个属性叫prototype,是构造函数的原型对象
- 构造函数的原型对象(prototype)中有一个constructor构造器,这个构造器指向的就是自己所在的原型对象所在的构造函数
- 实例对象的原型对象(proto)指向的是该构造函数的原型对象
- 构造函数的原型对象(prototype)中的方法是可以被实例对象直接访问的
利用原型共享数据
- 什么样的数据需要写在原型中
- 需要共享的数据
- 原型中可以共享那些数据?
- 属性
- 方法
function Student(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
Student.prototype.eat = function () {
console.log("今天中午吃外卖")
};//在Student原型中添加方法
Student.prototype.weight = "55kg";//在Student原型中添加属性
var per1 = new Student("小黑", 20, "male");//实例化一个对象
var per2 = new Student("小白", 22, "female");
console.log(per1.eat === per2.eat);//true
console.log(per1.weight);
console.log(per2.weight);
console.log(per1.name);
console.log(per2.name)
相互访问原型中的方法
function Animal(name,age){
this.name = name;
this.age = age;
}
Animal.prototype.eat = function () {
console.log("我要吃饭啦");
this.play()
};
Animal.prototype.play = function () {
console.log("主人陪我玩");
this.sleep()
};
Animal.prototype.sleep = function () {
console.log("我要睡觉啦")
};
var dog = new Animal("小花",3);
dog.eat()//我要吃饭啦 主人陪我玩 我要睡觉啦
实例对象使用属性和方法层层搜索
- 实例对象使用的属性或者方法,先在实例中查找,找到了则直接使用,找不到则,去实例对象的proto指向的原型对象prototype中找,找到了则使用,找不到则报错
function Person(name, age) {
this.name = name;
this.age = age;
this.eat = function () {
console.log("我还不饿,不想吃东西")
}
}
Person.prototype.sex = 'male';
Person.prototype.eat = function () {
console.log("我饿了,我要吃东西")
};
var per1 = new Person("cysky", 22);
per1.eat();//优先输出实例对象中有的属性和方法
console.log(per1.sex)//实例对象中没有的属性会在原型里去找
为内置对象添加原型和方法
- 相当于在改变源码
//反转字符串
String.prototype.myReverse = function () {
for(var i = this.length-1;i>=0;i--){
console.log(this[i])
}
};
var str = "abcdefg";
str.myReverse();
将局部变量变成全局变量
- 把局部变量给window就可以了(运用函数的自调用)
(function (形参) {
var num=10;//局部变量
})(实参);
console.log(num);
(function (window) {
var num=10;//局部变量
//js是一门动态类型的语言,对象没有属性,点了就有了
window.num=num;
})(window);
console.log(num);
原型链
- 是一种关系,是实例对象和原型对象之间的关系,是通过原型("proto")来联系的
使用对象就要先有构造函数
如果有公用的属性和方法,使用原型
实例化对象,并初始化
- 实例对象中的原型proto(浏览器用)和构造函数的原型prototype(程序员用)指向相同
- 实例对象中的原型指向构造函数中的原型
- 原型的指向是可以改变的
function Person(age) {
this.age = age;
}//构造函数
Person.prototype.eat = function () {
console.log("吃东西");
};//原型
function Student(sex){
this.sex = sex;
}
Student.prototype.sayHi = function () {
console.log("hello");
};
Student.prototype = new Person(10);
var stu = new Student("男");
stu.eat();
stu.sayHi();//报错:因为改变了原型对象的指向
this
构造函数中的this是指实例对象
原型中的this也是指实例对象
function Person(age){
this.age = age;//构造函数中的this
console.log(this);
}
Person.prototype.eat = function () {
console.log(this);//原型中的this
console.log("吃饭了吗");
};
var per = new Person(10);
per.eat();
console.log(per)//结果同构造函数和原型中的this一样
继承
JS中通过原型来实现继承
function Animal(name, weight) {
this.name = name;
this.weight = weight;
}//动物的构造函数
Animal.prototype.eat = function () {
console.log("animal can eat")
};//动物原型
function Dog(color){
this.color = color;
}//狗的构造函数
Dog.prototype = new Animal("哮天犬","50kg");//狗的原型指向改变
Dog.prototype.bitePerson = function () {
console.log("哼~汪汪~咬死你")
};//先改变指向,再添加原型
function ErHa(sex) {
this.sex = sex;
}//二哈构造函数
ErHa.prototype = new Dog('黑白色');//二哈原型
ErHa.prototype.playHost = function () {
console.log("哈哈~要坏衣服,要坏桌子,拆家..嘎嘎...好玩,开心不,惊喜不,意外不")
};
var erha = new ErHa("雄性");
console.log(erha.name,erha.weight,erha.color,erha.sex);
erha.eat();
erha.bitePerson();
erha.playHost();
借用构造函数
- 为了数据共享,改变原型指向,做到了继承---通过改变原型指向实现的继承
- 缺陷:因为改变原型指向的同时实现继承,直接初始化了属性,继承过来的属性的值都是一样的了,所以,这就是问题,只能重新调用对象的属性进行重新赋值。
- 解决方案:继承的时候,不用改变原型的指向,直接调用父级的构造函数的方式来为属性赋值就可以了------借用构造函数:把要继承的父级的构造函数拿过来,使用一下就可以了
- call
- 借用构造函数:构造函数名字.call(当前对象,属性,属性,属性....);
- 解决了属性继承,并且值不重复的问题
- 缺陷:父级类别中的方法不能继承
function Person(name, age, sex, weight) {
this.name = name;
this.age = age;
this.sex = sex;
this.weight = weight;
}
Person.prototype.sayHi = function () {
console.log("您好")
};
function Student(name, age, sex, weight, score) {
Person.call(this, name, age, sex, weight);
this.score = score;
}
var stu1 = new Student("小明", 10, "男", "10kg", "100");
var stu2 = new Student("小红", 20, "女", "20kg", "120");
console.log(stu1.name, stu1.age, stu1.sex, stu1.weight, stu1.score);
console.log(stu2.name, stu2.age, stu2.sex, stu2.weight, stu2.score);
继承组合
原型实现继承
借用构造函数实现继承
组合继承:原型继承+借用构造函数继承
Person.prototype.sayHi = function () {
console.log("hello")
};
function Student(name,age,sex,score){
Person.call(this,name,age,sex);//使用call方法继承属性
this.score = score;
}
Student.prototype = new Person();//使用原型继承方法
Student.prototype.eat = function () {
console.log("eat something")
};
var stu = new Student("小黑",20,"男","100分");
console.log(stu.name, stu.age, stu.sex, stu.score);
stu.sayHi();
stu.eat();
拷贝继承
拷贝继承;把一个对象中的属性或者方法直接复制到另一个对象中
function Person() {
}
Person.prototype.age = 10;
Person.prototype.sex = "male";
Person.prototype.height = "180cm";
Person.prototype.eat = function () {
console.log('eat something');
};
var obj2 = {};//Person的构造中有原型prototype,prototype就是一个对象,那么里面,age,sex,height,play都是该对象中的属性或者方法
for (var key in Person.prototype) {
obj2[key] = Person.prototype[key];
}
console.dir(obj2);
obj2.eat();
call&apply
作用:改变this的指向
Person.prototype.sayHi = function (x, y) {
console.log("你好呀:" + this.sex);
return 1000;
};
var per = new Person(10, "男");
per.sayHi();
console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
function Student(name, sex) {
this.name = name;
this.sex = sex;
}
var stu = new Student("小明","人妖");
var r1 = per.sayHi.apply(stu, [10,20]);//你好呀:人妖
var r2 = per.sayHi.call(stu,10,20);//你好呀:人妖
console.log(r1);//1000
console.log(r2)//1000
区别:参数传递的方式是不一样的
- apply的使用语法:
- 函数名字.apply(对象,[参数1,参数2,...]);
- 方法名字.apply(对象,[参数1,参数2,...]);
- call的使用语法:
- 函数名字.call(对象,参数1,参数2,...);
- 方法名字.call(对象,参数1,参数2,...);
只要是想使用别的对象的方法,并且希望这个方法是当前对象的,那么就可以使用apply或者是call的方法改变this的指向
apply和call方法实际上并不在函数这个实例对象中,而是在Function的prototype中
function f1() {
console.log(this+":======>调用了");
}
//f1是函数,也是对象
console.dir(f1);
//对象调用方法,说明该对象中有这个方法
f1.apply();
f1.call();
console.log(f1.__proto__ === Function.prototype);
//所有的函数都是Function的实例对象
console.log(Function.prototype);
//apply和call方法实际上并不在函数这个实例对象中,而是在Function的prototype中
console.dir(Function);
bind
- bind方法是复制的意思,参数可以在复制的时候传进去,也可以在复制之后调用的时候传入进去
- apply和call是调用的时候改变this指向
- bind方法,是复制一份的时候,改变了this的指向
- 使用的语法
- 函数名字.bind(对象,参数1,参数2,...);---->返回值是复制之后的这个函数
- 方法名字.bind(对象,参数1,参数2,...);---->返回值是复制之后的这个方法
闭包
概念
函数A中,有一个函数B,函数B中可以访问函数A中定义的变量或者是数据,此时形成了闭包(不严谨)
闭包模式
函数模式
对象模式
作用
缓存数据,延长作用于链
缺点
缓存数据
总结
如果想要缓存数据,就把这个数据放在外层的函数和里层的函数的中间位置
局部变量是在函数中,函数使用结束后,局部变量就会被自动的释放
闭包后,里面的局部变量的使用作用域链就会被延长
function f2() {
num = 10;
return function () {
num ++;
return num;
}
}
var ff = f2();
console.log(ff());
console.log(ff());
console.log(ff());
//产生相同的随机数
function f1() {
var num = parseInt(Math.random()*10+1);
return function () {
console.log(num)
}
}
var ff = f1();
ff();
ff();
ff();
递归
函数中调用函数自己,此时就是递归,递归一定要有结束的条件
//求n个数字的和,5 计算1+2+3+4+5
var sum = 0;
for (var i = 0; i <= 5; i++) {
sum += i;
}
console.log(sum);
function getSum(x){
if(x===1){
return 1
}
return x + getSum(x-1);
}
console.log(getSum(5))
拷贝
浅拷贝
拷贝就是复制,就相当于把一个对象中的所有的内容,复制一份给另一个对象,直接复制,或者说,就是把一个对象的地址给了另一个对象,他们指向相同,两个对象之间有共同的属性或者方法,都可以使用
var obj1={
age:10,
sex:"男",
car:["奔驰","宝马","特斯拉","奥拓"]
};
var obj2={};
function extend(a,b) {
for(var key in a){
b[key]=a[key];
}
}
extend(obj1,obj2);
console.dir(obj2);
console.dir(obj1);
深拷贝
拷贝还是复制,深:把一个对象中所有的属性或者方法,一个一个的找到.并且在另一个对象中开辟相应的空间,一个一个的存储到另一个对象中
var obj1={
age:10,
sex:"男",
car:["奔驰","宝马","特斯拉","奥拓"],
dog:{
name:"大黄",
age:5,
color:"黑白色"
}
};
var obj2={};//空对象
//通过函数实现,把对象a中的所有的数据深拷贝到对象b中
function extend(a,b) {
for(var key in a){
//先获取a对象中每个属性的值
var item=a[key];
//判断这个属性的值是不是数组
if(item instanceof Array){
//如果是数组,那么在b对象中添加一个新的属性,并且这个属性值也是数组
b[key]=[];
//调用这个方法,把a对象中这个数组的属性值一个一个的复制到b对象的这个数组属性中
extend(item,b[key]);
}else if(item instanceof Object){//判断这个值是不是对象类型的
//如果是对象类型的,那么在b对象中添加一个属性,是一个空对象
b[key]={};
//再次调用这个函数,把a对象中的属性对象的值一个一个的复制到b对象的这个属性对象中
extend(item,b[key]);
}else{
//如果值是普通的数据,直接复制到b对象的这个属性中
b[key]=item;
}
}
}
extend(obj1,obj2);
console.dir(obj1);
console.dir(obj2);