再学JavaScript (三)

155 阅读11分钟

计算精度

// 0.14 * 100 = 14.000000000000002;
// javascript 有计算精度不准的情况,应该避免小数运算

数学函数Math

Math.ceil(123.234);   // 124 向上取整,即使是 0.1 同样会进位
Math.floor(123.999); // 123 向下取整 
Math.random();        // 随机数 0-1的开区间数(>0 * <1)

for(var i = 0; i < 10; i++) {
// var num  = Math.random().toFixed(2) * 100; // 同样会存在 精度不准 14.000000000000002 
	var num = Math.ceil(Math.random() * 100);
	console.log(num);
}
// 可正常计算的范围 小数点前 16 位,后 16 位

不可配置的属性

一旦经历了var操作所得出的属性,window,这种属性叫做不可配置的属性, 不可配置的属性 delete 不掉

22. call & apply

这两个方法可以改变this指向

区别:

  1. call 需要把实参按照形参的个数传递进去
  2. apply 需要数组的形式,把实参传递进去(arguments类数组)
示例

函数执行 === 函数.call()

// 利用其它函数实现自己的功能
function test() {
  
}
test(); // test() === test.call();

利用其它构造函数对自身实现属性的创建

function Test(name, age) {
  this.name = name;
  this.age = age;
}
var obj = {};
Test.call(obj, 'howie', 18);
console.log(obj); // { name: 'howie', age: 18}

如果一个构造函数完全涵盖了自己的需求,可以使用call||apply来对自己进行实现,可以减少编写新的代码。

function Person(name, age, sex ) {
  this.name = name;
  this.age = age;
  this.sex = sex;

}
function Student(name, age, sex, tel, grade) {
  // Student 完全涵盖了 Person 的需求,可以利用call/apply 来利用 Person 实现自己的需求
  // new Student 之后,在当前函数中创建this对象,传入call中
  Person.call(this, name , age, sex);
  this.tel = tel;
  this.grade = grade;
}

var student = new Student('howie', 18, 'male', 152, 1);

应用
// 利用其它构造函数实现Car构造函数
function Wheel(wheelSize, style) {
  this.style = style;
  this.wheelSize = wheelSize;
}
function Sit(c, sitColor) {
  this.c = c;
  this.sitColor = sitColor;
}
function Model(height, width, len) {
  this.height = height;
  this.width = width;
  this.len = len;
}
function Car(wheelSize, style, c, sitColor, height, width, len) {
  Wheel.call(this, wheelSize, style);
  Sit.call(this, c, sitColor);
  Model.call(this, height, width, len);
}
var car = new Car(100, '花里胡哨', '真皮', '棕色', 1000, 1200, 1400);

23. 继承模式

继承发展史

  1. 传统模式 》 原型链 》 借用构造函数(call/apply) 》 共享原型 》 圣杯模式
  2. 缺点:
    1. 传统模式: 过多的继承了没有的属性。
    2. 构造函数:不能继承借用构造函数的原型,每次构造函数都要多走一个函数。
    3. 共享原型:不能随便改动自己的原型。
    4. 圣杯模式:是目前在ES5及以下中比较完美的方式。
示例

传统模式

// 过多继承了不用的属性
Grand.prototype.lastName = 'zs';
function Grand() {

}
var grand = new Grand();

Father.prototype = grand;
function Father() {
    this.name = 'father';
}

Son.prototype = new father();
function Son() {

}
var son = new Son();

借用构造函数

// 不能借用构造函数的原型
//  每次构造函数都要多走一个函数
function Person(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}
function Student(name, age, sex, grade) {
    Person.call(this, name, age, sex);
    this.grade = grade;
}
var student = new Student('zs', 22, 'male', '七年级一班');

共有原型

// 缺点 继承者(Son、Target)没有自己的原型,不能对自己的原型进行修改和添加

// 1
Father.prototype.lastName = 'zs'
function Father() {

}
Son.prototype = Father.prototype;
function Son() {

}
var son = new Son();


// 2
Father.prototype.lastName = 'zs'
function Father() {
}
function Son() {
}
function inherit(Target, Origin) {
    Target.prototype = Origin.prototype;
}
inherit(Son, Father);
var son = new Son();

圣杯模式

/*  
	function F(){};
	F.prototype = Father.prototype;
	Son.prototype = new F();
*/
Father.prototype.lastName = 'zs'
function Father() {
}
function Son() {
}
function inherit(Target, Origin) {
    function F() {};
    F.prototype = Origin.prototype;
    Target.prototype = new F();
    Target.prototype.Uber = Origin.prototype; // 标明它的超类,继承自谁
}
inherit(Son, Father);
Son.prototype.name = 'son';
var son = new Son();

// son.__proto__ --> new F().__proto__ --> father.prototype

圣杯进阶版(雅虎YUI)

// 圣杯进阶版本(雅虎YUI)
var inherit = (function() {
  var F = function() {};  // 利用闭包私有化函数表达式
  return function(Target, Origin) {
    F.prototype = Origin.prototype;
    Target.prototype = new F();
    Target.prototype.constuctor = Target;
    Target.prototype.Uber = Origin.prototype;
  }
}());

24. 拓展 - 模拟JQuery方法连续调用

示例
var $ = {
  smoke : function() {
    console.log('smokeing……');
    return this;
  },
  drink : function() {
    console.log('drinking……');
    return this;

  },
  perm : function() {
    console.log('perming……');
    return this;
  }
}
$.smoke().drink().perm();

25. 属性拼接

对象除了obj.prop调用属性的方式外,还有一个方式

obj.name === obj['name']obj.name 内部也是会转成这个方式来执行。["必须是字符串"]


26. 对象枚举

如果去枚举(遍历 enumeration)一个对象

for in

// 遍历数组
var  arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
for(var i = 0; i < arr.length; i ++) {
    console.log(arr[i]);
}

// 遍历对象  for in 遍历对象的专有方式,通过对象个数来控制循环圈数
var obj = {
    name :'zs',
    age : 123,
    sex : 'male',
    height : '180',
    weight : 75
}

// 循环对象,prop(任意名称)变量存储了当前循环对象的属性(prop 是字符串类型)
for(var prop in obj) 
/**
 * console.log(obj.prop);
 * 这是会打印 5 个undefined,因为这里把 prop 当做属性来调用,而obj中并没有加这个属性 ---> obj['prop'];
 */
  console.log(obj[prop]); // prop 是一个字符串类型的变量
}

hanOwnproperty

// hasOwnProperty 过滤原型上的属性和方法
var obj = {
    name :'zs',
    age : 123,
    sex : 'male',
    height : '180',
    weight : 75,
    __proto__ : {   // 如果有原型属性或者方法,单纯使用 for in 也会把原型中的属性和方法遍历出来
        lastName : 'ls',
        __proto__ : object.prototype, // for in 只会延展到自定义原型,系统定义不会打印
    }
}
for(var prop in obj) {
  // *hasOwnProperty() 判断当前属性是否是本身而非原型的属性,如果是本身则返回 true,反之 false
  if(obj.hasOwnProperty(prop)) { 
      console.log(obj[prop]);
  }
}

in

// 这个属性是否可以访问,判断属性是否是某个对象上的(不区分原型)
var obj = {
  name: 'howie',
  age : 18, // 不要问,问就是18岁
}
console.log('hobby' in obj); // 是否可以通过obj调用到该属性,如果可以返回 true 反之 false

instanceof

/*
  公式: A instanceof B  A
  官方描述 : 对象 A 是不是 B 对象构造出来的,是返回 true,反之返回 false
*/
function Person(name) {
    this.name = name;
}
var person = new Person('zs');
console.log(person instanceof Person); // 为 true
console.log([] instanceof Array); // true
console.log([] instanceof Object); // 依然为 true
// 官方描述不充足 
// * A 对象的原型链上有没有 B 的原型

27. 拓展 - 类型判断&克隆

引用值类型判断

var arr = [] || {}; 

/* 
如何判断一个变量是数组还是对象

1. 使用 变量.constructor 返回它的构造器是什么
2. 变量 instanceof 对象
*3. toString() 
每个函数都有原型 而原型最终都是继承自 Object.prototype,
我们可以利用 Object.prototype.toString() 方法,
改变方法内的 this 指向 Object.prototype.toString.call({}/[]);来判断属于哪种类型
*/

克隆

var obj = {
  name : 'Howie',
  age : 18,
  say : function() {
    console.log('i am Howie');
  },
  hobby: ['吃', '喝', '玩', '乐'],
}
var copy = {}
// 克隆
function clone (target, origin) {
  var target = target || {};
  for(var prop in obj) {
    copy[prop] = obj[prop];
  }
}
// 深度克隆
var obj = {
    name : 'howie',
    age : 18,
    hobby: ['吃', '喝', '玩','乐']
  }
function deepClone(origin, target) {
  var target = target || {};  // 容错
  var objectTypes = {
    object : '[object Object]',
    array : '[object Array]'
  },
  verifyType = Object.prototype.toString;
  for(var prop in origin) {
    if(origin.hasOwnProperty(prop)) { // 把原型中的属性和方法过滤掉
      if(origin[prop] !== null && typeof(origin[prop]) == 'object') {
        target[prop] = verifyType.call(origin[prop]) == objectTypes.object? {} : [];
        deepClone(origin[prop], target[prop]);
      } else {
        target[prop] = origin[prop];
      }
    }
  }
  return target;
}
var copy = deepClone(obj);
console.log(copy);
copy.hobby[2] = '学习';

28. this

函数编译过程中的 this 指向 window

全局作用域里的 this 指向 window

call / apply 可以改变函数运行时的 this 指向

obj.func() func()里面的 this 指向 obj,通俗来说就是谁调用此方法,方法内部的this就指向谁

示例
function test(c) {
    var a = 123;
    function b() {}
}
test(1) // 这种称为空执行 方法当中的 this 指向window

/* 预编译生成执行期上下文
  AO{
    arguments : [1],   其实里边还有 arguments 和 this 属性,默认 this 指向 window
    this : window,
    
    c : 1,
    a : undefined,
    b : fu()
  }
*/

// 函数经过 new 之后,会隐式的创建出 this 对象,并更改它的指向 
function test(c) {
    // var this = {} --> Object.create(test.prototype); 
    var a = 123;
    function b() {}
}
test(1)
new test();

29. callee & caller

arguments.callee 表示函数自身的引用

function test() {
    console.log(arguments.callee);
}
test();

// 使用场景,例如需要使用立即执行函数做一些初始化操作,求 n 的阶乘
var result = (function(n){
    if(n == 1) {
        return 1;
    }
    // 立即执行函数是没有函数名称的,所以在使用递归时需要使用 arguments.callee 来代替函数本身
    return n * arguments.callee(n - 1);  
}(100));
console.log(result)

function.caller 函数被调用时的函数环境

// 这不是 arguments的属性,其本身指函数在哪个执行环境下被调用,经常和 callee 混考
function test() {
    demo();
}
function demo() {
    console.log(demo.caller); // 返回 test函数引用
}
test();

30. 数组

基本使用

1. 数组字面量

var arr = [1, 2, 3];  // 数组字面量

2. 系统构造函数

var arr = new Array(1, 2, 3); // 系统构造函数

3. 区别

var arr = [10];  // 表示一个值为 10
var arr = new Array(10); // 表示数组的长度为 10

4. 扩展

var arr = [1, 2, , , 5, 6]; // 稀松数组

读写

arr[num] // 溢出读结果均为undefined
arr[num] = xxx; // 可以溢出写

数组的常用方法

改变原数组

  1. push()向数组末尾添加数据,并返回新数组(不限个数)
  2. pop()剪切数组末尾值,并返回剪切值
  3. unshift()向数组顶部添加数据,并返回新数组(不限个数)
  4. shift()剪切数据头部数据,并返回剪切值
  5. reverse()逆转数组
  6. splice()截取,填充
  7. sort()数组排序,默认是升序
// push、pop、shift、unshift、sort、reverse、splice

var arr = [1, 2, 3, 4, 5];

arr.push(6); // 向数组末尾添加数据,并返回新数组(不限个数)

arr.pop(); // 剪切数组末尾值,并返回剪切值()

arr.unshift(0); // 向数组顶部添加数据,并返回新数组(不限个数)

arr.shift(); // 剪切数据头部数据,并返回剪切值

arr.reverse(); // 逆转数组

arr.splice('开始位置','截取长度','在切口处添加的新数据') // 截取,填充
// arr.splice(1, 1); 从第一位开始,截取一位 结果为:[1, 3, 4, 5];

arr.sort(); // 数组排序,默认是升序

// sort() 它是按照ascii码来比较,和字符串相同,可以给方法写一个回调函数
/*
	书写回调的规则:
		1.必须有两个参数
		2.为负数时前面的数在前
		3.为0时不动位置
		4.为正数时前面的数在后
*/ 

arr.soft(function(a, b) {  // 升序排序
    if(a < b) {
        return 1;
    }else if(a > b) {
        return -1;
    }else {
        return 0;
    }
});
// 简化函数
arr.sort(function(a, b) {
    return a - b;
})

不改变原数组

  1. concat() 将两个数组连接,并返回新数组
  2. slice()截取 从数组的该位开始截取 截取到数组的该位
  3. json()用什么分隔符将数组拆分成字符串,规定传值要使用字符串类型的,如果不写参数默认以“,”逗号分隔
  4. split(字符串方法)按什么分隔符将字符串转换成数组
// concat、join、split、toString、slice
var arr1 = [1,2,3,4];
var arr2 = [5,6,7,8];

var arr3 = arr1.concat(arr2); // 将两个数组连接,并返回新数组

arr1.slice(从数组的该位开始截取,截取到数组的该位); // 截取 两个参数
arr1.slice(从数组的该位开始截取); // 一个参数会截取到最后
arr1.slice(); // 截取整个数组

arr1.join('-'); // 用什么分隔符将数组拆分成字符串,规定传值要使用字符串类型的,如果不写参数默认以“,”逗号分隔

// 字符串的方法
arr1.split(','); // 按什么分隔符将字符串转换成数组
 
// 以上都是不可改变原数组的,看结果需要查看方法返回值

模拟 push方法

var arr = [1, 2, 3];
Array.prototype.push = function() {  // 直接覆盖系统方法
  console.log('push被我重写了');
  for(var i = 0; i < arguments.length; i ++) {
    this[this.length] = arguments[i];
  }
  return this.length;
}
arr.push(4, 5, 6, 7, 8);

31. 类数组

常见的类数组有函数中的 arguments 和 使用 document.getElementsBy...

类数组的书写规范

  1. 类数组属性名必须是索引值(数字)。
  2. 必须加上 langth 属性。
  3. 最好加上 push 方法。
示例
var obj = {
  "0" : "a",
  "1" : "b",
  "2" : "c",
  "length" : 3,
  "push" : Array.prototype.push"splice" : Array.prototype.splice // 这个属性可以把对象的{}(花括号)该称[](方括号)
}
/*
	1.可以利用属性名模拟数组特征
	2.可以动态的增长length属性
	3.如果强行让类数组调用push方法,则会根据 langth 属性值的位置进行属性的扩展
*/

类数组的好处就是,可以对象和数组的特性拼到一起,arguments实参列表是一个类数组。


32. try-catch

try-catch可以帮助我们在程序中捕获异常

示例
// 在 try 里面发生的错误,不会执行错误后的 try 里面的代码
console.log('a'); // 正常执行
try{  // 尝试执行
    console.log('b');		// 正常执行
    console.log( c );		// 报错,但不抛出错误
    console.log('d');		// 不执行,因为以上有代码报错
}catch(e) {  // 错误捕捉,如果try{}中不存在错误,将不会执行
    // e错误对象 === error === error.message && error.name
    console.log(e.name +' '+ e.message);   
    // 错误名称(类型)e.name: ReferenceError  错误信息 e.message:c is not defined 
}
console.log('e');  // 正常执行

Error.name 的报错信息

  1. EvalError:eval() 的使用与定义不一致。
  2. RangeError: 数值越界。
  3. ReferenceError: 非法或不能识别的引用数值。例如:未经声明就使用的变量。
  4. SyntaxError: 发生语法解析错误,例如:使用了中文字符进行编码。
  5. TypeError: 操作数据类型错误,例如:在 ES5 严格模式下使用 arguments.callee
  6. URLError: URL处理函数使用不当

32. Error

  1. 语法错误。
  2. 逻辑错误。
  3. 一个代码块发生错误,不会影响其他代码块的执行。

33. ES5 严格模式

目前发布的javascript 版本有 es3.0/es5.0/es6.0/es7.0ing...

现在的浏览器依然是基于 es3.0 + es5.0的新增方法使用的,两者产生冲突的使用的是es3.0

如果使用es5.0的严格模式,则强制浏览器在冲突部分使用es5.0的方法

需要写在页面逻辑(全局严格模式)或者函数逻辑(局部严格模式)的最顶端

应用

"use strict" 启用 ES5 的全新规范,不在兼容 ES3 的一些方法。

严格模式有两种使用方式, 一种是全局严格模式,另一种是局部严格模式。

<!-- 全局严格模式 -->
<script>
	"use strict"
  function fn() {
    console.log('严格模式');
    console.log(arguments.callee); // 严格模式中对 callee 不再支持
  }
  fn(1);

</script>

<!-- 局部严格模式 -->
<script>

  function fn() {
    "use sttict"
    console.log(arguments.callee);
  }
  fn(1, 2 ,3)
</script>

启动方式是一行字符串,因为不会对不兼容的浏览器产生影响,提升容错。

严格模式下不支持的方法和书写规则

  1. with: with(){} 方法会修改作用域链,不推荐使用,过于消耗性能。
  2. arguemnts.callee:
  3. fn.caller
  4. 变量赋值前必须声明。
  5. 局部this必须被赋值(Person.call(null / undefined) 赋值是什么就是什么),预编译时 this不再指向 window
  6. 拒绝重复属性和参数

[注意^]: 启动 ES5 严格模式,”use strict"; 必须写在逻辑的最顶端。