计算精度
// 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指向区别:
call需要把实参按照形参的个数传递进去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. 继承模式
继承发展史
- 传统模式 》 原型链 》 借用构造函数(call/apply) 》 共享原型 》 圣杯模式
- 缺点:
- 传统模式: 过多的继承了没有的属性。
- 构造函数:不能继承借用构造函数的原型,每次构造函数都要多走一个函数。
- 共享原型:不能随便改动自己的原型。
- 圣杯模式:是目前在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; // 可以溢出写
数组的常用方法
改变原数组
push()向数组末尾添加数据,并返回新数组(不限个数)pop()剪切数组末尾值,并返回剪切值unshift()向数组顶部添加数据,并返回新数组(不限个数)shift()剪切数据头部数据,并返回剪切值reverse()逆转数组splice()截取,填充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;
})
不改变原数组
concat()将两个数组连接,并返回新数组slice()截取 从数组的该位开始截取 截取到数组的该位json()用什么分隔符将数组拆分成字符串,规定传值要使用字符串类型的,如果不写参数默认以“,”逗号分隔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...
类数组的书写规范
- 类数组属性名必须是索引值(数字)。
- 必须加上 langth 属性。
- 最好加上 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 的报错信息
EvalError:eval() 的使用与定义不一致。RangeError: 数值越界。ReferenceError: 非法或不能识别的引用数值。例如:未经声明就使用的变量。SyntaxError: 发生语法解析错误,例如:使用了中文字符进行编码。TypeError: 操作数据类型错误,例如:在 ES5 严格模式下使用 arguments.calleeURLError: URL处理函数使用不当
32. Error
- 语法错误。
- 逻辑错误。
- 一个代码块发生错误,不会影响其他代码块的执行。
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>
启动方式是一行字符串,因为不会对不兼容的浏览器产生影响,提升容错。
严格模式下不支持的方法和书写规则
with: with(){} 方法会修改作用域链,不推荐使用,过于消耗性能。arguemnts.callee:fn.caller- 变量赋值前必须声明。
- 局部
this必须被赋值(Person.call(null / undefined) 赋值是什么就是什么),预编译时this不再指向window - 拒绝重复属性和参数
[注意^]: 启动 ES5 严格模式,”use strict"; 必须写在逻辑的最顶端。