JavaScript学习笔记十三

94 阅读10分钟

thirteen

一:链式调用

1.1 尝试打印对象中的所有方法

需求:现在有一个对象 sched ,需要打印这个对象内的所有方法,只能在一行中把所有的方法全部调用出来,不能一行一行的使用对象名加方法名来调用。

代码范例1:错误的使用方法

var sched = {
  wakeup: function(){console.log('Running');},
  morning: function(){console.log('Going shopping');},
  noon: function(){console.log('Having a rest');},
  afternoon: function(){console.log('Studying');},
  evening: function(){console.log('Walking');},
  night: function(){console.log('Sleeping');}
}
//尝试按这种方式来调用
sched.wakeup().morning().noon().afternoon().evening().night();
//Print Result:只输出 Running ,然后报错,显然这个方式是不可行的。
1.2 尝试把所有方法汇总到一个方法内

这种方法是可行的,但不是我们需要,继续往下看。

代码范例2:验证上述内容

var sched = {
  wakeup: function () {
    console.log('Running');
  },
  morning: function () {
    console.log('Going shopping');
  },
  noon: function () {
    console.log('Having a rest');
  },
  afternoon: function () {
    console.log('Studying');
  },
  evening: function () {
    console.log('Walking');
  },
  night: function () {
    console.log('Sleeping');
  },
  all: function () {
    this.wakeup();
    this.morning();
    this.noon();
    this.afternoon();
    this.evening();
    this.night();
  }
}
sched.all();
1.3 尝试使用 return this 方式

​ 在对象方法中,使用 return 指令返回一个 this ,按照以往学习对象的知识中,可以很快的就想到,this 就是指向对象本身,所以在第一次使用方法

名后,就立马返回了一个对象名,然后在用点语法在加上方法名,又可以成功的执行下一个方法,如此往复,直到最后一个方法,就不需要 return this

代码范例3:验证上述内容

var sched = {
  wakeup: function (){
    console.log('Running');
    //这里的 return,可以理解成,执行 sched 对象中的 wakeup方法后,在返回对象名
    return this;
  },
  morning: function (){
    console.log('Going shopping');
    return this;
  },
  noon: function (){
    console.log('Having a rest');
    return this;
  },
  afternoon: function (){
    console.log('Studying');
    return this;
  },
  evening: function (){
    console.log('Walking');
    return this;
  },
  night: function (){
    console.log('Sleeping');
  }
}
sched.wakeup().morning().noon().afternoon().evening().night();

问:测试是否真的返回对象名?

答:观察 代码范例5 中的结果,可以得知确实没有问题。

代码范例5:验证上述内容

var sched = {
  wakeup: function (){
    console.log('Running');
    return this;
  }
}
console.log(sched.wakeup() === sched);
//Print Result: Running, true

二:对象遍历

2.1 拼接对象属性名

通过 代码范例6 ,可以很清楚的知道:

  • 对象的属性名,在拼接时,一定要用引号包裹起来,由此可以认为,属性名就是字符串;

  • 在拼接时,需要使用 [] 来包裹属性名;

  • 同时,属性名的拼接,可以是表达式,也可以是变量;

Tips1:

​ 在早期的 JavaScript 引擎( ECMAScript 1 之前)中,是没有 . 点语法来访问对象中的属性名.当时,访问对象的唯一方式是使用方括号 [] 的形式。后

来,点语法被引入( ECMAScript 1),并逐渐成为访问对象属性的首选方式,然而,为了兼容早期的 JavaScript 代码,现代 JavaScript 引擎仍然会把点语法

转换成方括号的形式来访问。

Tips2:

方括号 [] 与点语法 . 的使用场景:

  • 方括号在处理动态属性名或属性名为变量时特别有用;
  • 点语法则在访问静态、已知属性时更为简洁明了。

代码范例6:验证上述内容

//演示代码
var myLang = {
  No1: 'HTML',
  No2: 'CSS',
  No3: 'JavaScript',
  myStudyingLang: function(num){
    //常用,也是推荐的方法
    //console.log(this[`No` + num]);
      
    //根据上面常用的方法,衍生出来的 ---> 表达式
    //console.log(this[(`No` + num)]);
      
    //同上 ---> 变量
    // var putTogether =  'No' + num; //隐式转换 num 为字符串
    // console.log(this[putTogether]);
  }
}
myLang.myStudyingLang(1);
//Print Result:HTML

//使用 [] 方括号一样可以访问对象中的属性
var obj = {
    name : 123
}
console.log(obj['name']);
//Print Result:123
2.2 对象枚举

什么是枚举?

​ 在 JavaScript 中,是没有所谓正真意义上的枚举,这个所谓指的是:在一些强类型的编程语言(C、C++、Java)中,枚举是一种内置的数据类型,它包含

了一个固定的、有限的值集合,这些值具有共同的特性。但在 JavaScript 中,没有内置的枚举类型,通常是通过对象或其他结构来模拟枚举的行为。

​ 在 JavaScript 中可以理解成,一组有共同特性的数据的集合。

什么是遍历?在一组信息内,按顺序一个个获取其信息的过程。

在 JavaScript 中,提到枚举,就会提到遍历,可以理解为,有遍历就有枚举,有枚举也就有遍历,它们两个之间属于相辅相成关系。

在JavaScript中,枚举(遍历)的方法

for(var key in object)

  • key:用于存储当前遍历到的属性名。可以自由命名这个变量,但需要注意避免与上下文中的其他变量名发生冲突,以免造成全局变量名污染。
  • object:需要枚举(遍历)的数据名。

思考 car.key 为什么会是 undefined?

  • JavaScript 引擎会先将 car.key 转换为 car['key']
  • 然后将 key 认为是对象中的一个属性名,进行查找;
  • 查找不到,自然就会返回 undefined;
  • 其过程为 car.key ---> car['key'] ---> undefined

代码范例7:验证上述内容

//在 JavaScript 中,数组是一种特殊的对象
//循环数组的过程,也是遍历过程,也就是枚举数组过程
var arr = [1, 2, 3, 4, 5]
//第一种枚举数组方法
for(var i = 0; i < arr.length; i++){
    console.log(arr[i]);
}
//第二种枚举数组方法,但不推荐,一般是 for循环以及 forEach,后面学到在说。
for(var key in arr){
    console.log(arr[key]);
}
//Print Result: 1, 2, 3, 4, 5
---------------------------------------------
//对象枚举
var car = {
    brand: 'Benz',
    color: 'red',
    displacement: '3.0T',
    length: '3',
    width: '2'    
}
//枚举出该对象所有的属性名
for(var key in car){
    console.log(key);
}

//枚举出该对象所有的属性值
for(var key in car){
    console.log(car[key]);
}

//思考 car.key 为什么会是 undefined?
//思考 car['key'] 为什么会是 undefined?
for(var key in car){
    console.log(car.key); //car.key ---> car['key'] --->undefined
    console.log(car['key']); //同上原理
}
2.3 hasOwnProperty 方法的作用

通过观察代码范例8时,发现使用 for(var key in object) 的方式枚举,会把对象自身的原型也一起打印出来。

​ 在 JavaScript 中,hasOwnProperty 方法是用来检查一个对象是否含有特定的自身属性,而不是继承自其原型链的属性。这个方法对于区分对象自有属

性和从原型链继承的属性特别有用,尤其是在使用 for...in 循环遍历对象属性时

object.hasOwnProperty(key) 方法解释:

  • object ---> 对象名称;

  • key ---> 对象内的属性名或方法名;

  • hasOwnProperty 方法返回一个布尔值,true 或 false;

  • 如果值为 true,key 为对象自有属性名或方法名而不是通过原型继承下来的;

  • 如果值为 false,key 为属性名或方法名是通过原型继承下来的;

代码范例8:验证上述内容

function Car(){
   this.brand = 'Benz';
   this.color = 'red';
}
Car.prototype.displacement = '3.0T';

var car = new Car();
//枚举对象
for(var key in car){
  console.log(key + ':' + car[key]);
}
//Print Result:brand:Benz, color:red, displacement:3.0T

//只打印对象自身的属性或方法
function Car(){
   this.brand = 'Benz';
   this.color = 'red';
}
Car.prototype.displacement = '3.0T';

var car = new Car();
//枚举对象
for(var key in car){
  //下面的条件判断语句,可以理解成:
  //1、返回值为true,则代表是当前对象的自有属性名,即进入相应语句内;
  //2、返回值为false,则代表不是当前对象的自有属性名而是从原型上继承的,即什么也不做。
  if(car.hasOwnProperty(key)){
  console.log(key + ':' + car[key]);
  }
}
2.4 in 的用法

​ 通过观察代码范例9时,in 的作用就是为了判断对象中是否包含了相应的属性名或方法名,并且是可以直接向上查找,也就是在对象自身没有找到,就

会去原型上逐个寻找,如果有则返回 true,否则返回 false。

代码范例9:验证上述内容

function Car(){
   this.brand = 'Benz';
   this.color = 'red';
}
Car.prototype.displacement = '3.0T';

var car = new Car();
console.log('displacement' in car); //true

三:零碎知识

3.1 instanceof 运算符

MDN:

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,如果是则为true,反之false。

个人理解:

可以理解为,instanceof 检测实例化对象的原型是否与构造函数中的原型链是否重合,如果是则为true,反之false。

代码范例9:验证上述内容

function Person(){}
var person = new Person();
function Car(){
    this.brand = 'Benz';
    this.color = 'black';
}
var car = new Car();
//按照MDN的解释,可以理解为,instanceof 检测实例化对象的原型是否与构造函数中的原型链是否重合,如果是则为true,反之false。
console.log(car instanceof Car);//true
console.log(person instanceof Car);//false
console.log(person instanceof Object);//true

//常识
console.log([] instanceof Array); //true
console.log([] instanceof Objcet); //true
console.log({} instanceof Object); //true
3.2 数据类型的判断

问:在 JavaScript 中数据类型分为两种,原始类型与引用类型,那么要如何准确直观的判断数据类型?

答:具体方法展开如下:

1、使用 typeof 运算符:

  • 对于原始类型的判断显示非常直观,除了 null 它会返回 object
  • 对于引用类型,它基本都返回 object,除了函数返回 function

2、使用 Object.prototype.toString.call():

  • 这是一种更可靠的类型检查方法,它可以区分不同类型的对象,如数组、、对象、日期等。

3、数组特定的检查:

  • 对于数组,还可以使用 Array.isArray()

代码范例10:验证上述内容

//1、typeof 运算符
console.log(typeof {}); //object
console.log(typeof []); //objcet
console.log(typeof null); //object
console.log(typeof function(){}); //function

//2、Object.prototype.toString.call()
console.log(Object.prototype.toString.call([])); // [object Array]
console.log(Object.prototype.toString.call({})); // [object Object]
console.log(Object.prototype.toString.call(new Date())); // [object Date]
var regex = /abc/;
console.log(Object.prototype.toString.call(regex));//[object RegExp]

//3、Array.isArray()
console.log(Array.isArray([])); //true
3.3 Object.prototype.toString.call() 的认识

问:为什么 Object.prototype.toString.call() 方法能返回数据类型?

答:Object.prototype.toString() 返回为 [object Type],这里的 Type 是对象类型的字符串,它们包括:

  • [Array]、[Function]、[Error]、[Boolean]、[Number]、[String]、[Date]、[RegExp];
  • 在调用 Object.prototype.toString.call() 方法,被 call 重新指向当前传入的参数,此时参数的数据类型就会与 Type 字符串相映射,且返回。

代码范例11:验证上述内容

var a = [];
var b = {};
var c = 'abc';
var d = 123456;
var e = true;
//以下,不管传入什么类型的数据,一律返回的是 [object Object]
console.log(Object.prototype.toString(a)); //[object Object]
console.log(Object.prototype.toString(c)); //[object Object]
console.log(Object.prototype.toString(e)); //[object Object]

//在使用 call 的时候,指向了参数的本身,情况就不一样了
console.log(Object.prototype.toString.call(a)); //[object Array]
console.log(Object.prototype.toString.call(c)); //[object String]
console.log(Object.prototype.toString.call(e)); //[object Boolean]

//常识
//原始类型中的 null 和 undefined ,也可以这个方法来检测
console.log(Object.prototype.toString.call(null));
console.log(Object.prototype.toString.call(undefined));
3.4 this的阶段性认识

1、普通函数的 this 指向:

  • 在普通函数内部使用 this ,只要没有实例这个函数时,this 的指向一定是 window,那么也就可以在外部直接访问;
  • 例如 this.d = 3 ---> window.d = 3 ---> d = 3 ,具体看范例。

2、call/apply改变this指向;

3、构造函数的this指向实例化后的对象

代码范例12:验证上述内容

//普通函数
function test(){
    this.d = 3; 
}
test(); //函数运行
console.log(this.d); //3
console.log(window.d); //3
console.log(d); //3
3.5 callee/caller

callee/caller 不常用,在 ES5 中的严格模式下会抛出 TypeError 错误,在 ES6 中已经弃用,同时 arguments 也是一样,以上知识,需要知道。

callee :是 arguments 对象的一个属性。用于获取当前函数的函数体。这在匿名函数时很有用,注意是整个函数体而不是函数名。

caller :当 a 函数被 b 函数调用时,且在 a 函数内写入 a.caller 语句,然后 b 函数执行时,则返回 b 函数的函数体。作用就是返回调用该函数的函数。

代码范例13:验证上述内容

//callee
function test(){
    console.log(arguments.callee);
}
test();
//Print Result: ƒ test(){console.log(arguments.callee);}

//callee 严格模式下报错
"use strict";
function test(){
    console.log(arguments.callee);
}
test();//Print Result:Uncaught TypeError:......

//callee的应用
//递归累加
function sum(n){
    if(n <= 1){
        return 1;
    }
    return n + sum(n - 1);
}
sum(10); //Print Result:55

//使用立即执行函数递归累加并赋值给一个变量
var sum = (function(n){
    if(n <= 1){
        return 1;
    }
    return n + arguments.callee(n - 1);
})(10);
console.log(sum); //Print Result:55

//caller 
function test1(){
    test2();
}
function test2(){
    console.log(test2.caller);
}
test1();//Print Result: ƒ test1(){test2();}