JavaScript

来自tomtalk
Tom讨论 | 贡献2019年8月14日 (三) 21:20的版本 三. 原型对象

跳转至: 导航搜索

库、框架

Js构造函数

https://www.jianshu.com/p/5f38069a4acc

常规姿势

//声明一个"人"的构造函数,目的是通过new出来的对象都是一个个的具体的"人"
var Person = function () {
    var prx = "other"; //私有属性,该属性new出来的对象时无法获取到的。
    var prxMethond = function () {
        console.log("in prxMethod");
    }; //用var申明私有方法,该方法不能被new Person调用到。一般的,我们只需要再内部使用到的方法可最好声明称私有方法。
 
    this.name = "double"; //定义实例属性,也就是说,通过new出来的对象都具备这个属性。
    this.say = function () {
        console.log("hi i am " + this.name);
    } //say是一个实例方法,new出来的对象都有say的方法。
};
 
Person.prototype.eat = function () {
    console.log(this.name + "eat something...");
}; //为Person定义了一个原型方法eat,该方法为公共方法。每一个通过new Person 实例出来的对象都共享同一个eat方法。
 
Person.staticMethod = function () {
    console.log("this is static method");
}; //定义静态方法,该方法目的在于不用new Person 就能调用该方法。我们把不用实例化就能调用的方法叫做静态方法。

在Js中使用new 关键字调用一个函数的问题

var zhangsan = new Person();
 
// 实际上内部是这样的:
var zhangsan = {};
zhangsan.__proto__ = Person.prototype;
Person.call(zhangsan);

上面三句代码的说明:

  1. 声明了一个zhangsan的空对象
  2. 将Person的prototype赋值给zhangsan的proto属性。关于对象的prototype和proto概念如下:prototype是函数的一个属性(每个函数都有一个prototype属性),这个属性是一个指针,指向一个对象。它是显示修改对象的原型的属性。proto是一个对象拥有的内置属性(每个对象都有一个proto属性),是JS内部使用寻找原型链的属性。这就是为什么zhangsan可以调用到Person原型(Person.prototype)方法的原因。
  3. 调用Person函数,并且把zhangsan当成是this传入函数中,这就是为什么zhangsan可以调用Person函数定义的this.属性或this.方法的原因

构造函数返回值得问题

任意一个函数都是有返回值得,如果我们不明确的写出返回值 函数默认返回的是undefined.构造函数也是一样。

var f = function () {
    this.name = "f_name";
    this.say = function () {
        console.log("say");
    };
    console.log("in f");
};
console.log(f());
//我们用chrome执行上面代码获取的结果为:
//in f
//undefined
 
var fo = new f();
console.log(fo);
//返回的是:执行完fo.__proto__=f.prototype绑定关系和f.call(fo)调用的fo对象。

如果我的构造函数有明确的返回值呢?

var f=function(){
  this.name="f_name";
  this.say=function(){
    console.log("say");
  }
  return '123';
}
//非 new调用:
f(); //返回"123"的字符串,这相当于就是调用的一个普通函数
 
//new 调用
new f();
//返回值同返回undefined时相同的{__proto__:f,name:"f_name",say:function()arguments: null,caller: null,length: 0,name: "",prototype....}

如果我们的返回值是一个对象呢?

var f = function () {
    this.name = "f_name";
    this.say = function () {
        console.log("say");
    };
    return {"age": 25}
};
//非 new调用:
console.log(f()); //返回{"age":25}的字符串,这相当于就是调用的一个普通函数
 
//new 调用
console.log(new f()); //返回值是:{age: 25}

也就是说,如果我们的函数明确返回一个对象,则无论是否使用new关键字调用,都获取的是返回的这个对象,这也是很多使用该方法构建模块化的方法和原理。

构造函数的调用

结合上面的例子和原理的理解,下面做一些基本的测试。

Person().staticMethod()//报错,因为Person()是一般的函数调用,因为Person没有明确的返回值所以默认返回为undefined,所以 调用代码实际上是undefined.方法,所以error了
Person.staticMethod()//打印结果为:this is static method,此次调用的是静态方法,如何定义的就如何调用
Person.prototype.eat()//调用原型链中的方法,但eat中的this,指定的是window 所以打印结果为:undefined eat something...
var zhangsan = new Person()
//先在zhangsan对象中找是否有eat方法发现没有,
//则通过__proto__去找,上面我们提到过,
//new 关键字调用会将: zhangsan,__proto__=Person.prototype,
//所以就会去Person.prototype找eat方法,找到了调用,
//而关于this.name获取到值原因同样是因为new 关键字的 Person.call(zhangsan)的执行操作,
//将Person中的this相关的内容都进行了"copy",其实就是this复制到了zhangsan对象中,
//关于这点我们前面已验证过了.所以zhangsan.name是能找到值的
//输出结果就成了:double eat something...
zhangsan.eat()//double eat something...
zhangsan.name//同上

function, new function, new Function之间的区别区别分析

只要 new 表达式之后的 constructor 返回(return)一个引用对象(数组,对象,函数等),都将覆盖new创建的匿名对象,如果返回(return)一个原始类型(无 return 时其实为 return 原始类型 undefined),那么就返回 new 创建的匿名对象。

var yx01 = new function () {
    return "圆心"
};
console.log(yx01);
 
//我们运行代码,将返回显示“[object object] ”,此时该代码等价于:
//function 匿名类(){
//    return "圆心";
//}
 
var yx01 = new function () {
    return new String
};
console.log(yx01);
 
var yx02 = function () {
    return "圆心"
}();
console.log(yx02);

变量 doAdd 被定义为函数,然后 alsodoAdd 被声明为指向同一个函数的指针。用这两个变量都可以执行该函数的代码,并输出相同的结果 - "20"。

Function 对象也有与所有对象共享的 valueOf() 方法和 toString() 方法。这两个方法返回的都是函数的源代码,在调试时尤其有用。

var doAdd = new Function("iNum", "console.log(iNum + 10)");
var alsodoAdd = doAdd;
doAdd(10);	    //输出 "20"
alsodoAdd(10);	//输出 "20"
 
console.log(doAdd.valueOf());
console.log(doAdd.toString());

函数 doAdd() 定义了一个参数,因此它的 length 是 1;sayHi() 没有定义参数,所以 length 是 0。

function doAdd(iNum) {
    alert(iNum + 10);
}
 
function sayHi() {
    alert("Hi");
}
 
console.log(doAdd.length);	//输出 "1"
console.log(sayHi.length);	//输出 "0"
 
// 形式一
var foo01 = function () //or fun01 = function()
{
    var temp = 100;
    this.temp = 200;
    return temp + this.temp;
}
 
console.log(typeof(foo01));
console.log(foo01());
 
// 形式二
var foo02 = new function () {
    var temp = 100;
    this.temp = 200;
    return temp + this.temp;
};
 
console.log(typeof(foo02));
console.log(foo02.constructor());
 
// 形式三
var foo3 = new Function('var temp = 100; this.temp = 200; return temp + this.temp;');
 
console.log(typeof(foo3));
console.log(foo3());
 
// 形式四:这个方式是不常使用的,效果和方法三一样,不过不清楚不用new来生成有没有什么副作用,
// 这也体现了JavaScript一个最大的特性:灵活!能省就省。
var foo4 = Function('var temp = 100; this.temp = 200; return temp + this.temp;');
 
console.log(typeof(foo4));
console.log(foo4());

JavaScript中以function定义的函数和以new Function定义的函数不等价?

首先给一段代码:

window.local = 'hi';
(function () {
    var local = 1;
    console.log(typeof local);  //number
    Function("console.log(typeof local);")();  //string
}());

看完这段代码,相信应该有些模糊的猜测了。下面说结论:

  1. `Function(arg1, arg2, ..., body)` 等价于 `new Function(arg1, arg2, ..., body)`。
  2. 对 `function fn(){}` 和 `var fn = function(){}` 而言,以当前的 LexicalEnvironment 作为 scope 传给函数。
  3. 对 `new Function(arg1, arg2, ..., body)` 而言,以 Global Environment,浏览器上就是 window 为 scope。

所以你列出的那些例子中,`(new) Function("console.log(typeof local);")();` 中对local取得是 window.local, 这个是 undefined。

最详尽的 JS 原型与原型链终极详解,没有「可能是」。

https://www.jianshu.com/p/dee9f8b14771

总论

什么是原型、原型链?

它们有什么特点?

它们能做什么?

怎么确定它们的关系?

一. 普通对象与函数对象

凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。

Person.prototype.constructor == Person;
person1.__proto__ == Person.prototype;
person1.constructor == Person;

JavaScript 是基于原型的。简单来说,就是当我们创建一个函数的时候,系统就会自动分配一个 prototype属性,可以用来存储可以让所有实例共享的属性和方法。

function Person() {
}
 
var p = new Person();
 
p.__proto__ === Person.prototype // true
 
Person.prototype.constructor === Person // true
  • 每一个构造函数都拥有一个 prototype 属性,这个属性指向一个对象,也就是原型对象
  • 原型对象默认拥有一个 constructor 属性,指向指向它的那个构造函数
  • 每个对象都拥有一个隐藏的属性 __proto__,指向它的原型对象


原型特点

  • 实例可以共享原型上面的属性和方法。
  • 实例自身的属性会屏蔽原型上面的同名属性,实例上面没有的属性会去原型上面找。

原型重写

function Person(){}
var p = new Person();
Person.prototype = {
    name: 'tt',
    age: 18
}
 
Person.prototype.constructor === Person // false
 
p.name // undefined
  • 在已经创建了实例的情况下重写原型,会切断现有实例与新原型之间的联系。
  • 重写原型对象,会导致原型对象的 constructor 属性指向 Object ,导致原型链关系混乱,所以我们应该在重写原型对象的时候指定 constructor( instanceof 仍然会返回正确的值)。
Person.prototype = {
    constructor: Person
}

注意:以这种方式重设 constructor 属性会导致它的 Enumerable 特性被设置成 true(默认为false)。

二. 构造函数

我们先复习一下构造函数的知识:

function Person(name, age, job) {
 this.name = name;
 this.age = age;
 this.job = job;
 this.sayName = function() { alert(this.name) } 
}
var person1 = new Person('Zaxlct', 28, 'Software Engineer');
var person2 = new Person('Mick', 23, 'Doctor');

上面的例子中 person1 和 person2 都是 Person 的实例。这两个实例都有一个 constructor (构造函数)属性,该属性(是一个指针)指向 Person。 即:

console.log(person1.constructor == Person); //true
console.log(person2.constructor == Person); //true

我们要记住两个概念(构造函数,实例):person1 和 person2 都是 构造函数 Person 的实例

一个公式:实例的构造函数属性(constructor)指向构造函数。

三. 原型对象

每个对象都有 __proto__ 属性,但只有函数对象才有 prototype 属性。

原型对象(Person.prototype)是 构造函数(Person)的一个实例。

原型链

JavaScript 中所有的对象都是由它的原型对象继承而来。而原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链。

  • 所有原型链的终点都是 Object 函数的 prototype 属性。
  • Objec.prototype 指向的原型对象同样拥有原型,不过它的原型是 null ,而 null 则没有原型。

class类

ES6 提供了 Class(类) 这个概念,作为对象的模板,通过 class 关键字,可以定义类。

ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
 
  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}
 
// 可以这么改写
function Point(x, y) {
  this.x = x;
  this.y = y;
}
 
Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

class 里面定义的方法,其实都是定义在构造函数的原型上面实现实例共享,属性定义在构造函数中,所以 ES6 中的类完全可以看作构造函数的另一种写法

除去 class 类中的一些行为可能与 ES5 存在一些不同,本质上都是通过原型、原型链去定义方法、实现共享。所以,还是文章开始那句话 JavaScript是基于原型的。

关系判断

instanceof

最常用的确定原型指向关系的关键字,检测的是原型,但是只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型

function Person(){}
var p = new Person();
 
p instanceof Person // true
p instanceof Object // true

hasOwnProperty

通过使用 hasOwnProperty 可以确定访问的属性是来自于实例还是原型对象

function Person() {}
Person.prototype = {
    name: 'tt'
}
var p = new Person();
p.age = 15;
 
p.hasOwnProperty('age') // true
p.hasOwnProperty('name') // false

原型链的问题

由于原型链的存在,我们可以让很多实例去共享原型上面的方法和属性,方便了我们的很多操作。但是原型链并非是十分完美的:

function Person(){}
Person.prototype.arr = [1, 2, 3, 4];
 
var person1 = new Person();
var person2 = new Person();
 
person1.arr.push(5) 
person2.arr // [1, 2, 3, 4, 5]

引用类型,变量保存的就是一个内存中的一个指针。所以,当原型上面的属性是一个引用类型的值时,我们通过其中某一个实例对原型属性的更改,结果会反映在所有实例上面,这也是原型共享属性造成的最大问题。

另一个问题就是我们在创建子类型(比如上面的 p)时,没有办法向超类型( Person )的构造函数中传递参数。

后记

鉴于原型的特点和存在的问题,所以我们在实际开发中一般不会单独使用原型链。一般会使用构造函数和原型相配合的模式,当然这也就牵扯出了 JavaScript 中另一个有意思的领域:继承。

js正则表达式

var currency = /((^([1-9]\d*))|^0)(\.\d{1,2})?$|(^0\.\d{1,2}$)/;
var people = /^([1-9]\d*)?$/;

js浮点运算bug的解决办法

使用big.js。

jQuery中.bind() .live() .delegate() .on()的区别

//为每个匹配元素的特定事件绑定事件处理函数
bind(type,[data],fn) 
$("a").bind("click",function(){alert("ok");});
 
//给所有匹配的元素附加一个事件处理函数,即使这个元素是以后再添加进来的
live(type,[data],fn) 
$("a").live("click",function(){alert("ok");});
 
//指定的元素(属于被选元素的子元素)添加一个或多个事件处理程序,并规定当这些事件发生时运行的函数
delegate(selector,[type],[data],fn) 
$("#container").delegate("a","click",function(){alert("ok");})
 
//在选择元素上绑定一个或多个事件的事件处理函数
on(events,[selector],[data],fn)

差别

  • .bind()是直接绑定在元素上
  • .live()则是通过冒泡的方式来绑定到元素上的。更适合列表类型的,绑定到document DOM节点上。和.bind()的优势是支持动态数据。
  • .delegate()则是更精确的小范围使用事件代理,性能优于.live()
  • .on()则是最新的1.9版本整合了之前的三种方式的新事件绑定机制

js 判断微信浏览器

//JS
function isWeiXin(){
    var ua = window.navigator.userAgent.toLowerCase();
    if(ua.match(/MicroMessenger/i) == 'micromessenger'){
        return true;
    }else{
        return false;
    }
}
//PHP
function is_weixin(){
    if ( strpos($_SERVER['HTTP_USER_AGENT'], 'MicroMessenger') !== false ) {
        return true;
    }
    return false;
}

script标签的async属性

一般的script标签(不带async等属性)加载时会阻塞浏览器,也就是说,浏览器在下载或执行该js代码块时,后面的标签不会被解析,例如在head中添加一个script,但这个script下载时网络不稳定,很长时间没有下载完成对应的js文件,那么浏览器此时一直等待这个js文件下载,此时页面不会被渲染,用户看到的就是白屏(网页文件已下载好,但浏览器不解析)

而使用async属性,浏览器会下载js文件,同时继续对后面的内容进行渲染

通常如果js不需要改变DOM结构时可以使用async进行异步加载(比如一些统计代码可以异步加载,因为此代码与页面执行逻辑无关,不会改变DOM结构)

IE8中trim方法报错的问题

//IE8下没有
str.trim()
 
//替代方案
Ext.string.trim(str) //Extjs用法
$.trim(str)             //jquery用法

JS基础

window.location.hash属性介绍

History of Ecma

javascript避免数字计算精度误差的方法之一

如何判断Javascript对象是否存在

JS中级

OOP

再谈javascript面向对象编程

JS高级

javascript代码片段

杂项

Fiddler自动响应AutoResponder正则匹配

用正则批量替换

regex:http://cc-t\.ied\.com/(.+)\.js

http://localhost/$1.js

JS模板引擎jSmart

写一段最短的代码,用上js所有关键字

实用Javascript代码高亮脚本

JS正则表达式

精通JavaScript需要注意的细节:变量

确保链接不依赖于javascript

javascript 框架功能大剖析

12种JavaScript MVC框架之比较

JavaScript框架

ArtTemplate高性能JavaScript模板引擎

JavaScript开发者值得收藏的 7 个资源

一个Web前端攻城狮的收藏夹

前端招聘与前端卖身的困境

基于Web的打印方案比较分析

浏览器的两种模式quirks 和strict

zebra-dialog

http://sourceforge.net/projects/zebra-dialog/

http://stefangabos.ro/jquery/zebra-dialog/#how-to-use

使用Chrome Console调试代码

javascript代码风格

google javascript styleguide

webstorm

JSLint was used before it was defined

As Quentin says, there's a /*global*/ directive.Here is an example (put this at the top of the file):

/*global var1,var2,var3,var4,var5*/
webstorm 如何设置显示代码的行数

file>setting>editor>appearance>show line nmbers

注释

Webstorm非常的知心,当你写好函数时候,输入“/**”加回车,会自动生成jsdoc格式的注释。

combine this with the previous var statement

To avoid this JSLint errors you may

1、Define all variables together

var sGreeting = 'hello world', a, b, c;

2、Use JSLint directive Tolerate -> many var statements per function

JSHint bad line breaking before '+'

Relaxing Options -> Supress warnings about unsafe line breaks laxbreak

webstorm快捷键

F2 向下错误或警告快速定位

Shift+F2 向上错误或警告快速定位

reformat code (cltr+alt+L)

structure (alt+7)

ctrl+shift+ +/- 展开/折叠

Alt+1 快速打开或隐藏工程面板

Ctrl+X 删除行

Ctrl+D 复制行

shift+enter 重新开始一行(无论光标在哪个位置)

Ctrl+Shift+Up/Down 代码向上

Ctrl + shift + j 合并行

Shift + F6 Refactor Rename

SHIFT+ALT+INSERT 竖编辑模式

JS定时器

在javascritp中,有两个关于定时器的专用函数,分别为:

//倒计定时器:
timer = setTimeout(func_a, delaytime_ms); 
clearTimeout(timer);      //清除已设置的setTimeout对象
 
//循环定时器:
timer = setInterval(func_b, delaytime_ms); 
clearInterval(timer);     //清除已设置的setInterval对象