js高级中的一些总结
)
一.Function构造函数创建对象
所有的函数对象都是由Function构造函数创建出来的
1.创建一个无内容的函数
var fun1=new Function();
fun1();//这个函数没有实现任何功能,无意义
2.创建一个又函数体的对象
var fun2=new
Function("console.log(‘hello h5’));
fun2();//hello h5
3.创建一个带有参数体的函数对象
// 将Function中的最后一个参数作为函数体,前面的都是函数的形参。
var fun3 = new Function(“a”, “b”, “return a + b;”);
console.log(fun3(1, 2));//3
二.argunments参数详解
定义:arguments是一个对应于传递给函数的参数的类数组对象。此对象包含传递给函数的每个参数。
1.实参大于形参时:会被忽略
2形参大于实参时:没被赋值的形参回返会undefined
arguments解决形参少于实参的问题
function add(){
console.log(arguments);
}
add(1,2,3,4,5)
注意:arguments不是一个数组,而是伪数组,除了length属性和索引元素之外没有任何Array属性。
三
es5类的实现–class
class的基本结构
定义及用法:calss关键字定义类,创建构造函数,类名首字母大写
语法结构:
class 类名{
constructor(参数1,参数2){
//构造函数体,添加实现对象的成员
}
方法名 (){
添加原型对象
}
static 方法名{
// 添加静态成员,只能用类调用
}
// 添加原型对象成员
}
在ES6中,使用class关键字定义同样的类。
class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
doWork(){
console.log(“ES6中在原型对象上添加方法”);
}
}
var p = new Person(“Neld”,10);
console.log§;
ES6中使用class定义类只是一种语法糖(语法糖能够增加程序的可读性,从而减少程序代码出错的机会的一种语法),底层最终还是转换成ES5中使用的function类定义类,以及其中的实例成员和原型成员。
class使用的细节:
- constructor方法是创建对象的构造方法,通常在这里为实例对象定义属性(方法也是可以的),new 之后会自动调用。
2. constructor方法外面的方法是对象的原型方法。
3. 在之前外面还为构造方法添加过成员(静态成员),前面要加static。
class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
static doWork(){
console.log(“ES6中在原型对象上添加方法”);
}
}
Person.doWork()
class的继承结构
语法结构:
class子类 extends 父类{
construtor(参数1,参数2);{
调用父类构造函数,将数据封装到对应属性中
super(参数1,参数2);
}
}
class实现继承的细节:
- Animal中定义了动物都应该有的属性和方法
- 使用extends关键字实现Person类继承Animal类的功能,此时他们两就属于继承关系了。
- 在Person的构造方法中,使用super关键字调用父类中的构造方法。
四、异常捕获(了解)
JS引擎执行JS代码时,会发生各种错误,比如:
-
语法相关问题(程序报错)
-
业务逻辑相关问题(即使出错了,也会继续执行后面的代码)
try{
// 可能出错的代码
}catch(e){
// 处理try代码块中抛出的异常
// e 错误信息
throw //抛出自定义异常
}finally{
// 无论什么情况都会执行代码块
}:
简单示例:
try {
console.log(a);
var b = 1;
console.log(b);
} catch (e) {
console.log("错误信息:", e);
console.log("对不起,我们正在努力的殴打程序员");
} finally {
//无论什么情况都会执行
console.log("最终的处理");
}
需求:判断函数参数是不是数字,不是则抛出异常。
function fun(num) {
if (typeof num !== "number") {
throw "数据类型错误";
}
console.log(num);
}
try {
fun("1");
} catch (e) {
if(e==="数据类型错误"){
console.log('本程序猿知道了,马上就改')
};
}
五、作用域安全的构造函数(了解)
5.1-构造函数存在的作用域安全问题
我们之前学过,构造函数除了和new创建对象之外,还可以作为普通函数直接调用。
比如:
function Person(name, age) {
this.name = name;
this.age = age;
}
console.log(Person("zs", 10));//undefined
console.log(new Person("ls", 12));//初始化了name和age的Person对象
直接调用该构造函数,此时会造成什么问题呢?
-
得不到想要的对象,这样做毫无意义。
-
会存在作用域安全的问题,此时this指向window,这样并不安全,有可能改了全局变量,比如:
var name = “今天天气不错”;
function Person(name, age) {
this.name = name;
this.age = age;
}
Person(“zs”, 10);
console.log(name);//zs
既然存在这样的问题,我们就得解决,那么思路应该是怎样的呢?
5.2-解决构造函数作用域安全问题
方法一:判断 this == window
首先,造成上面问题的根本原因是程序员在使用的过程中可能会忘记new关键字,而导致作用域不安全的问题
所以,而当没有使用new关键字的时候,构造函数中的this关键字是指向window的
反过来,如果构造函数中的this指向window,说明没有使用new关键字,此时就有了下面的代码:
function Person(name, age) {
if(this == window){
throw "调用构造器需要使用new关键字";
}else{
this.name = name;
this.age = age;
}
}
但是在ES6中,这种方式存在一定的问题,此时的this不一定是指向window,原因我们后面再说。此时我们换种思路来解决。
方法二:判断构造器在不在this的原型链上
如果使用new调用该构造函数,那么this指向的是什么呢?
是当前构造函数创建的对象,所以根据类型判断也是可以的。
function Person(name, age) {
if(!(this instanceof Person)){
throw "调用构造器需要使用new关键字";
}else{
this.name = name;
this.age = age;
}
}
上面这种方式是完全OK的,下面我们再给出一种方式,大家可以了解一下。
方式三:使用ES6新属性target
在ES6中,为new引入了一个target属性,如果没有使用new调用构造函数,那么在该构造函数中new.target为undefined,反之为当前的构造函数。
function Person(name, age) {
console.log(new.target);
if(!new.target){
throw "调用构造器需要使用new关键字";
}else{
this.name = name;
this.age = age;
}
}
六、递归
6.1-递归的基本结构(掌握)
定义:函数中用调用函数自己的结构称作递归
function f1() {
console.log("从前有座山,山里有个庙,庙里有个老和尚给小和尚讲故事:");
f1();
};
f1();//浏览器崩溃,因为没有结束条件——死循环
递归两个要素
1.递归的边界——找到出口,在什么情况下跳出递归
2.递归的逻辑——找到入口,什么情况下重复调用自己,调用自己做什么
var i=0;
function f1() {
i++;
if (i<5){// <5的时候就是入口
f1();
} // =5的时候就是出口
console.log("从前有座山,山里有个庙,庙里有个老和尚给小和尚讲故事:");
};
f1();
需求:封装一个方法,计算正整数num的阶乘(递归阶乘)
什么是阶乘(factorial):所有小于及等于该数的正整数的积
// 递归阶乘
function factorial(num) {
if(num <= 1) { return 1 };
return num * factorial(num - 1);
}
6.2-拷贝(掌握)
6.2.1-浅拷贝实现
需求:就是将p1对象中的属性或者是方法拷贝到p2对象中。
我们首先想到是的for…in循环,将p1的属性拷贝一份到p2中:
var p1 = {
name:"zs",
age:10,
favs:["H5","Java","C"],
wife:{
name:"lily",
age:8
}
}
var p2 = {};
for(var key in p1){
p2[key] = p1[key];
}
console.log(p2);
缺点:对象内属性是引用数据类型的话,拷贝过来的就是引用地址,并没有实现真正的拷贝,依然同一份数据。
6.2.2-深拷贝实现
所以要实现深拷贝,当我们发现属性对应的值是一个对象或数组的时候,应该将该对象或数组再拷贝一份,然后赋值给当前属性。
思路:
-
浅拷贝拷贝基本数据类型
-
判断是否引用数据类型
-
递归调用,完成所有层次拷贝
-
判断value是数组还是对象
var p={
name:‘小明’,
age:13,
favs:[‘H5’,‘Java’,‘C’],
wife:{
name:‘小丽’,
age:15,
favs:[‘H5’,‘Java’,‘C’]
}
};
var p2={};
function deepCopy(source,target) {
for (var Key in source) {
//只拷贝当前对象的属性
if(source.hasOwnProperty(Key)) {
//选择引用数据类型
if (typeof source[Key] == ‘object’) {
//判断value是数组还是对象
target[Key] = isArray(source[Key]) ? [] : {};
//递归,深层次拷贝
deepCopy(source[Key], target[Key])
//arguments.callee(source[Key],target[Key])
}else{
target[Key] = source[Key]
}
}
}
}
function isArray(arr) {
if(Array.isArray){
return Array.isArray(arr)
}else{
return Object.prototype.toString().call(arr)==’[object Array]’
}
}
deepCopy(p,p2)
console.log(p2);
通过上面的深度拷贝得到的p2对象是和p1完全不同的两份数据,此时不再存在数据共享的问题。
6.3-排序(至少掌握一种)
6.3.1冒泡排序法(Bubble Sort)
一种计算机科学领域的较简单的排序算法。
基本思路是:重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来
需求:将数组[9,3,8,5,7]按从小到大的顺序重新排序
var arr = [9,3,8,5,7];
// 将数组中的数两两比对,调换顺序
for(var i = 0;i<arr.length-1;i++){
for(var j = i+1;j<arr.length;j++){
var temp;
if(arr[i]>arr[j]){
temp = arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
}
console.log(arr)
6.3.2-快速排序法(Quick Sort)
快速排序是对冒泡排序的一种改进。
基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
需求:随机获取10到999之间的100个整数,并且要从大到小排序,要求使用快速排序算法。
//获取到min到max-1的total个随机数
function getRandomNum(total,min,max){
let arr=[];
for(let i = 0;i<total;i++){
arr.push(Math.floor(Math.random()*(max-min)+min))
}
return arr
}
//快速排序算法
function arrSort(arr) {
if (arr.length <= 1) { return arr; }
var index = Math.floor(arr.length / 2);
var middle = arr.splice(index, 1)[0];
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i] < middle) {
right.push(arr[i])
} else {
left.push(arr[i])
}
}
return arrSort(left).concat(middle, arrSort(right))
}
console.log(arrSort(getRandomNum(100,10,1000)))
七、函数的调用和this的丢失(理解)
this指向总结
- 普通调用 fun() this指向调用函数的对象—window
- 对象调用 obj.fun() this指向调用函数的对象—obj
- 使用new关键字调用 new Fun() this指向函数内部创建的新对象
- call或者apply调用 this指向call或者apply方法的第一个参数
我们在调用函数的过程中需要时刻关注我们调用方式的不同对this的影响
如下面的案例中就发生了this的丢失问题:
<div id="main"></div>
<script>
console.log(document.getElementById("main"));
var getById = document.getElementById;
console.log(getById("main"));//Uncaught TypeError: Illegal invocation
</script>
为什么会出现这样的情况:
所以,上面的代码中将根据元素id获取元素的方法赋值给了另外一个变量getById,此时的getById就等价于document.getElementById方法,所以,按理说,应该可以使用getById完成获取元素的操作,但是结果却报错了,这是为什么呢?
原因其实很简单:
- 在document的getElementById方法中用到了this,正常使用document调用的时候this是指向document的
- 要完成获取元素的功能,this就必须要指向document
- 当使用getById(“main”)调用函数的时候,函数中this拿到的确是window
- 函数中本来需要的是document拿到的确是window的时候,代码执行报错
如果非得对这段代码做优化,我们应该怎么做:
var getById = function(id){
return document.getElementById(id);
}
console.log(getById("main"));
这段代码大家相信大家都能够看懂,所以我们以后再简化一个函数的使用的时候,一定要注意,不要轻易的将函数的this给搞丢了。
八、对象相关回顾总结(回顾)
8.1-创建对象的N中方式总结
- 字面量
- 内置构造函数
- 简单工厂函数
- 自定义构造函数
- Object.create()
- Object.assign()
8.2-类型检查的四种方式总结
通过前面知识点的学习,我们已经get到了四种类型检查的方式,为了在开发中灵活运用他们,我们需要对这几种方式进行总结。
-
typeof: 主要用来判断基本类型
console.log(typeof “abc”);//“string”
console.log(typeof 123);//“number”
console.log(typeof true);//“boolean”
console.log(typeof null);//“object”
console.log(typeof Function);//“function”
console.log(typeof {name:“Neld”, age: 10});//“object”
对于字符串,数字和布尔类型,返回对应类型的字符串(string, number, boolean),undefined和Function比较特殊,分别是undefined和function,这两个需要单独记忆,其他的(包括null)都是返回object -
constructor: 可以用来判断创建对象的构造器的类型
function Person() {}
function Dog() {}var p = new Person(); var d = new Dog(); console.log(p.constructor == Person);//true console.log(d.constructor == Dog);//true
使用这种方式,我们可以知道对象的具体类型是什么。
-
instanceof: 判断指定构造函数的原型对象是否在当前实例对象的原型链上
function Person() {}
function Dog() {}var p = new Person(); var d = new Dog(); console.log(p instanceof Person);//true console.log(d instanceof Dog);//true console.log(p instanceof Object);//true console.log(d instanceof Object);//true
Person和Object的原型对象分别在p和b对象的原型链上,所以上面的返回值都是true
-
Object.prototype.toString(): 获取数据类型对应的字符串
console.log(Object.prototype.toString.call(“Neld”));//[object String]
console.log(Object.prototype.toString.call(10));//[object Number]
console.log(Object.prototype.toString.call§);//[object Object]
console.log(Object.prototype.toString.call([]));//[object Array]
上面几种方式都能够以自身的方式来判断数据的类型,在开发中,我们根据具体的需求选择即可。