1.hasOwnProperty相关

为了判断一个对象是否包含自定义属性而不是原型链上的属性,我们需要使用继承自 Object.prototype 的 hasOwnProperty方法。
hasOwnProperty 是 JavaScript 中唯一一个处理属性但是不查找原型链的函数。

  1. // 修改Object.prototype
  2. Object.prototype.bar = 1;
  3. var foo = {goo: undefined};
  4.  
  5. foo.bar; //
  6. 'bar' in foo; // true
  7.  
  8. foo.hasOwnProperty('bar'); // false
  9. foo.hasOwnProperty('goo'); // true

注意: 通过判断一个属性是否 undefined 是不够的。 因为一个属性可能确实存在,只不过它的值被设置为 undefined。

hasOwnProperty 作为属性

JavaScript 不会保护 hasOwnProperty 被非法占用,因此如果一个对象碰巧存在这个属性, 就需要使用外部的 hasOwnProperty 函数来获取正确的结果。

  1. var foo = {
  2. hasOwnProperty: function() {
  3. return false;
  4. },
  5. bar: 'Here be dragons'
  6. };
  7.  
  8. foo.hasOwnProperty('bar'); // 总是返回 false
  9.  
  10. // 使用其它对象的 hasOwnProperty,并将其上下文设置为foo
  11. ({}).hasOwnProperty.call(foo, 'bar'); // true

当检查对象上某个属性是否存在时,hasOwnProperty 是唯一可用的方法。 同时在使用 for in loop遍历对象时,推荐总是使用 hasOwnProperty 方法, 这将会避免原型对象扩展带来的干扰。

for in 循环

和 in 操作符一样,for in 循环同样在查找对象属性时遍历原型链上的所有属性。

  1. // 修改 Object.prototype
  2. Object.prototype.bar = 1;
  3.  
  4. var foo = {moo: 2};
  5. for(var i in foo) {
  6. console.log(i); // 输出两个属性:bar 和 moo
  7. }

注意: 由于 for in 总是要遍历整个原型链,因此如果一个对象的继承层次太深的话会影响性能。

由于不可能改变 for in 自身的行为,因此有必要过滤出那些不希望出现在循环体中的属性, 这可以通过 Object.prototype 原型上的 hasOwnProperty 函数来完成。

使用 hasOwnProperty 过滤

  1. // foo 变量是上例中的
  2. for(var i in foo) {
  3. if (foo.hasOwnProperty(i)) {
  4. console.log(i);
  5. }
  6. }

推荐总是使用 hasOwnProperty。不要对代码运行的环境做任何假设,不要假设原生对象是否已经被扩展了。

2.命名函数的赋值表达式

另外一个特殊的情况是将命名函数赋值给一个变量。

  1. var foo = function bar() {
  2. bar(); // 正常运行
  3. }
  4. bar(); // 出错:ReferenceError

bar 函数声明外是不可见的,这是因为我们已经把函数赋值给了 foo; 然而在 bar 内部依然可见。这是由于 JavaScript 的命名处理所致, 函数名在函数内总是可见的。

注意:在IE8及IE8以下版本浏览器bar在外部也是可见的,是因为浏览器对命名函数赋值表达式进行了错误的解析, 解析成两个函数 foo 和 bar

3.方法的赋值表达式

另一个看起来奇怪的地方是函数别名,也就是将一个方法赋值给一个变量。

  1. var test = someObject.methodTest;
  2. test();

上例中,test 就像一个普通的函数被调用;因此,函数内的 this 将不再被指向到 someObject 对象。而是指向了window。

4.循环中的闭包

一个常见的错误出现在循环中使用闭包,假设我们需要在每次循环中调用循环序号

  1. for(var i = 0; i < 10; i++) {
  2. setTimeout(function() {
  3. console.log(i);
  4. }, 1000);
  5. }

上面的代码不会输出数字 0到 9,而是会输出数字10 十次。

当 console.log 被调用的时候,匿名函数保持对外部变量i的引用,此时 for循环已经结束,i的值被修改成了10.

为了得到想要的结果,需要在每次循环中创建变量 i的拷贝。

为了避免引用错误,为了正确的获得循环序号,最好使用 匿名包装器(注:其实就是我们通常说的自执行匿名函数)。

  1. for(var i = 0; i < 10; i++) {
  2. (function(e) {
  3. setTimeout(function() {
  4. console.log(e);
  5. }, 1000);
  6. })(i);
  7. }

外部的匿名函数会立即执行,并把 i 作为它的参数,此时函数内 e 变量就拥有了 i 的一个拷贝。

当传递给 setTimeout 的匿名函数执行时,它就拥有了对 e 的引用,而这个值是不会被循环改变的。

有另一个方法完成同样的工作,那就是从匿名包装器中返回一个函数。这和上面的代码效果一样。

  1. for(var i = 0; i < 10; i++) {
  2. setTimeout((function(e) {
  3. return function() {
  4. console.log(e);
  5. }
  6. })(i), 1000)
  7. }

5.对象使用和属性

JavaScript 中所有变量都可以当作对象使用,除了两个例外 nullundefined

  1. false.toString(); // 'false'
  2. [1, 2, 3].toString(); // '1,2,3'
  3.  
  4. function Foo(){}
  5. Foo.bar = 1;
  6. Foo.bar; //

一个常见的误解是数字的字面值(literal)不能当作对象使用。这是因为 JavaScript 解析器的一个错误, 它试图将点操作符解析为浮点数字面值的一部分。

  1. 2.toString(); // 出错:SyntaxError

有很多变通方法可以让数字的字面值看起来像对象。

  1. 2..toString(); // 第二个点号可以正常解析
  2. 2 .toString(); // 注意点号前面的空格
  3. (2).toString(); // 2先被计算

删除属性的唯一方法是使用 delete 操作符;设置属性为 undefined 或者 null 并不能真正的删除属性, 而仅仅是移除了属性和值的关联。

  1. var obj = {
  2. bar: 1,
  3. foo: 2,
  4. baz: 3
  5. };
  6. obj.bar = undefined;
  7. obj.foo = null;
  8. delete obj.baz;
  9.  
  10. for(var i in obj) {
  11. if (obj.hasOwnProperty(i)) {
  12. console.log(i, '' + obj[i]);
  13. }
  14. }

上面的输出结果有 bar undefinedfoo null - 只有 baz 被真正的删除了,所以从输出结果中消失。

6.arguments 对象

JavaScript 中每个函数内都能访问一个特别变量 arguments。这个变量维护着所有传递到这个函数中的参数列表。

arguments 变量不是一个数组(Array)。 尽管在语法上它有数组相关的属性 length,但它不从 Array.prototype 继承,实际上它是一个对象(Object)。

因此,无法对 arguments 变量使用标准的数组方法,比如 push, pop 或者 slice。 虽然使用 for 循环遍历也是可以的,但是为了更好的使用数组方法,最好把它转化为一个真正的数组。

转化为数组

下面的代码将会创建一个新的数组,包含所有 arguments 对象中的元素。

  1. Array.prototype.slice.call(arguments);

arguments 对象为其内部属性以及函数形式参数创建 gettersetter 方法。

因此,改变形参的值会影响到 arguments 对象的值,反之亦然。

  1. function foo(a, b, c) {
  2. arguments[0] = 2;
  3. a; // 2
  4.  
  5. b = 4;
  6. arguments[1]; //
  7.  
  8. var d = c;
  9. d = 9;
  10. c; //
  11. }
  12. foo(1, 2, 3);

如下一个例子:

  1. function sidEffecting(ary) {
  2. ary[0] = ary[2];
  3. }
  4. function bar(a,b,c) {
  5. c = 10
  6. sidEffecting(arguments);
  7. return a + b + c;
  8. }
  9. bar(1,1,1)

这里所有的更改都将生效,a和c的值都为10,a+b+c的值将为21。

7.类型相关

测试为定义变量

  1. typeof foo !== 'undefined'

上面代码会检测 foo 是否已经定义;如果没有定义而直接使用会导致 ReferenceError 的异常。 这是 typeof 唯一有用的地方。当然也能判断出来基本类型。

Object.prototype.toString检测一个对象的类型

为了检测一个对象的类型,强烈推荐使用 Object.prototype.toString 方法

如下例子:

  1. Object.prototype.toString.call([]) // "[object Array]"
  2. Object.prototype.toString.call({}) // "[object Object]"
  3. Object.prototype.toString.call(2) // "[object Number]"

类型转换

内置类型(比如 NumberString)的构造函数在被调用时,使用或者不使用 new 的结果完全不同。

  1. new Number(10) === 10; // False, 对象与数字的比较
  2. Number(10) === 10; // True, 数字与数字的比较
  3. new Number(10) + 0 === 10; // True, 由于隐式的类型转换

转换为字符串

  1. '' + 10 === '10'; // true

将一个值加上空字符串可以轻松转换为字符串类型。

转换为数字

  1. +'10' === 10; // true

使用一元的加号操作符,可以把字符串转换为数字。

转换为布尔型

通过使用 否 操作符两次,可以把一个值转换为布尔型。

  1. !!'foo'; // true
  2. !!''; // false
  3. !!'0'; // true
  4. !!'1'; // true
  5. !!'-1' // true
  6. !!{}; // true
  7. !!true; // true

8.为什么不要使用 eval

eval 函数会在当前作用域中执行一段 JavaScript 代码字符串。

  1. var foo = 1;
  2. function test() {
  3. var foo = 2;
  4. eval('foo = 3');
  5. return foo;
  6. }
  7. test(); //
  8. foo; //

但是 eval 只在被直接调用并且调用函数就是 eval 本身时,才在当前作用域中执行。

  1. var foo = 1;
  2. function test() {
  3. var foo = 2;
  4. var bar = eval;
  5. bar('foo = 3');
  6. return foo;
  7. }
  8. test(); //
  9. foo; //

上面的代码等价于在全局作用域中调用 eval,和下面两种写法效果一样:

  1. // 写法一:直接调用全局作用域下的 foo 变量
  2. var foo = 1;
  3. function test() {
  4. var foo = 2;
  5. window.foo = 3;
  6. return foo;
  7. }
  8. test(); //
  9. foo; //
  10.  
  11. // 写法二:使用 call 函数修改 eval 执行的上下文为全局作用域
  12. var foo = 1;
  13. function test() {
  14. var foo = 2;
  15. eval.call(window, 'foo = 3');
  16. return foo;
  17. }
  18. test(); //
  19. foo; //

任何情况下我们都应该避免使用 eval 函数。99.9% 使用 eval 的场景都有不使用 eval 的解决方案。

eval 也存在安全问题,因为它会执行任意传给它的代码, 在代码字符串未知或者是来自一个不信任的源时,绝对不要使用 eval 函数。

9.定时器

手工清空定时器

  1. var id = setTimeout(foo, 1000);
  2. clearTimeout(id);

清除所有定时器

由于没有内置的清除所有定时器的方法,可以采用一种暴力的方式来达到这一目的。

  1. // 清空"所有"的定时器
  2. for(var i = 1; i < 1000; i++) {
  3. clearTimeout(i);
  4. }

可能还有些定时器不会在上面代码中被清除(注如果定时器调用时返回的 ID 值大于 1000), 因此我们可以事先保存所有的定时器 ID,然后一把清除。

建议不要在调用定时器函数时,为了向回调函数传递参数而使用字符串的形式。

  1. function foo(a, b, c) {}
  2.  
  3. // 不要这样做
  4. setTimeout('foo(1,2, 3)', 1000)
  5.  
  6. // 可以使用匿名函数完成相同功能
  7. setTimeout(function() {
  8. foo(1, 2, 3);
  9. }, 1000)

绝对不要使用字符串作为 setTimeout 或者 setInterval 的第一个参数, 这么写的代码明显质量很差。当需要向回调函数传递参数时,可以创建一个匿名函数,在函数内执行真实的回调函数。

另外,应该避免使用 setInterval,因为它的定时执行不会被 JavaScript 阻塞。

后续逐渐添加

[笔记]JavaScript 秘密花园的更多相关文章

  1. RX编程笔记——JavaScript 获取地理位置

    RX编程笔记——JavaScript 获取地理位置 2016-07-05

  2. JavaScript 秘密花园——对象的使用和属性操作

    JavaScript 中所有变量都是对象,除了两个例外 null 和 undefined. false.toString(); // 'false' [1, 2, 3].toString(); // ...

  3. 160426、JavaScript 秘密花园

    简介 关于作者 这篇文章的作者是两位 Stack Overflow 用户, 伊沃·韦特泽尔 Ivo Wetzel(写作) 和 张易江 Zhang Yi Jiang(设计). 贡献者 贡献者 中文翻译 ...

  4. 学习笔记---Javascript事件Event、IE浏览器下的拖拽效果

    学习笔记---Javascript事件Event.IE浏览器下的拖拽效果     1. 关于event常用属性有returnValue(是否允许事件处理继续进行, false为停止继续操作).srcE ...

  5. 慕课笔记-JavaScript正则表达式

    目录 慕课笔记-JavaScript正则表达式笔记 概述 RegExp对象 修饰符 元字符 字符类 范围类 预定义类 预定义字符 边界 量词 贪婪模式 分组 或(使用竖线表示) 反向引用 忽略分组 前 ...

  6. 笔记-javascript

    笔记-javascript 1.      简介 JavaScript一种直译式脚本语言,是一种动态类型.弱类型.基于原型的语言,内置支持类型.它的解释器被称为JavaScript引擎,为浏览器的一部 ...

  7. 笔记-JavaScript与HTML DOM

    引用源:https://www.cnblogs.com/propheterLiu/p/5966791.html 笔记-JavaScript和HTML DOM 区别: javascript JavaSc ...

  8. 前端学习实践笔记--JavaScript深入【3】

    这章主要讨论闭包和原型,以及面向对象和继承. 闭包 闭包充分利用了JS里面作用域的概念,作用域的好处是内部函数可以访问定义它们的外部函数的参数和变量.使用闭包主要是为了读取函数内部的变量或者将函数内部 ...

  9. 前端学习实践笔记--JavaScript深入【1】

    这一年中零零散散看过几本javascript的书,回过头看之前写过的javascript学习笔记,未免有点汗颜,突出“肤浅”二字,然越深入越觉得javascript的博大精深,有种只缘身在此山中的感觉 ...

随机推荐

  1. puppetdb搭建

    puppetdb搭建 在agent端跑puppet agent -t 正常的情况下,安装puppetdb 部署postgresql数据库 部署puppetdb 建立puppetserver与puppe ...

  2. 可能是把Java内存区域讲的最清楚的一篇文章

    写在前面(常见面试题) 下面是面试官可能在“Java内存区域”知识点问你的问题,快拿出小本本记下来! 基本问题: 介绍下Java内存区域(运行时数据区). Java对象的创建过程(五步,建议能默写出来 ...

  3. redis安装以及安全配置

    redis安装以及安全配置 1. 安装 sudo apt-get install redis-server 使用which查询redis执行体安装路径: which redis-server #/us ...

  4. C# 结合 using 语句块的三种实用方法

    一.简介 阅读 Abp 源码的过程中,自己也学习到了一些之前没有接触过的知识.在这里,我在这儿针对研究学习 Abp 框架中,遇到的一些值得分享的知识写几篇文章.如果有什么疑问或者问题,欢迎大家评论指正 ...

  5. Android--UI之ScrollView

    前言 本篇博客主要讲解ScrollView和HorizontalScrollView两个容器的使用.它们分别代表了垂直滚动以及水平滚动,滚动的内容是它其中包含的View.在本篇会简单介绍ScrollV ...

  6. AndroidStudio意外崩溃,电脑重启,导致重启打开Androidstudio后所有的import都出错

    解决方案: File -> Invalidate Cashes / Restart 重新编译的时候可能会碰到下面的问题: 解决方案: 关闭Android Studio,删掉工程下的.gradle ...

  7. 全网最全最详细的Windows下安装Anaconda2 / Anaconda3(图文详解)

    不多说,直接上干货! 说明: Anaconda2-5.0.0-Windows-x86_64.exe安装下来,默认的Python2.7 Anaconda3-4.2.0-Windows-x86_64.ex ...

  8. 深度解读阿里巴巴云原生镜像分发系统 Dragonfly

    Dragonfly 是一个由阿里巴巴开源的云原生镜像分发系统,主要解决以 Kubernetes 为核心的分布式应用编排系统的镜像分发难题.随着企业数字化大潮的席卷,行业应用纷纷朝微服务架构演进,并通过 ...

  9. Elastic Search 安装和配置

    目标 部署一个单节点的ElasticSearch集群 依赖 java环境 $java -version java version "1.8.0_161" Java(TM) SE R ...

  10. MySQL数据库——表操作

    I.表操作 一.创建表 基本语法如下: create table 表名( 列名 类型 是否可以为空, 列名 类型 是否可以为空 )ENGINE=InnoDB DEFAULT CHARSET=utf8: ...