JavaScript问答

最近, SmartSpate决定收集有关JavaScript的问题并予以回答。 该材料(我们发布的翻译)包含有关JavaScript的两十几个问题及其答案。 这里涵盖的主题范围很广。 特别是这些是语言的功能,是程序员在编写JS代码时遇到的问题,可以在浏览器和Node.js中使用。



问题编号1。 原型继承


我习惯了“经典”课程,但决定学习JavaScript。 我在理解原型模型时遇到问题。 如果可能,以模板的形式说明在JavaScript中创建“类”的能力,向我们介绍类的封闭和开放方法以及属性。 我知道已经有很多关于此的文章,并且在JavaScript中,对象的方法和属性默认情况下是公开可用的,但是我想正确地理解所有这些。 原型继承如何在JavaScript中起作用?

▍答案


古典继承非常让人联想到人们如何继承其祖先的基因。 人们具有一些共同的基本能力,例如走路和说话。 另外,每个人都有一些特殊之处。 人们无法更改所谓的“类”,但可以在一定范围内更改自己的“属性”。 同时,祖父母,母亲和父亲不能在其生活过程中影响子女的基因。 因此,一切都安排在地球上,但让我们想象一下另一种继承机制以特殊方式起作用的星球。 说,一些能够突变的生物利用那里的“心灵感应遗传”机制。 他们可以在其生命过程中改变其后代的遗传信息这一事实得到了体现。

考虑在这个奇怪的星球上继承的例子。 父亲对象从祖父对象继承基因,儿子对象从父亲继承遗传信息。 这个星球上的每个居民都可以自由变异并改变其后代的基因。 例如,在“祖父”处,皮肤为绿色。 该标志由“父亲”和“儿子”继承。 突然,“祖父”认为他厌倦了绿色。 现在,他想变蓝,改变肤色(用JS表示-改变班级的原型),“感同身受”地将这种变异传递给“父亲”和“儿子”。 此后,“父亲”相信“祖父”已经从头脑中幸存下来,决定改变他的基因,以便他再次变绿(即,他改变了自己的原型)。 这些更改以“心灵感应”的方式传递给“儿子”。 结果,“父亲”和“儿子”再次具有绿色皮肤。 同时,“祖父”仍然是蓝色的。 现在,无论他如何处理颜色,都不会影响其他任何人。 所有这些都是由于“父亲”在他的“原型”中明确设置了他的皮肤颜色,而“儿子”继承了这种颜色。 然后“儿子”这样想:“我会变黑的。 让我的后代继承我父亲的肤色。” 为此,他更改了自己的属性(而不是原型的属性),以使其属性会影响其肤色,但不会影响其后代。 我们以代码形式表示所有这些内容:

var Grandfather = function () {}; //  Grandfather Grandfather.prototype.color = 'green'; var Father = function () {}; //  Father Father.prototype = new Grandfather (); //  - ,        var Son = function () {}; //  Son Son.prototype = new Father (); // Son   Father var u = new Grandfather (); //   Grandfather var f = new Father (); //   Father var s = new Son (); //   Son //     console.log ([u.color, f.color, s.color]); // ["green", "green", "green"] //  Grandfather        Grandfather.prototype.color = 'blue'; console.log ([u.color, f.color, s.color]); // ["blue", "blue", "blue"] //  Father      -   ,     Father.prototype.color = 'green'; //      : // Grandfather.prototype.color = 'green'; console.log ([u.color, f.color, s.color]); // ["blue", "green", "green"] // ,        Grandfather     color,      Grandfather.prototype.color = 'blue'; console.log ([u.color, f.color, s.color]); // ["blue", "green", "green"] //  Son,  ,       Grandfather,    s.color = 'black'; //        console.log ([u.color, f.color, s.color]); // ["blue", "green", "black"] var SonsSon = function () {}; //  SonsSon -   Son SonsSon.prototype = new Son (); //  var ss = new SonsSon (); //   SonsSon //         console.log ([u.color, f.color, s.color, ss.color]); // ["blue", "green", "black", "green"] 

问题编号2。 创建对象


如果使用new关键字创建对象的新实例,那么如何保护自己免受错误的侵害? 这是我通常的工作方式:

  1. 我总是构造构造函数,以便它们以大写字母开头。
  2. 我使用this instanceof Function_Name构造检查操作的正确性(出于性能原因,我尝试不使用this instanceof arguments.callee类型的构造)。
  3. 这种方法与上一种方法类似,但是比较是使用window ,因为我不喜欢对实体名称进行硬编码,也不需要为浏览器以外的环境编写代码。

什么是创建对象最方便的模型?

▍答案


从思想上和基于这种方法的熟悉程度来看,最好使用new关键字创建对象。 在这种情况下,必须为构造函数指定以大写字母开头的名称。

我更喜欢遵守规则,而不对构造函数执行其他检查。 如果在没有new情况下调用构造函数并且工作在全局范围内开始,则可以将其与“自欺欺人”进行比较。 同时,我绝不建议您在没有使用new关键字的情况下处理设计师中的情况。 例如,可能看起来像这样:如果在没有new情况下调用了构造函数,则无论如何都会创建并返回一个新对象。 这种方法在思想上是不正确的,并会导致错误。
这是使用构造函数的示例。

 var Obj = function () {   "use strict";   this.pew = 100; }; //  let o = new Obj(); o.pew++; console.log(o.pew); //101 // .   Obj (); // TypeError: this is undefined 

最好不要将new关键字用于工厂方法,并且在不需要构造函数的情况下,使用对象文字创建对象会更加方便。 假设以下示例中显示的构造函数代码明确地是多余的:

 //    . var Obj = function () {    if (! (this instanceof Obj)) {        return new Obj ();    }    this.pew = 100; }; 

如果即使要创建一个小对象,仍然需要构造函数,则最好这样做:

 var Obj = function () {    "use strict";    this.pew = 100; }; 

在这里,如上所示,由于构造函数在严格模式下工作,因此在不使用new情况下调用它时将发生错误。

问题编号3。 拦截鼠标点击


如何使用JavaScript知道单击了哪个鼠标按钮?

▍答案


单击鼠标按钮将生成mousedownmouseup事件。 在这种情况下, click事件仅由鼠标左键生成。 在事件处理程序中,您需要检查位于event.button属性中的代码,以便找出按下了哪个按钮(0-左,1-中间,2-右)。 但是,在IE中,一切看起来都有些不同。 考虑一个例子:

 var button = document.getElementById ('button'),              // 0 1 2    buttonMap = ['Left', 'Middle', 'Right'],    handler = function (event) {        event = event || window.event;        alert (buttonMap [event.button] + 'id:' + event.button);    }; if (button.addEventListener) {     button.addEventListener ('mousedown', handler, false); } else {     // IE 0 1 2 3 4     buttonMap = ['???', 'Left', 'Right', '???', 'Middle'];     button.attachEvent ('onmousedown', handler); } 

jQuery库考虑了此IE功能,因此在任何浏览器中使用它时,只需检查event.which属性的值即可,而不是与event.button

 $('button').mousedown(function (event) {   alert(['Left', 'Middle', 'Right'][event.which]); }); 

问题编号4。 拦截键盘按键的击键


是否可以使用JavaScript拦截箭头键(尤其是按下向下键和向上键),以便在单击它们后浏览器不会滚动页面? 如果可能的话,那么在不同的浏览器中实现此功能有什么特点? 假设一个页面显示在一个页面上,而该页面并不完全适合屏幕。 必须使用箭头键来组织在该表的单元格中移动,并且当您单击此类键时,要求浏览器不要滚动页面。

▍答案


为了实现这样的功能,首先,您需要禁用对控制动作的标准系统响应。 例如,箭头键和鼠标滚轮滚动页面,右键单击页面会弹出一个上下文菜单,当您单击submit按钮时, form.submit()函数,当您单击输入字段时,它将获得输入焦点,当您单击链接时,浏览器将加载它指向的页面。

这可以通过不同的方式来完成。 例如,像这样:

 window.addEventListener("keydown", function(e) {   //  -   if([37, 38, 39, 40].indexOf(e.keyCode) > -1) {       e.preventDefault();   } }, false); 

此后的页面通常不会响应箭头键的按下。
这里要注意的一件事。 preventDefault()在执行默认操作之前preventDefault()方法。 例如,如果单击某个字段以防止其获得输入焦点,则需要在默认操作之前将适当的处理程序挂在事件链中的事件上。 在我们的例子中,这是一个mousedown事件:

 $('input').bind ('mousedown', function (event) {   event.preventDefault();   //    return false; }); 

当您单击输入字段时,会发生以下事件-按照在此显示的顺序:

  1. mousedown
  2. focus (在此之前,失去焦点的另一个对象将触发blur事件)
  3. mouseup
  4. click

如果您尝试阻止元素获取输入焦点,那么从focus事件处理程序开始使用事件处理程序将无济于事。

问题编号5。 停止GIF动画和ESC键


按下ESC键时如何处理停止GIF动画的问题?

▍答案


在这里,您可以使用上面考虑的相同方法。 在某些浏览器中,按ESC键可停止GIF动画和页面加载。 这是它们的标准行为,并且为了防止它们以这种方式表现,与以前一样, preventDefault()事件方法对我们很有用。 ESC键代码是27。

问题编号6。 IIFE中的括号


声明立即调用的函数表达式(IIFE)时如何使用两个括号结构?

▍答案


在这种情况下,括号使解析器了解到在它们前面需要执行的功能。 但是他还需要了解这些括号是什么-分组运算符,或指示需要调用该函数的构造。 例如,如果我们使用两个括号,如下所示,则会SyntaxError错误:

 function () { //  }() 

这是因为该函数没有名称(必须在函数声明中指定其名称)。

让我们尝试重写此代码,为函数命名:

 function foo() { //  }() 

现在函数已经有了名称,从系统的角度来看,从理论上讲,这种构造应该看起来很正常。 但是错误并没有消失,尽管现在它与分组运算符有关,该运算符内部没有表达式。 请注意,在这种情况下,分组语句后面是分组运算符,而不是一个括号序列,该括号告诉系统必须调用其前面的函数。

通常,IIFE的设计如下:

 (function () {   //  })() 

但是还有其他方法,其本质是要以某种方式向解析器表明,在它只是一个需要执行的功能表达式之前:

 !function () { //  }(); +function () { //  }(); [function() { //  }()]; var a = function () { //  }(); 

IIFE在JavaScript编程中被广泛使用。 例如,此构造在jQuery中使用。 有了它的帮助,您可以创建闭包。 实际上,我们正在谈论的事实是,使用IIFE,程序员可以在本地范围内执行某些代码。 这有助于保护全局范围免受污染,并允许优化对全局变量的访问。 这样的设计被很好地缩小了。

问题编号7。 响应请求传递代码


服务器在与客户端进行AJAX交互的过程中,在响应主体中向客户端发送alert ('Boom !!!');字符串alert ('Boom !!!'); 。 客户端接受响应并使用eval()函数执行此代码。 叫什么 毕竟,服务器响应中包含的不是JSON,不是XML,也不是HTML。 您能以某种请求的响应主体的形式在服务器端执行来自服务器的代码,该怎么说?

▍答案


实际上,这种用于客户端-服务器交互的方案没有特殊的名称。 强烈建议不要使用系统交互方案。 这就像将PHP代码存储在数据库中,然后使用适当的语言方法执行代码一样糟糕。 即使我们不考虑意识形态方面的考虑,我们也可以说这样的体系结构是非常不灵活的,因此,如果在使用该体系结构的项目中,随着它的发展,有必要对它进行更改,这并不容易。 在这里,我们看到了一个将数据与代码和接口元素混合在一起时糟糕的系统体系结构的示例。 为了在这样的系统中进行更改,您首先需要了解其复杂体系结构的复杂性,然后在进行更改后再次“混淆”所有内容。 我不是在谈论代码重用。

为了简化代码支持,您需要努力最大程度地分离系统的各个部分,并减少这些部分的相互依赖性。 为了确保系统各部分之间的弱连接,即确保可以从中提取应用程序的某个片段,或者以最小的复杂性将其替换为另一个应用程序,可以使用事件机制或特殊的体系结构解决方案(例如MVC)。

问题编号8。 在主线程中执行繁重的操作


如何在JavaScript中组织某些资源密集型命令的执行,而不是“暂停”整个脚本?

▍答案


JavaScript是一种单线程语言。 网页代码在同一线程中执行,并且执行DOM树转换。 也有计时器。 每次执行一些消耗资源的操作(循环,调用“繁重”功能)时,这都会导致用户界面速度变慢甚至完全阻塞。 如果执行的操作对系统没有特别大的负担,那么它们对接口的影响将微不足道,以至于用户根本不会注意到它。 为了在主线程之外进行繁重的计算,在JavaScript中引入了Web worker的概念。

如果无法使用工人,则需要优化周期和“繁重”功能。 在“ JavaScript。 性能优化”尼古拉斯·扎卡斯(Nicholas Zakas)说,如果用户界面流被阻塞100毫秒或更短,用户将不会注意到任何东西。

从这个想法中,我们可以得出结论,可以将资源密集型计算分为多个片段,其实现最多需要100 ms,之后必须释放主线程。

这是上本书的示例代码:

 function timedProcessArray(items, process, callback) {   var todo = items.concat();   //    setTimeout(function () {       var start = +new Date();       do {           process(todo.shift());       } while (todo.length > 0 && (+new Date() - start < 50));       if (todo.length > 0){           setTimeout(arguments.callee, 25);       } else {           callback(items);       }   }, 25); } function saveDocument(id) {   var tasks = [openDocument, writeText, closeDocument, updateUI];   timedProcessArray(tasks, [id], function(){       alert("Save completed!");   }); } 

timedProcessArray()函数将主线程阻塞25 ms,执行一系列操作,然后释放它25 ms,然后重复此过程。

问题编号9。 关于调整浏览器窗口的大小


我能以某种方式发现用户已完成浏览器窗口大小的调整吗?

▍答案


没有特殊事件可以让您查找。 但是您可以使用onresize事件来确定用户是否正在调整窗口大小。 但是,这种方法不是很准确。

这是解决此问题的代码草案。

 var time = 0,   timerId,   TIME_ADMISSION = 100; // 0.1  function onresizeend () {   console.log('onresizeend'); }; function resizeWatcher () {   if (+new Date - time >= TIME_ADMISSION) {       onresizeend();       if (timerId) {           window.clearInterval(timerId);           timerId = null;       }   } }; $(window).resize(function () {   if (!timerId) {       timerId = window.setInterval(resizeWatcher, 25);   }   time = +new Date; }); 

问题编号10。 打开新的浏览器窗口和标签


如何使用window.open()方法打开一个新的浏览器窗口,而不是一个新的选项卡?

▍答案


window.open()方法的确切行为取决于浏览器。 Opera始终打开新标签页(尽管它们看起来像窗口),而Safari始终打开窗口(尽管此行为可以更改)。 可以控制Chrome,Firefox和Internet Explorer的行为。

因此,如果将其他参数(窗口位置window.open()传递给window.open()方法,则将打开一个新窗口:

 window.open('http://www.google.com', '_blank', 'toolbar=0,location=0,menubar=0'); 

如果仅将链接传递给此方法,则将打开一个新的浏览器选项卡:

 window.open('http://www.google.com'); 

通常,您需要打开一个新的浏览器标签。 Safari浏览器中可能与此有关。 默认情况下(取决于设置),浏览器在调用window.open()会打开一个新窗口。 但是,如果您单击链接,同时按下Ctrl + Shift/Meta + Shift ,则会打开一个新标签页(无论设置如何)。 在下面的示例中,我们将模拟Ctrl + Shift/Meta + Shift键时引发的click事件:

 function safariOpenWindowInNewTab (href) {    var event = document.createEvent ('MouseEvents'),        mac = (navigator.userAgent.indexOf ('Macintosh')> = 0); //  Ctrl + Shift + LeftClick / Meta + Shift + LeftClick ()    //       event.initMouseEvent (        / * type * / "click",        / * canBubble * / true        / * cancelable * / true,        / * view * / window,        / * detail * / 0,        / * screenX, screenY, clientX, clientY * / 0, 0, 0, 0,        / * ctrlKey * /! mac,        / * altKey * / false,        / * shiftKey * / true        / * metaKey * / mac,        / * button * / 0,        / * relatedTarget * / null    ); //     ,   ,         $ ('<a/>', {'href': href, 'target': '_blank'}) [0] .dispatchEvent (event); } 

问题11 深度复制对象


如何有效地组织对象的深度复制?

▍答案


如果您要创建其副本的对象(我们称其为oldObject )没有更改,那么通过其原型进行此操作将是最有效的(此操作非常快):

 function object(o) {   function F() {}   F.prototype = o;   return new F(); } var newObject = object(oldObject); 

如果您确实需要执行克隆对象的操作,则最快的方法将是递归地优化该过程并遍历其属性。 也许这是创建对象深层副本的最快算法:

 var cloner = {   _clone: function _clone(obj) {       if (obj instanceof Array) {           var out = [];           for (var i = 0, len = obj.length; i < len; i++) {               var value = obj[i];               out[i] = (value !== null && typeof value === "object") ? _clone(value) : value;           }       } else {           var out = {};           for (var key in obj) {               if (obj.hasOwnProperty(key)) {                   var value = obj[key];                   out[key] = (value !== null && typeof value === "object") ? _clone(value) : value;               }           }       }       return out;   }, clone: function(it) {       return this._clone({       it: it       }).it;   } }; var newObject = cloner.clone(oldObject); 

如果使用jQuery,则可以采用以下构造:

 //   var newObject = jQuery.extend ({}, oldObject); //   var newObject = jQuery.extend (true, {}, oldObject); 

问题编号12。 JavaScript析构函数


如何在JavaScript中创建类似析构函数的东西? 如何管理对象的生命周期?

▍答案


在JavaScript中,对象的最后一个引用消失后,该对象将从内存中删除:

 var a = {z: 'z'}; var b = a; var c = a; delete az; delete a; //    a console.log (b, c); // ,  ,    

在JavaScript中使用“析构函数”之类的东西只会清除对象的内容,而不会从内存中删除它。

问题编号13。 二进制数据处理


是否可以在JavaScript中处理二进制数据? 如果是这样,怎么办?

▍答案


如果需要在JavaScript应用程序中使用二进制数据,则可以尝试使用Binary Parser库。 但是她的代码是地狱。 在ES6 +中,有一个关于StructType类型的建议(这与C ++中通过给定struct的复合类型所提供的建议相同)。 需要此数据类型来简化二进制数据的使用。 使用它可能看起来像这样:

 const Point2D = new StructType({ x: uint32, y: uint32 }); const Color = new StructType({ r: uint8, g: uint8, b: uint8 }); const Pixel = new StructType({ point: Point2D, color: Color }); const Triangle = new ArrayType(Pixel, 3); let t = new Triangle([{ point: { x:  0, y: 0 }, color: { r: 255, g: 255, b: 255 } },                     { point: { x: 5, y: 5 }, color: { r: 128, g: 0,   b: 0 } },                     { point: { x: 10, y: 0 }, color: { r: 0,   g: 0, b: 128 } }]); 

问题编号14。 从另一个功能更改一个功能中的变量


如何从一个函数更改另一个函数中的变量?

▍答案


在这里您可以应用几种方法:

  1. 您可以使用smth()函数中指向我们感兴趣的函数上下文的链接primer()在以下示例中为primer()函数smth()
  2. 您可以将在primer()函数的上下文中创建的函数传递给smth()函数。

     var primer = function () {    var a, b, c, d, e = {}; smth (function () {        a = 1;        b = 2;        c = 3;        d = 4;    }, e); alert ([a, b, c, d, e.pewpew]); }, smth = function (callback, e) {    callback ();    e.pewpew = "pewpew"; }; primer (); 
  3. 以前(在Firefox 3.6之前),您可以使用__parent__属性访问上下文,但是在Firefox 4中已经删除了此功能。

问题编号15。 使用功能


告诉我们如何在JavaScript中调用函数。

▍答案


我想在正常使用它们时,无需讨论如何调用函数,方法和构造函数。 让我们讨论一下如何使用call()apply()方法。

使用call()配置对象的构造函数


 //   function extend (newObj, oldObj) {   function F () {};   F.prototype = oldObj.prototype;   newObj.prototype = new F ();   return newObj }; var Obj = function () {   this.obj_var = 100; }; Obj.prototype.obj_proto_var = 101; var NewObj = function () {   Obj.call (this); //   Obj     obj_var   this.new_obj_var = 102; }; extend (NewObj, Obj) NewObj.prototype.new_obj_proto_var = 103; new NewObj (); // {new_obj_proto_var: 103, new_obj_var: 102, obj_proto_var: 101, obj_var: 100} 

将类似数组的对象转换为数组


类似数组的对象与JavaScript数组相似,但与之不同。 具体而言,这表示为这样的对象没有普通数组的方法。 在这些对象中,例如,可以注意到传统函数的arguments对象以及getElementsByTagName()方法的结果。

 // document.getElementsByTagName ("div")  ,   ,    , ,  ,        document.getElementsByTagName ("div"). forEach (function (elem) {    // ... }); // TypeError: document.getElementsByTagName ("div"). forEach is not a function //    Array.prototype.slice.call(document.getElementsByTagName("div")).forEach (function (elem) {   // OK }); //        console.log(Array.prototype.slice.call ('pewpew')) // ["p", "e", "w", "p", "e", "w"] //  IE8    

创建包装对象


这种使用call()apply()允许您创建包装对象。 , - foo() , bar() .

:

 function bar () {console.log(arguments)} // foo (context, arg1, arg2, ...) function foo () {    var context = arguments [0];    var args = Array.prototype.slice.call (arguments, 1); //     bar    bar.apply (context, args); } 

Function.call.apply() :

 function foo() {   Function.call.apply(bar, arguments); } 

, Function.call.apply() , , foo() , bar .

№16.


, , ?


. , Firefox 3.6, __parent__ , .

№17.


, , , eval() ?


, . , :

 // 1:  eval() (function () {    "use strict";    var globalObject = (0, eval) ("this"); //  :)    return globalObject; } ()); // 2:    (function (global) {    // ... } (window)); // 3:      (function () {    return this; } ()); // 4:            ,       . //  -    "use strict"; (function (global) {    // global }) (this); 

№18.


, JavaScript, , ?


JavaScript - « ». . . , :

 $('#smth').click(function onSmthClick(event) {   if (smth) {       //         event.handlerFunction = onSmthClick;       event.handlerContext = this;       //         //  otherObjectSetSomeEvent   event.handlerFunction          otherObjectSetSomeEvent(event);   } else {       //  -    } }); 

— , . , 2 :

 $('#smth'). click (function handler1 (event) {   if (smth) {       //         leftObjectSetSomeEvent(event, function handler2 (e) {           //  -   e       });   } else {       //  -    } }); function leftObjectSetSomeEvent(event, callback) {   callback (event);   //    } 

№19.


JavaScript ? — , .


click «» DOM. (, , ).

 // jQuery $(window).bind ('click', function (e) {   console.log ('Clicked on', e.target); }); //       $('#pewpew').delegate ('*', 'click', function(e) {   console.log('Clicked on', e.target); }); //     $('#pewpew').delegate('.pewpew', 'click', function (e) {   console.log ('Clicked on element with .pewpew class name'); }); 

№20. XHR-


XHR- jQuery?


, - :

 function xhr(m, u, c, x) { with(new XMLHttpRequest) onreadystatechange = function (x) {   readyState ^ 4 || c(x.target) }, open(m, u), send() } 

- :

 function xhr(m, u, c, x) { with(new(this.XMLHttpRequest || ActiveXObject)("Microsoft.XMLHTTP")) onreadystatechange = function (x) {   readyState ^ 4 || c(x) }, open(m, u), send() } 

:

 xhr('get', '//google.com/favicon.ico', function (xhr) { console.dir(xhr) }); 

№21. -


(reflow) (repaint) -?


  1. requestAnimationFrame() , , setInterval() setTimeout() . , , , , , . , JavaScript- CSS- SVG-. , , , , , , . , , .
  2. float- ( ).
  3. DOM. , , , DOM ( ).
  4. — . ( , , ). 这是一个例子:

     //      element.style.left = "150px;"; // ... element.style.color = "green"; //  ,   ,    element.setAttribute ('style', 'color: green; left: 150px'); 
  5. ( ).
  6. — ( style.display = "none" ). , .

, -, . , , , , .

  1. .
  2. DOM- ( — ).
  3. Document.querySelectorAll() firstElementChild .
  4. , document.getElementsByTagName() ( , DOM, ).

№22. Node.js


Node.js, , ?


, ( PHP Apache). , , . Node.js — . , , cluster . (master) - (worker). , .

№23. runInNewContext() Node.js


runInNewContext() Node.js.


. , ( Node.js- Nodester). - , runInNewContext() . , «», . «» , , runInNewContext() .

总结


JavaScript . , - , .

亲爱的读者们! , - , , JavaScript , ?

Source: https://habr.com/ru/post/zh-CN436454/


All Articles