ECMA基础(五)
函数基础
数学的函数:
//x, y 任意x有确定的y 与之对应//x自变量//y -> x//y = f(x) x => 参数/定义域 y => 函数的值域 => 函数值是确定的
计算机的函数:
//函数式编程function test(a, b, c) {//执行语句}
高内聚低耦合概念
耦合问题:代码块里有重复高的代码
让一个功能体(代码块)具有强功能性和高独立性
-> 模块的单一责任制(独立完成功能且不依赖其他模块)
高内聚
模块有独立的功能
低耦合
重复代码抽象化
函数的作用
解耦有很多基于函数的方式
//解耦合 -> 最好的方式是函数if (3 > 0) {for (var i = 0; i < 10; i++){console.log(i);}}if (2 > 0) {for (var i = 0; i < 10; i++){console.log(i);}}if (1 > 0) {for (var i = 0; i < 10; i++){console.log(i);}}//如何解耦function test() {for (var i = 0; i < 10; i++){console.log(i);}}if (3 > 0) {test();}if (2 > 0) {test();}if (1 > 0) {test();}
函数基本写法
函数声明
//引用值 声明函数的关键字function test(参数) {//函数的执行语句;}test();
函数只有被调用的时候才会执行
函数名的命名规则
- 不能数字开头
- 可以以字母,下划线,
$开头 - 可以包含数字
- 小驼峰命名法,复合单词
函数种类
定义函数的方法
- 函数声明
- 函数表达式(字面量)
匿名函数
function(){}
匿名函数表达式/函数字面量
var test = function(){}
关于字面量:
- 字符串:
'' - 对象:
{} - 数组:
[] - 函数:
function(){}
函数的组成部分
//function 关键字//函数名//参数 可选//返回值 可以没有返回值//默认系统加returnfunction 函数名(参数) {}
形参实参
在函数调用的时候可以给变量赋值,外部可以通过参数给内部的变量赋值,当函数定义的时候参数是没有值的,仅仅是占位用的
形参
占位 -> 形式上占位 -> 形式参数 -> 形参
实参
当调用的时候,有实际参数的赋值
实际参数 -> 实参 -> 给占位符赋值
赋值的时候,参数都是一一对应的
var a = Number(window.prompt('a'));var b = Number(window.prompt('b'));function test(a,b) {console.log(a + b);}test(a, b);
- 实参参数数量 小于 形参参数数量 不报错且只打印实参数量,形参为
undefined - 实参参数数量 大于 形参参数数量 不报错且只打印形参数量
函数内部其实是知道实参的个数
arguments参数
function test(a, b) {for (var i = 0; i < arguments.length; i++){console.log(arguments[i]); //1 2 3}}test(1, 2, 3);
形参和实参的长度能否知道
function test(a, b) {//形参长度console.log(test.length); //2//实参长度console.log(arguments.length); //3}test(1, 2, 3);
可以取实参的其中一位
function test(a, b) {console.log(arguments[1]); //2}test(1, 2, 3);
实参求和:一个函数被调用时,累加他的实参值
//问题:不知道实参有多少个function sum() {var a = 0;for (var i = 0; i < arguments.length; i++){a += arguments[i];}console.log(a);}sum(1, 2, 3, 4, 5); //15
函数内部是可以更改实参的值
function test(a, b) {a = 3;console.log(arguments[0]); //3}test(1, 2);
若有实参传值函数内部可以修改实参的值,反之不能修改
function test(a, b) {b = 3;console.log(arguments[1]); //undefined}test(1);
映射
无论实参如何赋值,形参都会跟着变,形参数组里必须有对应的值
如果实参和形参的对应关系是存在的,那么就存在必然的映射关系
function test(a, b) {a = 3;console.log(arguments[0]); //3}test(1, 2);//test(1, 2) => test(3, 2);
return
函数内部return
- 函数内部没有写
returnJS引擎会默认隐式加上 - 函数内部
return下面的语句是不会执行的 return后面接数据会返回相应的数据值
function test(name) {//真 => name//假往后走找最后一位return name || '你没有输入名字';}// console.log(test('1111')); //1111console.log(test()); //'你没有输入名字'
变量类型
- 全局变量
- 局部变量
函数内部能访问外部变量
b = 2;function test() {var a = 1;console.log(b); //2}test()
外部不能访问函数内部变量
b = 2;function test() {var a = 1;console.log(b); //2}test()console.log(a); //报错 a not defineconsole.log(typeof(a)); //undefined
a = 1;function test1() {var b = 2;console.log(a); //1function test2() {var c = 3;console.log(b); //2}test2();console.log(c); //报错}test1()
每个函数都有自己独立的作用域
function test1() {var a = 1;console.log(b); //报错}function test2() {var b = 2;console.log(a); //报错}test1();test2();
参数默认值
参数的默认值为undefined
function test(a, b) {console.log(a); //1console.log(b); //undefined}test(1);
不传递实参的情况下初始默认值
function test(a = 1, b = 2) {console.log(a); //1console.log(b); //2}test();
es6写法
function test(a = 1){}, es5且IE8不支持
保留第一个参数的默认值,更改另外的参数
关于形参a和实参arguments[0] 对应且存储的地方不同但具有映射关系
- 选非
undefined的数据 - 形参实参都为
undefined结果也是undefine
function test(a = 1, b) {console.log(a); //1console.log(b); //2}test(undefined, 2);
function test(a = undefined, b) {console.log(a); //1console.log(b); //2}test(1, 2);
es5版本的情况下初始化形参的默认值
写法一
function test(a, b) {//实参1存在返回实参1,不存在返回1//实参2存在返回实参1,不存在返回2var a = arguments[0] || 1;var b = arguments[1] || 2;console.log(a + b);}test(); //3test(3, 4); //7
写法二
function test(a, b) {var a, b;if (typeof (arguments[0]) !== 'undefined') {a = arguments[0];} else {a = 1;}if (typeof (arguments[1]) !== 'undefined') {b = arguments[1];} else {b = 2;}console.log(a + b);}test(); //3test(3, 4); //7
写法二可以用三元运算改写
function test(a, b) {var a, b;typeof (arguments[0]) !== 'undefined' ? arguments[0] : a = 1;typeof (arguments[1]) !== 'undefined' ? arguments[1] : b = 2;console.log(a + b);}test(); //3test(3, 4); //7
递归
函数自己调用自己,要考虑性能问题
- 找规律
- 找出口
总结:总是走到出口的时候,再向上一步一步的赋值计算,然后返回结果
对象
var teacher = {name: 'zhangsan',age: 32,sex: 'male',height: 176,weight: 160,teach: function () {console.log('I am teaching Java');},smoke: function () {console.log('I am smoking');},eat: function () {console.log('I am having a dinner');}}
对象创建
- 字面量/直接量
- 构造函数
对象的调用方式
var myLang = {No1: 'HTML',No2: 'CSS',No3: 'JavaScript',myStudyingLang: function (num) {//myLang['No1'] -> this['No' + num]console.log(this['No' + num]);}}myLang.myStudyingLang(1); //HTMLobj = {name: '123'}console.log(obj['name']); //123
字面量
var obj = {name: 'zhangsan',sex: 'male'}obj.name = 'lisi';
构造函数,系统自带
var obj = new Object();obj.name = 'lisi';obj.sex = 'male';console.log(obj);
执:
执行方法
teacher.drink();
增:
属性增加
teacher.address = 'beijing';
方法增加
teacher.drink = 'I am drinking beer';
删:
移除属性
delete teacher.address;
删除方法
delete teacher.teach;
改:
属性更改
teacher.height = 166;
查:
属性查找
teacher.eat();
this
代表对象本身
案例:出勤
var attendance = {students: [],total: 6,join: function (name) {this.students.push(name);if (this.students.length === this.total) {console.log(name + '到课,学生已到齐');} else {console.log(name + '到课,学生未到齐');}},leave: function (name) {//this.students.indexOf(name) => 数字元素索引//indexOf() = -1 => 不存在该数组元素//splice(index, 删除位数)var idx = this.students.indexOf(name);if (idx !== -1) {this.students.splice(idx, 1);}console.log(name + '早退了');console.log(this.students);},classOver: function () {this.students = [];console.log('已下课');}}attendance.join('zhangsan'); //["zhangsan"]attendance.join('lisi'); //["zhangsan", "lisi"]attendance.join('wangwu'); //["zhangsan", "lisi", "wangwu"]attendance.leave('lisi'); //["zhangsan", "wangwu"]attendance.classOver();
包装类
思考:数字和字符串是否有自己的属性和方法?
var a = 1;var b = 'abc';a.len = 3;b.add = 'bcd';a.reduce = function () {}
原始值并没有自己的方法和属性
思考:数字是不是一定是原始值?
var a = 1;console.log(a); //1var b = new Number(a);console.log(b); //Number {1}b.len = 1;console.log(b); //Number {1, len: 1}b.add = function () {console.log(1);}console.log(b); //Number {1, len: 1, add: ƒ}
能看出成为实例化对象后的数字对象,且可以设置属性和方法
//对象 + 数字也能参与运算且运算结果变为原始值var a = 1;var b = new Number(a);b.len = 1;b.add = function () {console.log(1);}var c = 3;var d = b + 1;console.log(a + c); //4console.log(d); //2
经过包装参与的对象运算后又变为原始值
var a = 1;console.log(a); //1var aa = new Number(1);console.log(aa); //Number {1}aa.name = 'aa';console.log(aa); //Number {1, name: "aa"}var bb = aa + 1;console.log(bb); //2console.log(aa); //Number {1, name: "aa"}
运算后又变回数字对象
系统内置的构造函数包装方法:
new Number()new String()new Boolean()
经过包装参与运算后又变为原始值
var a = 'abc';console.log(a); //abcvar aa = new String('abc');aa.name = 'aa';console.log(aa); //String {"abc", name: "aa"}var bb = aa + 'bcd';console.log(bb); //abcbcd
var test = new Number(undefined);console.log(test); //Number {NaN}var test = new Number(null);console.log(test); //Number {0}var test = new String(undefined);console.log(test); //String {"undefined"}var test = new String(null);console.log(test); //String {"null"}
undefined和null是不可以设置任何的属性和方法
console.log(undefined.length); //报错console.log(null.length); //报错
JavaScript包装类的过程
var a = 123;a.len = 3;console.log(a.len); //undefined
var str = 'abc';console.log(str.length); //3
var a = 123;a.len = 3;//原始值没有属性和方法//JS判断:new Number(123).len = 3; => 无法保存 => delete/*** 相当于* var obj = {* name: 'obj'* }* console.log(obj); //{name: "obj"}* delete obj.name;* console.log(obj); //{}*/console.log(a.len); //undefined
var str = 'abc';console.log(str.length); //3console.log(new String(str).length); //3
数组截断arr.length
var arr = [1, 2, 3, 4, 5];arr.length = 3;console.log(arr); //[1, 2, 3]arr.length = 6;console.log(arr); //[1, 2, 3, 4, 5, empty]
字符串截断
var str = 'abc';str.length = 1;console.log(str); //abc//new String(str).length = 1 => 无法保存 => deleteconsole.log(str.length); //3
笔试题
var name = 'languiji';name += 10; //'languiji10'var type = typeof (name); //'string'if (type.length === 6) {type.text = 'string'; //new String(type).text = 'string' => 无法保存 => delete}console.log(type.text); //undefined
改造
var name = 'languiji';name += 10;console.log(typeof (name)); //'string'var type = new String(typeof (name));console.log(type); //String {"string"}if (type.length === 6) {type.text = 'string';}console.log(type.text); //string
枚举
遍历数组
//循环数组var arr = [1, 2, 3, 4, 5];//遍历过程for (var i = 0; i < arr.length; i++) {console.log(arr[i]);}
//循环数组var arr = ['red', 'white', 'black', 'pink', 'blue'];//遍历过程for (var i in arr) {console.log(i); //0 1 2 3 4console.log(arr[i]); //red white black pink blue}
遍历对象
//狭义的对象var car = {brand: 'Benz',color: 'red',displacement: '3.0',lang: '5',width: '2.5'}//key: 键名//car[key]: 键值for (var key in car) {console.log(key);//brand color displacement lang widthconsole.log(car[key]);//JS引擎 car.key -> car['key'] -> undefined}
hasOwnProperty()
返回值:布尔值
作用于拷贝
for ... in遍历会遍历出所有对象包括原型上的属性
function Car() {this.brand = 'Benz';this.color = 'red';this.displacement = '3.0';}Car.prototype = {lang: '5',width: '2.5'}Object.prototype.name = 'Object';var car = new Car();//遍历for (var key in car) {console.log(key + ': ' + car[key]);}/*** console.log(key + ': ' + car[key]);* brand: Benz* color: red* displacement: 3.0* lang: 5* width: 2.5* name: Object*/
只想打印自定义属性而不是原型上的属性
function Car() {this.brand = 'Benz';this.color = 'red';this.displacement = '3.0';}Car.prototype = {lang: '5',width: '2.5'}Object.prototype.name = 'Object';var car = new Car();//遍历for (var key in car) {if (car.hasOwnProperty(key)) {console.log(car[key]);}}/*** console.log(car[key]);* Benz* red* 3.0*/
in
返回值:布尔值
判断属性是否存在于对象里
var car = {brand: 'Benz',color: 'red'}//car['displacement']console.log('displacement' in car); //false
//hasOwnProperty 排除原型//in 不排除原型function Car() {this.brand = 'Benz';this.color = 'red';}Car.prototype = {displacement: '3.0'}var car = new Car();console.log('displacement' in car); //true
instanceof()
返回值:布尔值
判断对象是否是构造函数实例化出来的
缺点:不能判断原始值
可以拿来判断数据类型(不推荐)
A对象的原型里到底有没有B的原型
function Car() {}var car = new Car();console.log(car instanceof Car); //trueconsole.log(car instanceof Object); //truefunction Person() {}var p = new Person();console.log(p instanceof Car); //false
在原型链上有重合的都为true
console.log([] instanceof Array); //trueconsole.log([] instanceof Object); //trueconsole.log({} instanceof Object); //true
callee/caller
callee返回的是正在被执行的函数对象
function test(a, b, c) {//callee返回的是正在被执行的函数对象//callee返回的是函数本身console.log(arguments.callee);//function test(a, b, c) {...}console.log(arguments.callee.length); //3}test(1, 2, 3);
//arguments.callee.length函数本身形参的长度//test.length函数本身形参的长度//arguments.length实参的长度function test(a, b, c) {console.log(arguments.callee.length == test.length); //trueconsole.log(arguments.callee.length === test.length); //true}test(1, 2, 3);
callee在哪个函数内部指向哪个函数
function test1() {console.log(arguments.callee);function test2() {console.log(arguments.callee);}test2();}test1();
用递归的方式累加n位
function sum(n) {if (n <= 1) {return 1;}//n + (n - 1)//10 + 9//10 + 9 + 8 ...return n + sum(n - 1);}var res = sum(10);console.log(res); //55
希望是一个自启动函数交给全局变量
var sum = (function (n) {if (n <= 1) {return 1;}//此时找不到函数名//此时可以用calleereturn n + arguments.callee(n - 1);})(10);console.log(sum); //55
caller
调用当前函数的函数引用
返回调用所在的函数本身
test1();function test1() {test2();}function test2() {//调用当前函数的函数引用//在哪个函数内部调用返回改函数本身console.log(test2.caller); //function test1() {...}}
严格模式报错
'use strict'; //报错test1();function test1() {test2();}function test2() {console.log(test2.caller);}
对象克隆
var person1 = {name: 'zhangsan',age: 18,sex: 'male',height: 180,weight: 140}//希望拥有person1所有的属性和方法//person1的引用值地址赋值给了person2//person2修改属性的时候person1也会随之更改//因为他们指向同一的存储空间var person2 = person1;person2.name = 'lisi';console.log(person1);//{name: "lisi", age: 18, sex: "male", height: 180, weight: 140}console.log(person2);//{name: "lisi", age: 18, sex: "male", height: 180, weight: 140}
此时需要拷贝(赋值,克隆)
浅拷贝
//如何克隆//循环:把属性添加到person2里//声明一个空对象不会指向同一个存储空间//浅拷贝var person2 = {};for (var key in person1) {//键值赋值//person1['name'] -> person1.name//循环的key刚好也是对象的键名//console.log(key);//console.log(person1[key]);person2[key] = person1[key];}person2.name = 'lisi';console.log(person1);console.log(person2);
Object.prototype.num = 1;var person1 = {name: 'zhangsan',age: 18,sex: 'male',height: 180,weight: 140,son: {first: 'Jenney',second: 'Lucy',third: 'John'}}var person2 = {};for (var key in person1) {person2[key] = person1[key];}person2.name = 'lisi';person2.son.forth = 'Ben';console.log(person1);console.log(person2);/*** console.log(person1);* {name: "zhangsan", age: 18, sex: "male", height: 180, weight: 140, …}age: 18height: 180name: "zhangsan"sex: "male"son: {first: "Jenney", second: "Lucy", third: "John", forth: "Ben"}weight: 140__proto__: Object*//*** {name: "lisi", age: 18, sex: "male", height: 180, weight: 140, …}age: 18height: 180name: "lisi"num: 1sex: "male"son: {first: "Jenney", second: "Lucy", third: "John", forth: "Ben"}weight: 140__proto__: Object*/
以上说明浅拷贝只遍历第一层结构的属性
写一个浅拷贝的函数
function clone(origin, target) {//万一用户不传target参数,自己默认创建空对象var tar = target || {};for (var key in origin) {//排除原型上的属性if (origin.hasOwnProperty(key)) {tar[key] = origin[key];}}return tar;}var person2 = clone(person1);
深拷贝
如何深拷贝?
//循环对象之前需要检测对象里面的属性值是否是引用值//当发现有引用值的时候需要遍历//不仅判断键值对是否含有引用值,还得判断是对象还是数组//利用递归克隆函数进行再次循环function deepClone(origin, target) {//万一用户不传target参数,自己默认创建空对象var target = target || {},toStr = Object.prototype.toString,arrType = '[object Array]';for (var key in origin) {//排除原型上的属性if (origin.hasOwnProperty(key)) {//判断是否为引用值 同时排除nullif (typeof (origin[key]) === 'object' && origin[key] !== null) {//判断引用值是否为数组类型if (toStr.call(origin[key]) === arrType) {//创建空数组target[key] = [];} else {//引用值是对象//创建空对象target[key] = {};}//递归,再次遍历deepClone(origin[key], target[key]);} else {//这里是递归的出口//遍历第一层 浅拷贝target[key] = origin[key];}}}return target;}var person2 = deepClone(person1);
数组
数组字面量
var arr = [];
内置构造函数(不推荐)
var arr = new Array(); //[]
声明数组(不使用)
var arr = Array(); //[]
//数组arr = [1, 2, 3, 4, 5];console.log(arr.length); //5arr.push(9);console.log(arr); // [1, 2, 3, 4, 5, 9]
数组的原型Array prototype
所有数组都继承于Array.prototype,且里面所有的方法都可以继承和使用
console.log(Array.prototype);
数组是什么?
var obj1 = {};var obj2 = new Object();//通过系统内置的Object构造函数声明对象var obj3 = Object();console.log(obj1.__proto_); //Object prototypeconsole.log(obj1.__proto_); //Object prototypeconsole.log(obj1.__proto_); //Object prototypevar arr = [1, 2, 3, 4, 5];//用对象模仿数组//index标记数组内部的元素即数组元素的下标(索引值)var obj = {0: 1,1: 2,2: 3,3: 4,4: 5}//访问机制一样//obj1.name -> obj1['name']console.log(arr[2]); //3console.log(obj[2]); //3
实际上数组在JavaScript底层机制,就是继承对象而来,数组就是对象的另一种形式
数组截取
//截取机制把最后的那位截取掉var arr2 = [, 1, 3, 5, 7, ];console.log(arr2); //[empty, 1, 3, 5, 7]console.log(arr2.length); //5
稀松数组
var arr = [, , ];console.log(arr); //[empty × 2]console.log(arr.length); //2//稀松数组var arr3 = [, 1, 3, , , 5, 7, ];console.log(arr3); //[empty, 1, 3, empty × 2, 5, 7]
数组new Array传值
var arr4 = new Array(, 1, 3, 4, 5, ); //报错语法错误var arr5 = new Array(1, 3, , , 4, 5); //报错语法错误var arr6 = new Array(1, 2, 3, 4, 5); //报错语法错误console.log(arr6); //[1, 2, 3, 4, 5]var arr1 = new Array(5);//[,,,,,] 传数字设置数组长度var arr1 = new Array(5.2); //报错非法的数组长度var arr1 = new Array('a'); //["a"]console.log(arr1); //[empty × 5]console.log(arr1.length); //5
var arr = [1, 2, 3, 4, 5];// var arr = [1, 2, 3, 4, 5, empty];console.log(arr[5]); //undefined//为什么打印undefinedvar obj = {0: 1,1: 2,2: 3,3: 4,4: 5}console.log(obj[5]); //undefined
写入/更改
//arr写入var arr1 = [];arr1[3] = 'a';console.log(arr1); //[empty × 3, "a"]//arr更改元素arr1[2] = 'b';console.log(arr1); //[empty × 2, "b", "a"]
类数组
特点:
- 具有
length属性 - 数组形式下标对应的值
arguments
// 类数组argumentsfunction test() {arguments.push(7);console.log(arguments); //报错}test(1, 2, 3, 4, 5);// 说明arguments没有继承Arr.prototype// 其实类数组是一个像数组的对象
HTML标签列表
var oDiv = document.getElementsByTagName('div');console.log(oDiv); //[div, div, div]console.log(oDiv.push(7)); //报错
重写类数组
//试写一个类数组//类数组原理var obj = {'0': 1,'1': 2,'2': 3,'3': 4,'4': 5,'length': 5,//此属性可以把{}转为[]//继承splice方法'splice': Array.prototype.splice}//把数组原型上的push方法挂载到对象原型上Object.prototype.push = Array.prototype.push;//重写push方法Array.prototype.push = function (elem) {//属性名=属性值//obj[obj.length] = elem;//obj[5] = elem;this[this.length] = elem;this.length++;}console.log(obj);//{0: 1, 1: 2, 2: 3, 3: 4, 4: 5, length: 5}//[1, 2, 3, 4, 5, splice: ƒ]
//alibabavar obj = {'2': 3,'3': 4,'length': 2,'splice': Array.prototype.slice,'push': Array.prototype.push}console.log(obj.push(1)); //3console.log(obj.push(2)); //4console.log(obj);/*** Object(4) [empty × 2, 1, 2, splice: ƒ, push: ƒ]2: 13: 2length: 4push: ƒ push()splice: ƒ slice()__proto__: Object*///分析:obj[2] = 1;//obj[length] = push(1) -> length++ -> obj[2] = 1 -> 2: 1obj[3] = 2;//obj[length] = push(2) -> length++ -> obj[3] = 2 -> 3: 2
var person = {//数组特性'0': '张晓一','1': '张小三','2': '张小三',//对象特性'name': '张三','age': 32,'weight': 140,'height': 180,'length': 3}Object.prototype.push = Array.prototype.push;Object.prototype.splice = Array.prototype.splice;console.log(person[1]); //张小三console.log(person.weight); //140console.log(person.length); //3console.log(person);//Object(3) ["张晓一", "张小三", "张小三", name: "张三", age: 32, weight: 140, height: 180]//对象的方式遍历for (var key in person) {//排除原型上的方法 push / spliceif (person.hasOwnProperty(key)) {console.log(person[key]);//张晓一 张小二 张三 32 140 180 3}}
类数组转为数组
利用Array.prototype.slice.call(arguments))方法
//类数组转数组//类数组没有slice(),push()方法function test() {console.log(arguments);console.log( Array.prototype.slice.call(arguments));}test(1, 2, 3, 4);/*** console.log(arguments):* Arguments(4) [1, 2, 3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ]0: 11: 22: 33: 4callee: ƒ test()length: 4Symbol(Symbol.iterator): ƒ values()__proto__: Object*//*** (4) [1, 2, 3, 4]0: 11: 22: 33: 4length: 4__proto__: Array(0)*/
