资讯专栏INFORMATION COLUMN

ES5学习(上)

HackerShell / 3221人阅读

摘要:对象是一个值超出有效范围时发生的错误。包括返回原数组包括数组对象函数可以用来判断变量是否为对象数组对象函数构造函数与直接赋值是等价的。只适用于,数组不适用通过可以看出一个值到底是什么类型,其中返回值的第二个值表示该值的构造函数。

这是ES5的入门篇教程的笔记,网址:JavaScript教程,以下内容中黑体表示大标题,还有一些重点;斜体表示对于自身,还需要下功夫学习的内容。这里面有一些自己的见解,所以若是发现问题,欢迎指出~

数据类型
只有var存在变量提升,let不存在变量提升。
js内部都是以64位浮点数的形式存储(64位表示精度),整数也如此,所以1.0和1是相同的一个数,1.0===1.
JavaScript 语言的底层根本没有整数。由于浮点数不是精确的值,所以涉及小数的比较和运算都不是很准确。
NaN === NaN 用于判断是否是NaN。

关于函数
函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域
函数参数不是必须的,JavaScript允许省略参数(与其他语言不同!!!),函数的length属性与实际传入的参数个数无关,只反映函数预期传入的参数个数,如下:

function f(a, b) {
    return a;
}

f(1, 2, 3) // 1
f(1) // 1
f() // undefined
f(, 1) // SyntaxError: Unexpected token ,...
f(undefined, 1) // undefined 省略考前的参数,只有显式传入undefined

f.length // 2

函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递,也就是说,在函数体内修改参数值,不会影响到函数外部;但是,如果是复合类型(数组、对象、其他函数),传递方式是传址传递,将会影响到原始值(如果函数内部修改的内容,不是参数对象的某个属性,而是替换掉整个参数,这是不会影响到原始值)。

var p = 2;
function f(p) {
    p = 3; // 这只是一个拷贝
}
f(p);
p // 2

var obj = { p: 1 };
function f(o) {
    o.p = 2;
}
f(obj);
obj.p // 2

var obj = [1, 2, 3];
function f(o) {
  o = [2, 3, 4]; // 形参o的值指向的是实参obj的地址,重新对o赋值后,o会指向另一个地址,而obj指向的原地址比那个没有发生改变,所以obj不会变化。
}
f(obj);
obj // [1, 2, 3] 

由于JavaScript允许函数有不定数目的参数,可以通过arguments对象读取所有参数,在严格模式下,arguments很多方法都是受到限制的;arguments很像数组,但它是一个对象(arguments是伪数组)。

什么是闭包?就是在函数的内部,再定义一个函数,用来得到函数内部的局部变量。闭包两个最大用处有两个,一个是可以读取函数内部的变量,另一个是让这些变量始终保持在内存中,也就是闭包可以使得它的诞生环境一直存在(这个作用有点不理解,需要去理解系统垃圾回收机制)。闭包还可以封装对象的私有属性和私有方法,如下(跟Java类似):

function Person(name) {
  var _age;
  function setAge(n) {
    _age = n;
  }
  function getAge() {
    return _age;
  }
  return {
    name: name,
    getAge: getAge,
    setAge: setAge
  };
}

var p1 = Person("张三");
p1.setAge(25);
p1.getAge() // 25

注: 外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。

在JavaScript中,圆括号()是一种运算符(以前知道圆括号是执行函数的意思,但它算运算符还真的不知道,所以人丑还是要多读书),跟在函数名之后,表示调用该函数,比如,print()就是表示调用print函数。不能再函数的定义之后加上圆括号,会产生语法错误,因为JavaScript规定function关键字出现在行首,一律解释成语句,而不是表达式,如下:

function(){ /* code */ }();
// SyntaxError: Unexpected token ...

// 以下两种方法都可以马上执行,这叫做“立即调用的函数表达式”,简称IIFE
(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();

关于数组
JavaScript语言规定,对象的键名一律为字符串,所以数组的键名其实也是字符串,之所以可以用数值读取,是因为非字符串的键名会被转为字符串(这个以前还真不知道)。
对象中有两种去读取成员的方法:点结构(object.key)和方括号结构(object[key])。但是,对于数值的键名,不能使用点结构。因为arr.0的写法不合法,多带带的数值不能作为标识符,所以数组成员只能用方括号arr[0]表示(方括号是运算符,可以接受数值)。
清空/删除数组,可以用length属性试试哦;当然如果设置length大于数组个数时,读取新增的位置都会返回undefined。
for..in 用于对象,不推荐用于数组(因为数组也是对象,可以用非数字键加键名,for...in会遍历所有数字键和非数字键,数组遍历肯定遍历的是数字键);for()循环、while循环用于数组。(en,这个我知道)

数组的空位和undifined不一样,但是读取到的却是undefined,length属性不过滤空位,但数组的forEach方法、for...in结构、以及Object.keys方法进行遍历,空位都会被跳过。空位表示数组没有这个元素,所以不会被遍历;undefined表示数组有这个元素,但值为undefined,所以遍历不会跳过。

var a = [, , ,];
a[1] // undefined

这里说的“类似数组的对象”,在其他地方看到过其他资料,叫做“伪数组”。
根本特征:具有length属性,但是length属性不是动态值,不会随着成员的变化而变化。(其他地方也提到过,arguments也是伪数组。)
变为真正的数组,一是用slice,二时用call()。这两个内容看不懂,还需要努力!

运算符
“重载”!!!我只在Java里面听到过函数重载,js里面运算符竟然也有这种叫法,长知识了!什么叫“重载”呢?加法运算符在运行时决定,是执行相加,还是执行连接,也就是说,运算子的不同,导致了不同的语法行为,这种行为成为“重载”。(只有加法运算符才有这种操作,减法、除法和乘法都不会发生重载,因为其他的算术运算符,有一个规则:所有运算子一律转为数值,再进行相应的数学运算。)

指数运算符‘**’,这是第一次遇见,以前都是用Math.pow()来求幂的!!!
发现两者最大的区别在于编译是99**99编译时计算,Math.pow(99, 99)运行时计算,具体说明参考“常量折叠”优化技巧
‘**’指数运算符是右结合,而不是左结合,如下:

2 ** 3 **2
// 512 相当于 2**(3 ** 2)

关于相等和严格相等,这里的解释是最令人通俗易懂的!!!
‘==’比较的是两个值是否相等;‘===’表比较它们是否为“同一个值”。“同一个值”也就是不仅要比较他们的值,还要比较它们的类型。
NaN与任何值都不相等(包括自身),正0等于负0(为什么,想不通)。
复合类型的数据比较,比较的是它们是否指向同一个地址。
null和undefined与自身严格相等,与其他类型的值比较时,结果都为false,它们互相比较时结果为true。

NaN == NaN // false
+0 === -0 // true

{} === {} // false
{} == {} // false

undefined === undefined // true
null === null // true

false == null // false
false == undefined // false

0 == null // false
0 == undefined // false

undefined == null // true

二进制位运算符实际中并不常用,所以并不怎么熟悉,但是我发现了一个有趣的东西,那就是异或运算符^。
平时互换两个变量的值,一般的做法是引入一个临时变量temp,用来作为中间变量,但如果熟悉异或运算符,可以进行如下操作:

let a = 10;
let b = 99;
a ^= b, b ^= a, a ^= b;
a // 99
b // 10

12.9 ^ 0 // 12 异或运算也可以用来取整(“向下取整”,不能严格地说是向下取整)。
-12.9 ^ 0 // -12

数据类型转换
强制转换有三种Number()、String()和Boolean(),以前只会用Number(),不怎么常用后面两种,常用toString()和!!代替前面两个,也没什么区别吧。
Number函数比parseInt函数严格很多,基本上只要有一个字符无法转成数值,整个字符串就会被转为NaN。parseInt逐个解析字符,而Number函数整体转换字符串的类型。
关于Boolean(),除了undefined、null、0、NaN、"",五个值转换结果为false,其他的值全部为true,包括空对象、空数组的转换结果也为true。

parseInt("42 cats") // 42
Number("42 cats") // NaN

Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean("") // false
Boolean({}) // true
Boolean([]) // true

Boolean(false) // false
Boolean(new Boolean(false)) // true 因为这是一个对象

null转为数值时为0,而undefined转为数值时为NaN。

错误机制处理
每次出现报错信息,都看不懂报错的原因是为什么,终于可以让我好好理解一下,因为什么原因会报错了。
Error实例对象是最一般的错误类型,在它的基础上,JavaScript还定义了其他6种错误对象,也就是说,存在Error的6个派生对象。
SyntaxError对象是解析代码时发生的语法错误
ReferenceError对象是引用一个不存在的变量时发生的错误
RangeError对象是一个值超出有效范围时发生的错误。
TypeError对象是变量或参数不是预期类型时发生的错误。
URIError对象是URI相关函数的参数不正确时抛出的错误。
EvalError对象是eval函数没有被正确执行时,抛出的错误,该错误类型已经不再使用了,只是为了保证与以前代码兼容,才继续保留。

编程规范
不使用分号的三种情况
1、for和while循环,注:do…while循环是有分号的;
2、分支语句:if,switch,try
3、函数的声明语句,注:函数表达式仍然要使用分号。

for( ; ; ) {
}

while (true) {
} // 没有分号

do {
    a--;
} while(a > 0); // 有分号 

if (true) {
}

switch () {
}

try {
} catch {
}

function f() {
} // 没有分号
let f = function f() {
}; // 有分号

有一个重大的发现,竟然可以用对象结果代替switch…case结构!!!

function doAction(action) {
    switch (action) {
        case "hack":
            return "hack";
        case "slash":
            return "slash";
        default:
            throw new Error("Invalid action.");
    }
}

// 变身后
function doAction(action) {
    let actions = {
        "hack": function () {
            return "hack";
        },
        "slash": function () {
            return "slash";
        }
    };
    if (typeof actions[action] !== "function") {
        throw new Error("Invalid action.");
    }
    
    return actions[action]();
}

语法专题
真是没想到console.log方法还支持占位符,可以像C语言一样用于输出不同类型的数据。
占位符有以下几种:
%s 字符串
%d 整数
%i 整数
%f 浮点数
%o 对象的连接
%c CSS格式字符串(这个是最令我好奇的)

其他的有关console方法,感觉用处不是很大,其中有个console.count()感觉还是会用到的,count方法用于计数,输出它被调用了多少次。

function greet(user) {
    console.count();
    return "hi " + user;
}

greet("bob")
// default: 1
// "hi bob"

greet("alice")
// default: 2
// "hi alice"

标准库
Object对象的原生方法分成两类:
1)Object本身的方法(又称为“静态方法”):直接定义在Object对象的方法;
2)Object的实例方法:定义在Object原型对象Object.prototype上的方法,它可以被Obejct实例直接使用。

Object.print = function (o) { console.log(o) }; // 这个Object对象本身的方法,print方法直接定义在Object对象上
Object.prototype.print = function () {
    console.log(this);
};

let obj = new Object();
obj.print() // Object  凡是定义在Object.prototype对象上面的属性和方法,将被所有实例对象共享。

Object本身是一个函数,如果参数是原始类型的值,Object方法将其转为对应的包装对象的实例;如果Object方法的参数是一个对象,它总是返回该对象,即不用转换。

let obj = Object(1);
obj instanceof Object // true
obj instanceof Number // true 包括number、string、boolean

let arr = [];
let obj = Object(arr); // 返回原数组
obj === arr // true 包括数组、对象、函数

function isObject(value) {
    return value === Obejct(value);
} // 可以用来判断变量是否为对象(数组、对象、函数)

Object构造函数与直接赋值是等价的。

var o1 = {a: 1};
var o2 = new Object(o1); // 两个对象指针指向同一个地址。
var o3 = new Object({a: 1}); // 不能这样赋值哦,这样赋值,重新分配了一个地址,就是一个全新的对象。
o1 === o2 // true 只适用于Object,数组不适用
o1 === o3 // false

var obj = new Object(123);
obj instanceof Number // true

通过Object.prototype.toString可以看出一个值到底是什么类型,其中返回值的第二个值表示该值的构造函数。实例对象可能会自定义toString方法,覆盖掉Object.propotype.toString方法,所以最要直接用Object.prototype.toString方法,并通过函数的call方法,可以在任意上调用这个方法。

Object.prototype.toString.call(2) // "[object Number]"
Object.prototype.toString.call("") // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(Math) // "[object Math]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call([]) // "[object Array]"

// **比typeof运算符更准确的类型判断函数**
var type = function (o){
  var s = Object.prototype.toString.call(o); // *这里的正则表达式还需下点功夫,学会手动写正则*
  return s.match(/[object (.*?)]/)[1].toLowerCase();
};

type({}); // "object"
type([]); // "array"
type(5); // "number"
type(null); // "null"
type(); // "undefined"
type(/abcd/); // "regex"
type(new Date()); // "date"

emmm接下来的属性描述,平时不怎么接触,对这一块也很陌生,以后要多看看!!!
for...in循环:只有“可遍历”的属性,才会被for...in循环遍历,包括继承的属性。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104859.html

相关文章

  • 零基础的前端开发初学者应如何系统地学习

    摘要:在有了基础之后,进一步学习内容包括框架。前端学习交流群禁止闲聊,非喜勿进。代码提交前必须做的三个事情检查所有变更跑一边单元测试手动运行一遍所有 网站开发开发大致分为前端和后端,前端主要负责实现视觉和交互效果,以及与服务器通信,完成业务逻辑。其核心价值在于对用户体验的追求。可以按如下思路学习系统学习: 基础知识: html + css 这部分建议在 w3school 在线教程上学习,边...

    JouyPub 评论0 收藏0
  • 零基础的前端开发初学者应如何系统地学习

    摘要:在有了基础之后,进一步学习内容包括框架。前端学习交流群禁止闲聊,非喜勿进。代码提交前必须做的三个事情检查所有变更跑一边单元测试手动运行一遍所有 网站开发开发大致分为前端和后端,前端主要负责实现视觉和交互效果,以及与服务器通信,完成业务逻辑。其核心价值在于对用户体验的追求。可以按如下思路学习系统学习: 基础知识: html + css 这部分建议在 w3school 在线教程上学习,边...

    funnyZhang 评论0 收藏0
  • 前端基础进阶(十四):es6常用基础合集

    摘要:在继承的构造函数中,我们必须如上面的例子那么调用一次方法,它表示构造函数的继承,与中利用继承构造函数是一样的功能。 showImg(https://segmentfault.com/img/remote/1460000009078532); 在实际开发中,ES6已经非常普及了。掌握ES6的知识变成了一种必须。尽管我们在使用时仍然需要经过babel编译。 ES6彻底改变了前端的编码风格,...

    Ryan_Li 评论0 收藏0
  • 重新认识JavaScript面向对象: 从ES5到ES6

    摘要:基于原型的面向对象在基于原型的语言中如并不存在这种区别它只有对象不论是构造函数,实例,原型本身都是对象。允许动态地向单个的对象或者整个对象集中添加或移除属性。为了解决以上两个问题,提供了构造函数创建对象的方式。 showImg(https://segmentfault.com/img/remote/1460000013229218); 一. 重新认识面向对象 1. JavaScript...

    VishKozus 评论0 收藏0
  • 重新认识JavaScript面向对象: 从ES5到ES6

    摘要:基于原型的面向对象在基于原型的语言中如并不存在这种区别它只有对象不论是构造函数,实例,原型本身都是对象。允许动态地向单个的对象或者整个对象集中添加或移除属性。为了解决以上两个问题,提供了构造函数创建对象的方式。 showImg(https://segmentfault.com/img/remote/1460000013229218); 一. 重新认识面向对象 1. JavaScript...

    用户83 评论0 收藏0

发表评论

0条评论

HackerShell

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<